diff --git a/schema/plan-0.7.0.dtd b/schema/plan-0.7.0.dtd index dff9e7c5..773f2fff 100644 --- a/schema/plan-0.7.0.dtd +++ b/schema/plan-0.7.0.dtd @@ -1,360 +1,369 @@ - + - + + + - + ---> + country CDATA #IMPLIED> - + + + + + + - - + + + day (0|1|2|3|4|5|6) "0"> - - + + length CDATA #IMPLIED> - + - - - - + - + - + - + + + + + - - + + - + + + + + + + + - + diff --git a/schema/plan-0.7.0.xsd b/schema/plan-0.7.0.xsd index 4b5fd1a1..c9a5a5a4 100644 --- a/schema/plan-0.7.0.xsd +++ b/schema/plan-0.7.0.xsd @@ -1,528 +1,590 @@ - - - - + - - - - - - + + + + + + + + + + + + + + + + + + - + - - + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - + - - - + - - - - - - - - + + + + + + + - - + + + + + + + + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/src/libs/kernel/KPlatoXmlLoaderBase.cpp b/src/libs/kernel/KPlatoXmlLoaderBase.cpp index 828fbd63..808839b6 100644 --- a/src/libs/kernel/KPlatoXmlLoaderBase.cpp +++ b/src/libs/kernel/KPlatoXmlLoaderBase.cpp @@ -1,1363 +1,1363 @@ /* This file is part of the KDE project Copyright (C) 2010 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 "KPlatoXmlLoaderBase.h" #include "kptlocale.h" #include "kptxmlloaderobject.h" #include "kptnode.h" #include "kptproject.h" #include "kpttask.h" #include "kptcalendar.h" #include "kptschedule.h" #include "kptrelation.h" #include "kptresource.h" #include "kptaccount.h" #include "kptappointment.h" #include #include #include using namespace KPlato; KPlatoXmlLoaderBase::KPlatoXmlLoaderBase() { } bool KPlatoXmlLoaderBase::load(Project *project, const KoXmlElement &element, XMLLoaderObject &status) { debugPlanXml<<"project"; // load locale first KoXmlNode n = element.firstChild(); for (; ! n.isNull(); n = n.nextSibling()) { if (! n.isElement()) { continue; } KoXmlElement e = n.toElement(); if (e.tagName() == "locale") { Locale *l = project->locale(); l->setCurrencySymbol(e.attribute("currency-symbol", l->currencySymbol())); //NOTE: decimalplaces missing } } QList cals; QString s; bool ok = false; project->setName(element.attribute("name")); project->removeId(project->id()); project->setId(element.attribute("id")); project->registerNodeId(project); project->setLeader(element.attribute("leader")); project->setDescription(element.attribute("description")); QTimeZone tz(element.attribute("timezone").toLatin1()); if (tz.isValid()) { project->setTimeZone(tz); } else warnPlanXml<<"No timezone specified, using default (local)"; status.setProjectTimeZone(project->timeZone()); // Allow for both numeric and text s = element.attribute("scheduling", "0"); project->setConstraint((Node::ConstraintType) (s.toInt(&ok))); if (! ok) project->setConstraint(s); if (project->constraint() != Node::MustStartOn && project->constraint() != Node::MustFinishOn) { errorPlanXml << "Illegal constraint: " << project->constraintToString(); project->setConstraint(Node::MustStartOn); } s = element.attribute("start-time"); if (! s.isEmpty()) { project->setConstraintStartTime(DateTime::fromString(s, project->timeZone())); } s = element.attribute("end-time"); if (! s.isEmpty()) { project->setConstraintEndTime(DateTime::fromString(s, project->timeZone())); } // Load the project children // Do calendars first, they only reference other calendars //debugPlanXml<<"Calendars--->"; n = element.firstChild(); for (; ! n.isNull(); n = n.nextSibling()) { if (! n.isElement()) { continue; } KoXmlElement e = n.toElement(); if (e.tagName() == "calendar") { // Load the calendar. // Referenced by resources Calendar * child = new Calendar(); child->setProject(project); if (load(child, e, status)) { cals.append(child); // temporary, reorder later } else { // TODO: Complain about this errorPlanXml << "Failed to load calendar"; delete child; } } else if (e.tagName() == "standard-worktime") { // Load standard worktime StandardWorktime * child = new StandardWorktime(); if (load(child, e, status)) { project->setStandardWorktime(child); } else { errorPlanXml << "Failed to load standard worktime"; delete child; } } } // calendars references calendars in arbitrary saved order bool added = false; do { added = false; QList lst; while (!cals.isEmpty()) { Calendar *c = cals.takeFirst(); if (c->parentId().isEmpty()) { project->addCalendar(c, status.baseCalendar()); // handle pre 0.6 version added = true; //debugPlanXml<<"added to project:"<name(); } else { Calendar *par = project->calendar(c->parentId()); if (par) { project->addCalendar(c, par); added = true; //debugPlanXml<<"added:"<name()<<" to parent:"<name(); } else { lst.append(c); // treat later //debugPlanXml<<"treat later:"<name(); } } } cals = lst; } while (added); if (! cals.isEmpty()) { errorPlanXml<<"All calendars not saved!"; } //debugPlanXml<<"Calendars<---"; // Resource groups and resources, can reference calendars n = element.firstChild(); for (; ! n.isNull(); n = n.nextSibling()) { if (! n.isElement()) { continue; } KoXmlElement e = n.toElement(); if (e.tagName() == "resource-group") { // Load the resources // References calendars ResourceGroup * child = new ResourceGroup(); if (load(child, e, status)) { project->addResourceGroup(child); } else { // TODO: Complain about this delete child; } } } // The main stuff n = element.firstChild(); for (; ! n.isNull(); n = n.nextSibling()) { if (! n.isElement()) { continue; } KoXmlElement e = n.toElement(); if (e.tagName() == "project") { //debugPlanXml<<"Sub project--->"; /* // Load the subproject Project * child = new Project(this); if (child->load(e)) { if (!addTask(child, this)) { delete child; // TODO: Complain about this } } else { // TODO: Complain about this delete child; }*/ } else if (e.tagName() == "task") { //debugPlanXml<<"Task--->"; // Load the task (and resourcerequests). // Depends on resources already loaded Task *child = new Task(project); if (load(child, e, status)) { if (! project->addTask(child, project)) { delete child; // TODO: Complain about this } } else { // TODO: Complain about this delete child; } } } // These go last n = element.firstChild(); for (; ! n.isNull(); n = n.nextSibling()) { debugPlanXml<"; // Load accounts // References tasks if (! load(project->accounts(), e, status)) { errorPlanXml << "Failed to load accounts"; } } else if (e.tagName() == "relation") { //debugPlanXml<<"Relation--->"; // Load the relation // References tasks Relation * child = new Relation(); if (! load(child, e, status)) { // TODO: Complain about this errorPlanXml << "Failed to load relation"; delete child; } //debugPlanXml<<"Relation<---"; } else if (e.tagName() == "schedules") { debugPlanXml<<"Project schedules & task appointments--->"; // References tasks and resources KoXmlNode sn = e.firstChild(); for (; ! sn.isNull(); sn = sn.nextSibling()) { if (! sn.isElement()) { continue; } KoXmlElement el = sn.toElement(); //debugPlanXml<findScheduleManagerByName(el.attribute("name")); if (sm == 0) { sm = new ScheduleManager(*project, el.attribute("name")); add = true; } } } else if (el.tagName() == "plan") { sm = new ScheduleManager(*project); add = true; } if (sm) { if (load(sm, el, status)) { if (add) project->addScheduleManager(sm); } else { errorPlanXml << "Failed to load schedule manager"; delete sm; } } else { debugPlanXml<<"No schedule manager ?!"; } } debugPlanXml<<"Project schedules & task appointments<---"; } else if (e.tagName() == "resource-teams") { //debugPlanXml<<"Resource teams--->"; // References other resources KoXmlNode tn = e.firstChild(); for (; ! tn.isNull(); tn = tn.nextSibling()) { if (! tn.isElement()) { continue; } KoXmlElement el = tn.toElement(); if (el.tagName() == "team") { Resource *r = project->findResource(el.attribute("team-id")); Resource *tm = project->findResource(el.attribute("member-id")); if (r == 0 || tm == 0) { errorPlanXml<<"resource-teams: cannot find resources"; continue; } if (r == tm) { errorPlanXml<<"resource-teams: a team cannot be a member of itself"; continue; } r->addTeamMemberId(tm->id()); } else { errorPlanXml<<"resource-teams: unhandled tag"<wbsDefinition(), e, status); } else if (e.tagName() == "locale") { // handled earlier } else if (e.tagName() == "resource-group") { // handled earlier } else if (e.tagName() == "calendar") { // handled earlier } else if (e.tagName() == "standard-worktime") { // handled earlier } else if (e.tagName() == "project") { // handled earlier } else if (e.tagName() == "task") { // handled earlier } else { warnPlanXml<<"Unhandled tag:"<schedules()) { project->setParentSchedule(s); } debugPlanXml<<"Project loaded:"<name()<allNodes(); return true; } bool KPlatoXmlLoaderBase::load(Task *task, const KoXmlElement &element, XMLLoaderObject &status) { debugPlanXml<<"task"; QString s; bool ok = false; task->setId(element.attribute("id")); task->setName(element.attribute("name")); task->setLeader(element.attribute("leader")); task->setDescription(element.attribute("description")); //debugPlanXml<setConstraint((Node::ConstraintType)constraint.toInt(&ok)); if (! ok) { task->setConstraint(constraint); } s = element.attribute("constraint-starttime"); if (! s.isEmpty()) { task->setConstraintStartTime(DateTime::fromString(s, status.projectTimeZone())); } s = element.attribute("constraint-endtime"); if (! s.isEmpty()) { task->setConstraintEndTime(DateTime::fromString(s, status.projectTimeZone())); } task->setStartupCost(element.attribute("startup-cost", "0.0").toDouble()); task->setShutdownCost(element.attribute("shutdown-cost", "0.0").toDouble()); // Load the task children KoXmlNode n = element.firstChild(); for (; ! n.isNull(); n = n.nextSibling()) { if (! n.isElement()) { continue; } KoXmlElement e = n.toElement(); if (e.tagName() == "project") { // Load the subproject /* Project *child = new Project(this, status); if (child->load(e)) { if (!project.addSubTask(child, this)) { delete child; // TODO: Complain about this } } else { // TODO: Complain about this delete child; }*/ } else if (e.tagName() == "task") { // Load the task Task *child = new Task(task); if (load(child, e, status)) { if (! status.project().addSubTask(child, task)) { delete child; // TODO: Complain about this } } else { // TODO: Complain about this delete child; } } else if (e.tagName() == "resource") { // tasks don't have resources } else if (e.tagName() == "estimate" || (/*status.version() < "0.6" &&*/ e.tagName() == "effort")) { // Load the estimate load(task->estimate(), e, status); } else if (e.tagName() == "resourcegroup-request") { // Load the resource request // Handle multiple requests to same group gracefully (Not really allowed) ResourceGroupRequest *r = task->requests().findGroupRequestById(e.attribute("group-id")); if (r) { warnPlanXml<<"Multiple requests to same group, loading into existing group"; if (! load(r, e, status)) { errorPlanXml<<"Failed to load resource request"; } } else { r = new ResourceGroupRequest(); if (load(r, e, status)) { task->addRequest(r); } else { errorPlanXml<<"Failed to load resource request"; delete r; } } } else if (e.tagName() == "workpackage") { load(task->workPackage(), e, status); } else if (e.tagName() == "progress") { load(task->completion(), e, status); } else if (e.tagName() == "schedules") { KoXmlNode n = e.firstChild(); for (; ! n.isNull(); n = n.nextSibling()) { if (! n.isElement()) { continue; } KoXmlElement el = n.toElement(); if (el.tagName() == "schedule") { NodeSchedule *sch = new NodeSchedule(); if (loadNodeSchedule(sch, el, status)) { sch->setNode(task); task->addSchedule(sch); } else { errorPlanXml<<"Failed to load schedule"; delete sch; } } } } else if (e.tagName() == "documents") { load(task->documents(), e, status); } else if (e.tagName() == "workpackage-log") { KoXmlNode n = e.firstChild(); for (; ! n.isNull(); n = n.nextSibling()) { if (! n.isElement()) { continue; } KoXmlElement el = n.toElement(); if (el.tagName() == "workpackage") { WorkPackage *wp = new WorkPackage(task); if (loadWpLog(wp, el, status)) { task->addWorkPackage(wp); } else { errorPlanXml<<"Failed to load logged workpackage"; delete wp; } } } } } //debugPlanXml<setId(element.attribute("id")); calendar->setParentId(element.attribute("parent")); calendar->setName(element.attribute("name","")); QTimeZone tz(element.attribute("timezone").toLatin1()); if (tz.isValid()) { calendar->setTimeZone(tz); } else warnPlanXml<<"No timezone specified, use default (local)"; bool dc = (bool)element.attribute("default","0").toInt(); if (dc) { status.project().setDefaultCalendar(calendar); } KoXmlNode n = element.firstChild(); for (; ! n.isNull(); n = n.nextSibling()) { if (! n.isElement()) { continue; } KoXmlElement e = n.toElement(); if (e.tagName() == "weekday") { if (! load(calendar->weekdays(), e, status)) { return false; } } if (e.tagName() == "day") { CalendarDay *day = new CalendarDay(); if (load(day, e, status)) { if (! day->date().isValid()) { delete day; errorPlanXml<name()<<": Failed to load calendarDay - Invalid date"; } else { CalendarDay *d = calendar->findDay(day->date()); if (d) { // already exists, keep the new delete calendar->takeDay(d); warnPlanXml<name()<<" Load calendarDay - Date already exists"; } calendar->addDay(day); } } else { delete day; errorPlanXml<<"Failed to load calendarDay"; return true; // don't throw away the whole calendar } } } return true; } bool KPlatoXmlLoaderBase::load(CalendarDay *day, const KoXmlElement &element, XMLLoaderObject &status) { debugPlanXml<<"day"; bool ok=false; day->setState(QString(element.attribute("state", "-1")).toInt(&ok)); if (day->state() < 0) { return false; } //debugPlanXml<<" state="<setDate(QDate::fromString(s, Qt::ISODate)); if (! day->date().isValid()) { day->setDate(QDate::fromString(s)); } } day->clearIntervals(); KoXmlNode n = element.firstChild(); for (; ! n.isNull(); n = n.nextSibling()) { if (! n.isElement()) { continue; } KoXmlElement e = n.toElement(); if (e.tagName() == "interval") { //debugPlanXml<<"Interval start="<addInterval(new TimeInterval(start, length)); } } return true; } bool KPlatoXmlLoaderBase::load(CalendarWeekdays *weekdays, const KoXmlElement& element, XMLLoaderObject& status) { debugPlanXml<<"weekdays"; bool ok; int dayNo = QString(element.attribute("day","-1")).toInt(&ok); if (dayNo < 0 || dayNo > 6) { errorPlanXml<<"Illegal weekday: "<weekday(dayNo + 1); if (day == 0) { errorPlanXml<<"No weekday: "<setState(CalendarDay::None); } return true; } bool KPlatoXmlLoaderBase::load(StandardWorktime *swt, const KoXmlElement &element, XMLLoaderObject &status) { debugPlanXml<<"swt"; swt->setYear(Duration::fromString(element.attribute("year"), Duration::Format_Hour)); swt->setMonth(Duration::fromString(element.attribute("month"), Duration::Format_Hour)); swt->setWeek(Duration::fromString(element.attribute("week"), Duration::Format_Hour)); swt->setDay(Duration::fromString(element.attribute("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() == "calendar") { // pre 0.6 version stored base calendar in standard worktime if (status.version() >= "0.6") { warnPlanXml<<"Old format, calendar in standard worktime"; warnPlanXml<<"Tries to load anyway"; } // try to load anyway Calendar *calendar = new Calendar; if (load(calendar, e, status)) { status.project().addCalendar(calendar); calendar->setDefault(true); status.project().setDefaultCalendar(calendar); // hmmm status.setBaseCalendar(calendar); } else { delete calendar; errorPlanXml<<"Failed to load calendar"; } } } return true; } bool KPlatoXmlLoaderBase::load(Relation *relation, const KoXmlElement &element, XMLLoaderObject &status) { debugPlanXml<<"relation"; relation->setParent(status.project().findNode(element.attribute("parent-id"))); if (relation->parent() == 0) { warnPlanXml<<"Parent node == 0, cannot find id:"<setChild(status.project().findNode(element.attribute("child-id"))); if (relation->child() == 0) { warnPlanXml<<"Child node == 0, cannot find id:"<child() == relation->parent()) { warnPlanXml<<"Parent node == child node"; return false; } if (! relation->parent()->legalToLink(relation->child())) { warnPlanXml<<"Realation is not legal:"<parent()->name()<<"->"<child()->name(); return false; } relation->setType(element.attribute("type")); relation->setLag(Duration::fromString(element.attribute("lag"))); if (! relation->parent()->addDependChildNode(relation)) { errorPlanXml<<"Failed to add relation: Child="<child()->name()<<" parent="<parent()->name(); return false; } if (! relation->child()->addDependParentNode(relation)) { relation->parent()->takeDependChildNode(relation); errorPlanXml<<"Failed to add relation: Child="<child()->name()<<" parent="<parent()->name(); return false; } //debugPlanXml<<"Added relation: Child="<child()->name()<<" parent="<parent()->name(); return true; } bool KPlatoXmlLoaderBase::load(ResourceGroup *rg, const KoXmlElement &element, XMLLoaderObject &status) { debugPlanXml<<"resource-group"; rg->setId(element.attribute("id")); rg->setName(element.attribute("name")); rg->setType(element.attribute("type")); 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 (load(child, e, status)) { child->addParentGroup(rg); status.project().addResource(child); } else { // TODO: Complain about this delete child; } } } return true; } bool KPlatoXmlLoaderBase::load(Resource *resource, const KoXmlElement &element, XMLLoaderObject &status) { debugPlanXml<<"resource"; const Locale *locale = status.project().locale(); QString s; resource->setId(element.attribute("id")); resource->setName(element.attribute("name")); resource->setInitials(element.attribute("initials")); resource->setEmail(element.attribute("email")); resource->setType(element.attribute("type")); resource->setCalendar(status.project().findCalendar(element.attribute("calendar-id"))); resource->setUnits(element.attribute("units", "100").toInt()); s = element.attribute("available-from"); if (! s.isEmpty()) { resource->setAvailableFrom(DateTime::fromString(s, status.projectTimeZone())); } s = element.attribute("available-until"); if (! s.isEmpty()) { resource->setAvailableUntil(DateTime::fromString(s, status.projectTimeZone())); } resource->setNormalRate(locale->readMoney(element.attribute("normal-rate"))); resource->setOvertimeRate(locale->readMoney(element.attribute("overtime-rate"))); resource->setAccount(status.project().accounts().findAccount(element.attribute("account"))); KoXmlElement e; KoXmlElement parent = element.namedItem("required-resources").toElement(); forEachElement(e, parent) { if (e.nodeName() == "resource") { QString id = e.attribute("id"); if (id.isEmpty()) { warnPlanXml<<"Missing resource id"; continue; } resource->addRequiredId(id); } } parent = element.namedItem("external-appointments").toElement(); forEachElement(e, parent) { if (e.nodeName() == "project") { QString id = e.attribute("id"); if (id.isEmpty()) { errorPlanXml<<"Missing project id"; continue; } resource->clearExternalAppointments(id); // in case... AppointmentIntervalList lst; load(lst, e, status); Appointment *a = new Appointment(); a->setIntervals(lst); a->setAuxcilliaryInfo(e.attribute("name", "Unknown")); resource->addExternalAppointment(id, a); } } return true; } bool KPlatoXmlLoaderBase::load(Accounts &accounts, const KoXmlElement &element, XMLLoaderObject &status) { debugPlanXml<<"accounts"; KoXmlNode n = element.firstChild(); for (; ! n.isNull(); n = n.nextSibling()) { if (! n.isElement()) { continue; } KoXmlElement e = n.toElement(); if (e.tagName() == "account") { Account *child = new Account(); if (load(child, e, status)) { accounts.insert(child); } else { // TODO: Complain about this warnPlanXml<<"Loading failed"; delete child; } } } if (element.hasAttribute("default-account")) { accounts.setDefaultAccount(accounts.findAccount(element.attribute("default-account"))); if (accounts.defaultAccount() == 0) { warnPlanXml<<"Could not find default account."; } } return true; } bool KPlatoXmlLoaderBase::load(Account* account, const KoXmlElement& element, XMLLoaderObject& status) { debugPlanXml<<"account"; account->setName(element.attribute("name")); account->setDescription(element.attribute("description")); KoXmlNode n = element.firstChild(); for (; ! n.isNull(); n = n.nextSibling()) { if (! n.isElement()) { continue; } KoXmlElement e = n.toElement(); if (e.tagName() == "costplace") { Account::CostPlace *child = new Account::CostPlace(account); if (load(child, e, status)) { account->append(child); } else { delete child; } } else if (e.tagName() == "account") { Account *child = new Account(); if (load(child, e, status)) { account->insert(child); } else { // TODO: Complain about this warnPlanXml<<"Loading failed"; delete child; } } } return true; } bool KPlatoXmlLoaderBase::load(Account::CostPlace* cp, const KoXmlElement& element, XMLLoaderObject& status) { debugPlanXml<<"cost-place"; cp->setObjectId(element.attribute("object-id")); if (cp->objectId().isEmpty()) { // check old format cp->setObjectId(element.attribute("node-id")); if (cp->objectId().isEmpty()) { errorPlanXml<<"No object id"; return false; } } cp->setNode(status.project().findNode(cp->objectId())); if (cp->node() == 0) { cp->setResource(status.project().findResource(cp->objectId())); if (cp->resource() == 0) { errorPlanXml<<"Cannot find object with id: "<objectId(); return false; } } bool on = (bool)(element.attribute("running-cost").toInt()); if (on) cp->setRunning(on); on = (bool)(element.attribute("startup-cost").toInt()); if (on) cp->setStartup(on); on = (bool)(element.attribute("shutdown-cost").toInt()); if (on) cp->setShutdown(on); return true; } bool KPlatoXmlLoaderBase::load(ScheduleManager *manager, const KoXmlElement &element, XMLLoaderObject &status) { debugPlanXml<<"schedule-manager"; MainSchedule *sch = 0; if (status.version() <= "0.5") { manager->setUsePert(false); MainSchedule *sch = loadMainSchedule(manager, element, status); if (sch && sch->type() == Schedule::Expected) { sch->setManager(manager); manager->setExpected(sch); } else { delete sch; } return true; } manager->setName(element.attribute("name")); manager->setManagerId(element.attribute("id")); manager->setUsePert(element.attribute("distribution").toInt() == 1); manager->setAllowOverbooking((bool)(element.attribute("overbooking").toInt())); manager->setCheckExternalAppointments((bool)(element.attribute("check-external-appointments").toInt())); manager->setSchedulingDirection((bool)(element.attribute("scheduling-direction").toInt())); manager->setBaselined((bool)(element.attribute("baselined").toInt())); manager->setSchedulerPluginId(element.attribute("scheduler-plugin-id")); manager->setRecalculate((bool)(element.attribute("recalculate").toInt())); manager->setRecalculateFrom(DateTime::fromString(element.attribute("recalculate-from"), status.projectTimeZone())); KoXmlNode n = element.firstChild(); for (; ! n.isNull(); n = n.nextSibling()) { if (! n.isElement()) { continue; } KoXmlElement e = n.toElement(); //debugPlanXml<type() == Schedule::Expected) { sch->setManager(manager); manager->setExpected(sch); break; } else { delete sch; } } else if (e.tagName() == "plan") { ScheduleManager *sm = new ScheduleManager(status.project()); if (load(sm, e, status)) { status.project().addScheduleManager(sm, manager); } else { errorPlanXml<<"Failed to load schedule manager"; delete sm; } } } return true; } bool KPlatoXmlLoaderBase::load(Schedule *schedule, const KoXmlElement& element, XMLLoaderObject& /*status*/) { debugPlanXml<<"schedule"; schedule->setName(element.attribute("name")); schedule->setType(element.attribute("type")); schedule->setId(element.attribute("id").toLong()); return true; } MainSchedule* KPlatoXmlLoaderBase::loadMainSchedule(ScheduleManager* /*manager*/, const KoXmlElement& element, XMLLoaderObject& status) { debugPlanXml<<"main-schedule"; MainSchedule *sch = new MainSchedule(); if (loadMainSchedule(sch, element, status)) { status.project().addSchedule(sch); sch->setNode(&(status.project())); status.project().setParentSchedule(sch); // If it's here, it's scheduled! sch->setScheduled(true); } else { errorPlanXml << "Failed to load schedule"; delete sch; sch = 0; } return sch; } bool KPlatoXmlLoaderBase::loadMainSchedule(MainSchedule *ms, const KoXmlElement &element, XMLLoaderObject &status) { debugPlanXml; QString s; load(ms, element, status); s = element.attribute("start"); if (!s.isEmpty()) { ms->startTime = DateTime::fromString(s, status.projectTimeZone()); } s = element.attribute("end"); if (!s.isEmpty()) { ms->endTime = DateTime::fromString(s, status.projectTimeZone()); } ms->duration = Duration::fromString(element.attribute("duration")); ms->constraintError = element.attribute("scheduling-conflict", "0").toInt(); KoXmlNode n = element.firstChild(); for (; ! n.isNull(); n = n.nextSibling()) { if (! n.isElement()) { continue; } KoXmlElement el = n.toElement(); if (el.tagName() == "appointment") { // Load the appointments. // Resources and tasks must already be loaded Appointment * child = new Appointment(); if (! load(child, el, status, *ms)) { // TODO: Complain about this errorPlanXml << "Failed to load appointment" << endl; delete child; } } else if (el.tagName() == "criticalpath-list") { // Tasks must already be loaded for (KoXmlNode n1 = el.firstChild(); ! n1.isNull(); n1 = n1.nextSibling()) { if (! n1.isElement()) { continue; } KoXmlElement e1 = n1.toElement(); if (e1.tagName() != "criticalpath") { continue; } QList lst; for (KoXmlNode n2 = e1.firstChild(); ! n2.isNull(); n2 = n2.nextSibling()) { if (! n2.isElement()) { continue; } KoXmlElement e2 = n2.toElement(); if (e2.tagName() != "node") { continue; } QString s = e2.attribute("id"); Node *node = status.project().findNode(s); if (node) { lst.append(node); } else { errorPlanXml<<"Failed to find node id="<m_pathlists.append(lst); } ms->criticalPathListCached = true; } } return true; } bool KPlatoXmlLoaderBase::loadNodeSchedule(NodeSchedule* schedule, const KoXmlElement &element, XMLLoaderObject& status) { debugPlanXml<<"node-schedule"; QString s; load(schedule, element, status); s = element.attribute("earlystart"); if (s.isEmpty()) { // try version < 0.6 s = element.attribute("earlieststart"); } if (! s.isEmpty()) { schedule->earlyStart = DateTime::fromString(s, status.projectTimeZone()); } s = element.attribute("latefinish"); if (s.isEmpty()) { // try version < 0.6 s = element.attribute("latestfinish"); } if (! s.isEmpty()) { schedule->lateFinish = DateTime::fromString(s, status.projectTimeZone()); } s = element.attribute("latestart"); if (! s.isEmpty()) { schedule->lateStart = DateTime::fromString(s, status.projectTimeZone()); } s = element.attribute("earlyfinish"); if (! s.isEmpty()) { schedule->earlyFinish = DateTime::fromString(s, status.projectTimeZone()); } s = element.attribute("start"); if (! s.isEmpty()) schedule->startTime = DateTime::fromString(s, status.projectTimeZone()); s = element.attribute("end"); if (!s.isEmpty()) schedule->endTime = DateTime::fromString(s, status.projectTimeZone()); s = element.attribute("start-work"); if (!s.isEmpty()) schedule->workStartTime = DateTime::fromString(s, status.projectTimeZone()); s = element.attribute("end-work"); if (!s.isEmpty()) schedule->workEndTime = DateTime::fromString(s, status.projectTimeZone()); schedule->duration = Duration::fromString(element.attribute("duration")); schedule->inCriticalPath = element.attribute("in-critical-path", "0").toInt(); schedule->resourceError = element.attribute("resource-error", "0").toInt(); schedule->resourceOverbooked = element.attribute("resource-overbooked", "0").toInt(); schedule->resourceNotAvailable = element.attribute("resource-not-available", "0").toInt(); schedule->constraintError = element.attribute("scheduling-conflict", "0").toInt(); schedule->notScheduled = element.attribute("not-scheduled", "1").toInt(); schedule->positiveFloat = Duration::fromString(element.attribute("positive-float")); schedule->negativeFloat = Duration::fromString(element.attribute("negative-float")); schedule->freeFloat = Duration::fromString(element.attribute("free-float")); return true; } bool KPlatoXmlLoaderBase::load(WBSDefinition &def, const KoXmlElement &element, XMLLoaderObject &/*status*/) { debugPlanXml<<"wbs-def"; def.setProjectCode(element.attribute("project-code")); def.setProjectSeparator(element.attribute("project-separator")); def.setLevelsDefEnabled((bool)element.attribute("levels-enabled", "0").toInt()); KoXmlNode n = element.firstChild(); for (; ! n.isNull(); n = n.nextSibling()) { if (! n.isElement()) { continue; } KoXmlElement e = n.toElement(); if (e.tagName() == "default") { def.defaultDef().code = e.attribute("code", "Number"); def.defaultDef().separator = e.attribute("separator", "."); } else if (e.tagName() == "levels") { KoXmlNode n = e.firstChild(); for (; ! n.isNull(); n = n.nextSibling()) { if (! n.isElement()) { continue; } KoXmlElement el = n.toElement(); WBSDefinition::CodeDef d; d.code = el.attribute("code"); d.separator = el.attribute("separator"); int lvl = el.attribute("level", "-1").toInt(); if (lvl >= 0) { def.setLevelsDef(lvl, d); } else errorPlanXml<<"Invalid levels definition"; } } } return true; } bool KPlatoXmlLoaderBase::load(Documents &documents, const KoXmlElement &element, XMLLoaderObject &status) { debugPlanXml<<"documents"; KoXmlNode n = element.firstChild(); for (; ! n.isNull(); n = n.nextSibling()) { if (! n.isElement()) { continue; } KoXmlElement e = n.toElement(); if (e.tagName() == "document") { Document *doc = new Document(); if (! load(doc, e, status)) { warnPlanXml<<"Failed to load document"; status.addMsg(XMLLoaderObject::Errors, "Failed to load document"); delete doc; } else { documents.addDocument(doc); status.addMsg(i18n("Document loaded, URL=%1", doc->url().url())); } } } return true; } bool KPlatoXmlLoaderBase::load(Document *document, const KoXmlElement &element, XMLLoaderObject &status) { debugPlanXml<<"document"; Q_UNUSED(status); document->setUrl(QUrl(element.attribute("url"))); document->setType((Document::Type)(element.attribute("type").toInt())); document->setStatus(element.attribute("status")); document->setSendAs((Document::SendAs)(element.attribute("sendas").toInt())); return true; } bool KPlatoXmlLoaderBase::load(Estimate* estimate, const KoXmlElement& element, XMLLoaderObject& status) { debugPlanXml<<"estimate"; estimate->setType(element.attribute("type")); estimate->setRisktype(element.attribute("risk")); if (status.version() <= "0.6") { estimate->setUnit((Duration::Unit)(element.attribute("display-unit", QString().number(Duration::Unit_h)).toInt())); QList s = status.project().standardWorktime()->scales(); estimate->setExpectedEstimate(Estimate::scale(Duration::fromString(element.attribute("expected")), estimate->unit(), s)); estimate->setOptimisticEstimate(Estimate::scale(Duration::fromString(element.attribute("optimistic")), estimate->unit(), s)); estimate->setPessimisticEstimate(Estimate::scale(Duration::fromString(element.attribute("pessimistic")), estimate->unit(), s)); } else { if (status.version() <= "0.6.2") { // 0 pos in unit is now Unit_Y, so add 3 to get the correct new unit estimate->setUnit((Duration::Unit)(element.attribute("unit", QString().number(Duration::Unit_ms - 3)).toInt() + 3)); } else { estimate->setUnit(Duration::unitFromString(element.attribute("unit"))); } estimate->setExpectedEstimate(element.attribute("expected", "0.0").toDouble()); estimate->setOptimisticEstimate(element.attribute("optimistic", "0.0").toDouble()); estimate->setPessimisticEstimate(element.attribute("pessimistic", "0.0").toDouble()); estimate->setCalendar(status.project().findCalendar(element.attribute("calendar-id"))); } return true; } bool KPlatoXmlLoaderBase::load(ResourceGroupRequest* gr, const KoXmlElement& element, XMLLoaderObject& status) { debugPlanXml<<"resourcegroup-request"; gr->setGroup(status.project().findResourceGroup(element.attribute("group-id"))); if (gr->group() == 0) { errorPlanXml<<"The referenced resource group does not exist: group id="<group()->registerRequest(gr); - + Q_ASSERT(gr->parent()); 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 (load(r, e, status)) { - gr->addResourceRequest(r); + gr->parent()->addResourceRequest(r, gr); } else { errorPlanXml<<"Failed to load resource request"; delete r; } } } // meaning of m_units changed int x = element.attribute("units").toInt() -gr->count(); gr->setUnits(x > 0 ? x : 0); return true; } bool KPlatoXmlLoaderBase::load(ResourceRequest *rr, const KoXmlElement& element, XMLLoaderObject& status) { debugPlanXml<<"resource-request"; rr->setResource(status.project().resource(element.attribute("resource-id"))); if (rr->resource() == 0) { warnPlanXml<<"The referenced resource does not exist: resource id="<setUnits(element.attribute("units").toInt()); KoXmlElement parent = element.namedItem("required-resources").toElement(); KoXmlElement e; QList required; forEachElement(e, parent) { if (e.nodeName() == "resource") { QString id = e.attribute("id"); if (id.isEmpty()) { errorPlanXml<<"Missing project id"; continue; } Resource *r = status.project().resource(id); if (r == 0) { warnPlanXml<<"The referenced resource does not exist: resource id="<resource()) { required << r; } } } } rr->setRequiredResources(required); return true; } bool KPlatoXmlLoaderBase::load(WorkPackage &wp, const KoXmlElement& element, XMLLoaderObject& status) { debugPlanXml<<"workpackage"; Q_UNUSED(status); wp.setOwnerName(element.attribute("owner")); wp.setOwnerId(element.attribute("owner-id")); return true; } bool KPlatoXmlLoaderBase::loadWpLog(WorkPackage *wp, KoXmlElement& element, XMLLoaderObject& status) { debugPlanXml<<"wplog"; wp->setOwnerName(element.attribute("owner")); wp->setOwnerId(element.attribute("owner-id")); wp->setTransmitionStatus(wp->transmitionStatusFromString(element.attribute("status"))); wp->setTransmitionTime(DateTime(QDateTime::fromString(element.attribute("time"), Qt::ISODate))); return load(wp->completion(), element, status); } bool KPlatoXmlLoaderBase::load(Completion &completion, const KoXmlElement& element, XMLLoaderObject& status) { debugPlanXml<<"completion"; QString s; completion.setStarted((bool)element.attribute("started", "0").toInt()); completion.setFinished((bool)element.attribute("finished", "0").toInt()); s = element.attribute("startTime"); if (!s.isEmpty()) { completion.setStartTime(DateTime::fromString(s, status.projectTimeZone())); } s = element.attribute("finishTime"); if (!s.isEmpty()) { completion.setFinishTime(DateTime::fromString(s, status.projectTimeZone())); } completion.setEntrymode(element.attribute("entrymode")); if (status.version() < "0.6") { if (completion.isStarted()) { Completion::Entry *entry = new Completion::Entry(element.attribute("percent-finished", "0").toInt(), Duration::fromString(element.attribute("remaining-effort")), Duration::fromString(element.attribute("performed-effort"))); entry->note = element.attribute("note"); QDate date = completion.startTime().date(); if (completion.isFinished()) { date = completion.finishTime().date(); } // almost the best we can do ;) completion.addEntry(date, entry); } } else { KoXmlElement e; forEachElement(e, element) { if (e.tagName() == "completion-entry") { QDate date; s = e.attribute("date"); if (!s.isEmpty()) { date = QDate::fromString(s, Qt::ISODate); } if (!date.isValid()) { warnPlanXml<<"Invalid date: "<setEffort(date, a); } } } return true; } bool KPlatoXmlLoaderBase::load(Appointment *appointment, const KoXmlElement& element, XMLLoaderObject& status, Schedule &sch) { debugPlanXml<<"appointment"; Node *node = status.project().findNode(element.attribute("task-id")); if (node == 0) { errorPlanXml<<"The referenced task does not exists: "<addAppointment(appointment, sch)) { errorPlanXml<<"Failed to add appointment to resource: "<name(); return false; } if (! node->addAppointment(appointment, sch)) { errorPlanXml<<"Failed to add appointment to node: "<name(); appointment->resource()->takeAppointment(appointment); return false; } //debugPlanXml<<"res="<name()<name(); return false; } appointment->setIntervals(lst); return true; } bool KPlatoXmlLoaderBase::load(AppointmentIntervalList& lst, const KoXmlElement& element, XMLLoaderObject& status) { debugPlanXml<<"appointment-interval-list"; KoXmlElement e; forEachElement(e, element) { if (e.tagName() == "interval") { AppointmentInterval a; if (load(a, e, status)) { lst.add(a); } else { errorPlanXml<<"Could not load interval"; } } } return true; } bool KPlatoXmlLoaderBase::load(AppointmentInterval& interval, const KoXmlElement& element, XMLLoaderObject& status) { bool ok; QString s = element.attribute("start"); if (!s.isEmpty()) { interval.setStartTime(DateTime::fromString(s, status.projectTimeZone())); } s = element.attribute("end"); if (!s.isEmpty()) { interval.setEndTime(DateTime::fromString(s, status.projectTimeZone())); } double l = element.attribute("load", "100").toDouble(&ok); if (ok) { interval.setLoad(l); } debugPlanXml<<"interval:"< * Copyright (C) 2004-2007, 2012 Dag Andersen * Copyright (C) 2016 Dag Andersen * Copyright (C) 2019 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 "Resource.h" #include "ResourceGroup.h" #include "kptresourcerequest.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 using namespace KPlato; Resource::Resource() : QObject(0), // atm QObject is only for casting m_project(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_currentSchedule(0), m_shared(false) { //debugPlan<<"("<removeRunning(*this); } for (ResourceGroup *g : m_parents) { g->takeResource(this); } } void Resource::removeRequests() { foreach (ResourceRequest *r, m_requests) { r->setResource(0); // avoid the request to mess with my list - r->parent()->deleteResourceRequest(r); + delete r; } m_requests.clear(); } +void Resource::registerRequest(ResourceRequest *request) +{ + Q_ASSERT(!m_requests.contains(request)); + m_requests.append(request); +} + +void Resource::unregisterRequest(ResourceRequest *request) +{ + m_requests.removeOne(request); + Q_ASSERT(!m_requests.contains(request)); +} + +const QList &Resource::requests() const +{ + return m_requests; +} + 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; // No: m_parents = resource->m_parents; // 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(); } void Resource::addParentGroup(ResourceGroup *parent) { if (!parent) { return; } if (!m_parents.contains(parent)) { m_parents.append(parent); parent->addResource(this); } } bool Resource::removeParentGroup(ResourceGroup *parent) { if (parent) { parent->takeResource(this); } return m_parents.removeOne(parent); } void Resource::setParentGroups(QList &parents) { for (ResourceGroup *g : m_parents) { removeParentGroup(g); } m_parents = parents; } QList Resource::parentGroups() const { return m_parents; } 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()); } 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; if (time == end) { #ifndef PLAN_NLOGDEBUG m_currentSchedule->logDebug(QString("Task '%1' start time == end time: %2").arg(node->node()->name(), time.toString(Qt::ISODate))); #endif node->resourceNotAvailable = true; return; } 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.second > time && 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); //if (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="<type(); dbg << (r->name().isEmpty() ? r->id() : r->name()); dbg << ']'; return dbg; } diff --git a/src/libs/kernel/Resource.h b/src/libs/kernel/Resource.h index 3e547590..a20c7033 100644 --- a/src/libs/kernel/Resource.h +++ b/src/libs/kernel/Resource.h @@ -1,479 +1,472 @@ /* 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 * Copyright (C) 2019 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 ResourceGroup; class ResourceRequest; class ResourceGroupRequest; class ResourceRequestCollection; class Schedule; class ResourceSchedule; class Schedule; class XMLLoaderObject; /** * 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); ~Resource() override; 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 addParentGroup(ResourceGroup *parent); bool removeParentGroup(ResourceGroup *parent); void setParentGroups(QList &parents); QList parentGroups() const; /// 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; } + void registerRequest(ResourceRequest *request); + void unregisterRequest(ResourceRequest *request); + const QList &requests() const; /// 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; QList m_parents; 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; }; } // namespace KPlato PLANKERNEL_EXPORT QDebug operator<<(QDebug dbg, KPlato::Resource *r); #endif diff --git a/src/libs/kernel/XmlSaveContext.h b/src/libs/kernel/XmlSaveContext.h index e1f66252..8cc2672e 100644 --- a/src/libs/kernel/XmlSaveContext.h +++ b/src/libs/kernel/XmlSaveContext.h @@ -1,115 +1,261 @@ /* This file is part of the KDE project * Copyright (C) 2019 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; * 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. */ #ifndef XMLSAVECONTEXT_H #define XMLSAVECONTEXT_H #include "plankernel_export.h" #include "kptproject.h" #include "kptnode.h" #include "kpttask.h" #include #include #include namespace KPlato { class PLANKERNEL_EXPORT XmlSaveContext { public: XmlSaveContext(Project *project = 0) : options(SaveAll) , m_project(project) {} static QDomDocument createDocument() { QDomDocument document("plan"); document.appendChild(document.createProcessingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"") ); QDomElement doc = document.createElement("plan"); doc.setAttribute("editor", "Plan"); doc.setAttribute("mime", "application/x-vnd.kde.plan"); doc.setAttribute("version", PLAN_FILE_SYNTAX_VERSION); document.appendChild(doc); return document; } enum SaveOptions { SaveAll = 0x1, - SaveNodes = 0x2 + SaveSelectedNodes = 0x2, + SaveRelations = 0x4, + SaveResources = 0x8, + SaveRequests = 0x10, + SaveProject = 0x20 }; bool saveAll(const Project *project) const { Q_UNUSED(project); return options & SaveAll; } bool saveAll(const Task *task) const { Q_UNUSED(task); return options & SaveAll; } bool saveNode(const Node *node) const { if (options & SaveAll) { return true; } return nodes.contains(node); } bool saveChildren(const Node *node) const { Q_UNUSED(node); if (options & SaveAll) { return true; } return false; // TODO } bool saveRelation(const Relation *relation) const { if (options & SaveAll) { return true; } - return (options & SaveNodes) && nodes.contains(relation->parent()) && nodes.contains(relation->child()); + return (options & SaveSelectedNodes) && nodes.contains(relation->parent()) && nodes.contains(relation->child()); } void save() const { + Q_ASSERT(m_project); + if (!m_project) { + return; + } document = createDocument(); QDomElement doc = document.documentElement(); - if (m_project) { + + if (options & (SaveAll | SaveProject)) { + debugPlanXml<<"project"; m_project->save(doc, *this); + if (options & SaveAll) { + return; + } + } else { + doc.appendChild(doc.ownerDocument().createElement("project")); } - if (options & SaveNodes) { - QDomElement element = doc.elementsByTagName("project").item(0).toElement(); - Q_ASSERT(!element.isNull()); + QDomElement projectElement = doc.elementsByTagName("project").item(0).toElement(); + Q_ASSERT(!projectElement.isNull()); + if (options & SaveSelectedNodes) { + debugPlanXml<<"tasks:"< it(nodes); while (it.hasNext()) { - it.next()->save(element, *this); + it.next()->save(projectElement, *this); + } + } + if (options & SaveRelations) { + QListIterator it(nodes); + while (it.hasNext()) { + it.next()->saveRelations(projectElement, *this); + } + } + if (options & SaveResources) { + debugPlanXml<<"resource-groups:"<resourceGroups().count(); + if (!m_project->resourceGroups().isEmpty()) { + QDomElement ge = projectElement.ownerDocument().createElement("resource-groups"); + projectElement.appendChild(ge); + QListIterator git(m_project->resourceGroups()); + while (git.hasNext()) { + git.next()->save(ge); + } + } + debugPlanXml<<"resources:"<resourceList().count(); + if (!m_project->resourceList().isEmpty()) { + QDomElement re = projectElement.ownerDocument().createElement("resources"); + projectElement.appendChild(re); + QListIterator rit(m_project->resourceList()); + while (rit.hasNext()) { + rit.next()->save(re); + } + } + debugPlanXml<<"resource-group-relations"; + if (!m_project->resourceList().isEmpty() && !m_project->resourceGroups().isEmpty()) { + QDomElement e = projectElement.ownerDocument().createElement("resource-group-relations"); + projectElement.appendChild(e); + for (ResourceGroup *g : m_project->resourceGroups()) { + for (Resource *r : g->resources()) { + QDomElement re = e.ownerDocument().createElement("resource-group-relation"); + e.appendChild(re); + re.setAttribute("group-id", g->id()); + re.setAttribute("resource-id", r->id()); + } + } + } + debugPlanXml<<"required-resources"; + if (m_project->resourceList().count() > 1) { + QList > requiredList; + for (Resource *resource : m_project->resourceList()) { + for (const QString &required : resource->requiredIds()) { + requiredList << std::pair(resource->id(), required); + } + } + if (!requiredList.isEmpty()) { + QDomElement e = projectElement.ownerDocument().createElement("required-resources"); + projectElement.appendChild(e); + for (const std::pair pair : requiredList) { + QDomElement re = e.ownerDocument().createElement("required-resource"); + e.appendChild(re); + re.setAttribute("resource-id", pair.first); + re.setAttribute("required-id", pair.second); + } + } + } + // save resource teams + debugPlanXml<<"resource-teams"; + QDomElement el = projectElement.ownerDocument().createElement("resource-teams"); + projectElement.appendChild(el); + foreach (Resource *r, m_project->resourceList()) { + if (r->type() != Resource::Type_Team) { + continue; + } + foreach (const QString &id, r->teamMemberIds()) { + QDomElement e = el.ownerDocument().createElement("team"); + el.appendChild(e); + e.setAttribute("team-id", r->id()); + e.setAttribute("member-id", id); + } + } + } + if (options & SaveRequests) { + // save resource requests + QHash groups; + QHash resources; + for (Task *task : m_project->allTasks()) { + const ResourceRequestCollection &requests = task->requests(); + for (ResourceGroupRequest *gr : requests.requests()) { + groups.insert(task, gr); + } + for (ResourceRequest *rr : requests.resourceRequests(false)) { + resources.insert(task, rr); + } + } + debugPlanXml<<"resourcegroup-requests:"<::const_iterator it; + for (it = groups.constBegin(); it != groups.constEnd(); ++it) { + if (!it.value()->group()) { + warnPlanXml<<"resourcegroup-request with no group"; + continue; + } + QDomElement ge = el.ownerDocument().createElement("resourcegroup-request"); + el.appendChild(ge); + ge.setAttribute("request-id", it.value()->id()); + ge.setAttribute("task-id", it.key()->id()); + ge.setAttribute("group-id", it.value()->group()->id()); + ge.setAttribute("units", QString::number(it.value()->units())); + } + } + QHash > required; // QHash> + debugPlanXml<<"resource-requests:"<::const_iterator it; + for (it = resources.constBegin(); it != resources.constEnd(); ++it) { + if (!it.value()->resource()) { + continue; + } + QDomElement re = el.ownerDocument().createElement("resource-request"); + el.appendChild(re); + re.setAttribute("request-id", it.value()->id()); + re.setAttribute("task-id", it.key()->id()); + if (it.value()->parent()) { + re.setAttribute("group-id", it.value()->parent()->group()->id()); + } + re.setAttribute("resource-id", it.value()->resource()->id()); + re.setAttribute("units", QString::number(it.value()->units())); + // collect required resources + for (Resource *r : it.value()->requiredResources()) { + required.insert(it.key(), std::pair(it.value(), r)); + } + } } } } mutable QDomDocument document; - SaveOptions options; + int options; QList nodes; private: Project *m_project; }; } //namespace KPlato #endif diff --git a/src/libs/kernel/commands/RemoveResourceCmd.cpp b/src/libs/kernel/commands/RemoveResourceCmd.cpp index 09031b56..193b005c 100644 --- a/src/libs/kernel/commands/RemoveResourceCmd.cpp +++ b/src/libs/kernel/commands/RemoveResourceCmd.cpp @@ -1,81 +1,79 @@ /* This file is part of the KDE project * Copyright (C) 2019 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 "RemoveResourceCmd.h" #include "Resource.h" #include "kptproject.h" #include "kptaccount.h" #include "kptappointment.h" #include "kptresourcerequest.h" #include "kptcommand.h" #include "kptdebug.h" using namespace KPlato; RemoveResourceCmd::RemoveResourceCmd(Resource *resource, const KUndo2MagicString& name) : AddResourceCmd(resource->project(), resource, name) { //debugPlan<requests(); if (m_project) { foreach (Schedule * s, m_project->schedules()) { Schedule *rs = resource->findSchedule(s->id()); if (rs && ! rs->isDeleted()) { debugPlan<name(); addSchScheduled(s); } } } if (resource->account()) { - m_cmd.addCommand(new ResourceModifyAccountCmd(*resource, resource->account(), 0)); + m_postCmd.addCommand(new ResourceModifyAccountCmd(*resource, resource->account(), 0)); + } + foreach (ResourceRequest * r, m_requests) { + m_preCmd.addCommand(new RemoveResourceRequestCmd(r->parent(), r)); + //debugPlan<<"Remove request for"<resource()->name(); } } RemoveResourceCmd::~RemoveResourceCmd() { while (!m_appointments.isEmpty()) delete m_appointments.takeFirst(); } void RemoveResourceCmd::execute() { - foreach (ResourceRequest * r, m_requests) { - r->parent()->takeResourceRequest(r); - //debugPlan<<"Remove request for"<resource()->name(); - } + m_preCmd.redo(); AddResourceCmd::unexecute(); - m_cmd.execute(); + m_postCmd.redo(); setSchScheduled(false); } void RemoveResourceCmd::unexecute() { - foreach (ResourceRequest * r, m_requests) { - r->parent()->addResourceRequest(r); - //debugPlan<<"Add request for"<resource()->name(); - } - m_cmd.unexecute(); + m_postCmd.undo(); AddResourceCmd::execute(); + m_preCmd.undo(); setSchScheduled(); } diff --git a/src/libs/kernel/commands/RemoveResourceCmd.h b/src/libs/kernel/commands/RemoveResourceCmd.h index 1454bf61..8ef8370c 100644 --- a/src/libs/kernel/commands/RemoveResourceCmd.h +++ b/src/libs/kernel/commands/RemoveResourceCmd.h @@ -1,56 +1,57 @@ /* This file is part of the KDE project * Copyright (C) 2004-2007 Dag Andersen * Copyright (C) 2011 Dag Andersen * Copyright (C) 2016 Dag Andersen * Copyright (C) 2019 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 KPTREMOVERESOURCECMD_H #define KPTREMOVERESOURCECMD_H #include "plankernel_export.h" #include "AddResourceCmd.h" #include "MacroCommand.h" /// The main namespace namespace KPlato { class Resource; class ResourceRequest; class Appointment; class PLANKERNEL_EXPORT RemoveResourceCmd : public AddResourceCmd { public: RemoveResourceCmd(Resource *resource, const KUndo2MagicString& name = KUndo2MagicString()); ~RemoveResourceCmd() override; void execute() override; void unexecute() override; private: QList m_requests; QList m_appointments; - MacroCommand m_cmd; + MacroCommand m_preCmd; + MacroCommand m_postCmd; }; } #endif diff --git a/src/libs/kernel/kptappointment.cpp b/src/libs/kernel/kptappointment.cpp index 34c4f6a1..935cf16d 100644 --- a/src/libs/kernel/kptappointment.cpp +++ b/src/libs/kernel/kptappointment.cpp @@ -1,1064 +1,1064 @@ /* This file is part of the KDE project Copyright (C) 2005 - 2011 Dag Andersen Copyright (C) 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 "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 namespace KPlato { class Resource; AppointmentInterval::AppointmentInterval() : d(new AppointmentIntervalData()) { //debugPlan<start; } void AppointmentInterval::setStartTime(const DateTime &time) { if (d->start != time) { d->start = time; } } const DateTime &AppointmentInterval::endTime() const { return d->end; } void AppointmentInterval::setEndTime(const DateTime &time) { if (d->end != time) { d->end = time; } } double AppointmentInterval::load() const { return d->load; } void AppointmentInterval::setLoad(double load) { if (d->load != load) { d->load = load; } } Duration AppointmentInterval::effort() const { return (d->end - d->start) * d->load / 100; } Duration AppointmentInterval::effort(const DateTime &start, const DateTime &end) const { if (start >= d->end || end <= d->start) { return Duration::zeroDuration; } DateTime s = (start > d->start ? start : d->start); DateTime e = (end < d->end ? end : d->end); return (e - s) * d->load / 100; } Duration AppointmentInterval::effort(QDate time, bool upto) const { DateTime t(time); //debugPlan<start<end; if (upto) { if (t <= d->start) { return Duration::zeroDuration; } DateTime e = (t < d->end ? t : d->end); Duration eff = (e - d->start) * d->load / 100; //debugPlan<toString(); return eff; } // from time till end if (t >= d->end) { return Duration::zeroDuration; } DateTime s = (t > d->start ? t : d->start); return (d->end - s) * d->load / 100; } bool AppointmentInterval::loadXML(KoXmlElement &element, XMLLoaderObject &status) { //debugPlan; bool ok; QString s = element.attribute(QStringLiteral("start")); if (!s.isEmpty()) d->start = DateTime::fromString(s, status.projectTimeZone()); s = element.attribute(QStringLiteral("end")); if (!s.isEmpty()) d->end = DateTime::fromString(s, status.projectTimeZone()); d->load = element.attribute(QStringLiteral("load"), QStringLiteral("100")).toDouble(&ok); if (!ok) d->load = 100; if (! isValid()) { errorPlan<<"AppointmentInterval::loadXML: Invalid interval:"<<*this<start.timeZone() == d->end.timeZone()); } return isValid(); } void AppointmentInterval::saveXML(QDomElement &element) const { Q_ASSERT(isValid()); - QDomElement me = element.ownerDocument().createElement(QStringLiteral("interval")); + QDomElement me = element.ownerDocument().createElement(QStringLiteral("appointment-interval")); element.appendChild(me); me.setAttribute(QStringLiteral("start"), d->start.toString(Qt::ISODate)); me.setAttribute(QStringLiteral("end"), d->end.toString(Qt::ISODate)); me.setAttribute(QStringLiteral("load"), QString::number(d->load)); } bool AppointmentInterval::isValid() const { return d->start.isValid() && d->end.isValid() && d->start < d->end && d->load >= 0.0; } AppointmentInterval AppointmentInterval::firstInterval(const AppointmentInterval &interval, const DateTime &from) const { //debugPlan<start<<" < "<start; return true; } else if (other.d->start < d->start) { //debugPlan<<"other start"<start<<" < "<start; return false; } // Start is assumed equal //debugPlan<<"this end"<end<<" < "<end; return d->end < other.d->end; } bool AppointmentInterval::intersects(const AppointmentInterval &other) const { return (d->start < other.d->end && d->end > other.d->start); } AppointmentInterval AppointmentInterval::interval(const DateTime &start, const DateTime &end) const { // TODO: Find and fix those that call with "wrong" timezone (should be local zone atm) const DateTime s = start.toTimeZone(d->start.timeZone()); const DateTime e = end.toTimeZone(d->end.timeZone()); if (s <= d->start && e >= d->end) { return *this; } return AppointmentInterval(qMax(s, d->start), qMin(e, d->end), d->load); } QString AppointmentInterval::toString() const { return QStringLiteral("%1 - %2, %3%").arg(d->start.toString(Qt::ISODate)).arg(d->end.toString(Qt::ISODate)).arg(d->load); } QDebug operator<<(QDebug dbg, const KPlato::AppointmentInterval &i) { dbg<<"AppointmentInterval["< &other) : m_map(other) { } QMultiMap< QDate, AppointmentInterval > AppointmentIntervalList::map() { return m_map; } const QMultiMap< QDate, AppointmentInterval >& AppointmentIntervalList::map() const { return m_map; } AppointmentIntervalList &AppointmentIntervalList::operator=(const AppointmentIntervalList &lst) { m_map = lst.m_map; return *this; } AppointmentIntervalList &AppointmentIntervalList::operator-=(const AppointmentIntervalList &lst) { if (lst.m_map.isEmpty()) { return *this; } foreach (const AppointmentInterval &ai, lst.map()) { subtract(ai); } return *this; } void AppointmentIntervalList::subtract(const DateTime &st, const DateTime &et, double load) { subtract(AppointmentInterval(st, et, load)); } void AppointmentIntervalList::subtract(const AppointmentInterval &interval) { //debugPlan< l; QList v = m_map.values(date); m_map.remove(date); foreach (const AppointmentInterval &vi, v) { if (! vi.intersects(interval)) { //debugPlan<<"subtract: not intersect:"< load) { l.insert(0, AppointmentInterval(st, qMin(vi.endTime(), et), vi.load() - load)); //if (! l.at(0).isValid()) { debugPlan< load) { //debugPlan<<"subtract: interval load"< load) { //debugPlan<<"subtract: vi==interval"< v = m_map.values(date); m_map.remove(date); QList l; foreach (const AppointmentInterval &vi, v) { if (! li.isValid()) { l.insert(0, vi); Q_ASSERT_X(l.at(0).isValid(), "Original", "Invalid interval"); continue; } if (! li.intersects(vi)) { //debugPlan<<"not intersects:"<::const_iterator it = m_map.lowerBound(start.date()); for (; it != m_map.constEnd() && it.key() <= end.date(); ++it) { d += it.value().effort(start, end); } return d; } void AppointmentIntervalList::saveXML(QDomElement &element) const { foreach (const AppointmentInterval &i, m_map) { i.saveXML(element); #ifndef NDEBUG if (!i.isValid()) { // NOTE: This should not happen, so hunt down cause if it does warnPlan<<"Invalid interval:"<::const_iterator it = i.map().constBegin(); for (; it != i.map().constEnd(); ++it) { dbg<::const_iterator it = m_intervals.map().lowerBound(start.date()); for (; it != m_intervals.map().constEnd() && it.key() <= end.date(); ++it) { AppointmentInterval ai = it.value().interval(start, end); if (ai.isValid()) { lst.add(ai); //debugPlan<resource() && m_node && m_node->node()) debugPlan<<"Mode="<resource()->name()<<" to"<node()->name()<<""<addAppointment(this, sch)) { errorPlan<<"Failed to add appointment to resource: "<name(); return false; } if (!node->addAppointment(this, sch)) { errorPlan<<"Failed to add appointment to node: "<name(); m_resource->takeAppointment(this); return false; } //debugPlan<<"res="<resource()->name()<<" node="<node()->name(); m_intervals.loadXML(element, status); if (isEmpty()) { errorPlan<<"Appointment is empty (added anyway): "<name()<name(); return false; } return true; } void Appointment::saveXML(QDomElement &element) const { if (isEmpty()) { errorPlan<<"Incomplete appointment data: No intervals"; } if (m_resource == 0 || m_resource->resource() == 0) { errorPlan<<"Incomplete appointment data: No resource"; return; } if (m_node == 0 || m_node->node() == 0) { errorPlan<<"Incomplete appointment data: No node"; return; // shouldn't happen } //debugPlan; QDomElement me = element.ownerDocument().createElement(QStringLiteral("appointment")); element.appendChild(me); me.setAttribute(QStringLiteral("resource-id"), m_resource->resource()->id()); me.setAttribute(QStringLiteral("task-id"), m_node->node()->id()); //debugPlan<resource()->name()<node()->name(); m_intervals.saveXML(me); } // Returns the total planned effort for this appointment Duration Appointment::plannedEffort(const Resource *resource, EffortCostCalculationType type) const { if (m_resource->resource() != resource) { return Duration::zeroDuration; } return plannedEffort(type); } // Returns the total planned effort for this appointment Duration Appointment::plannedEffort(EffortCostCalculationType type) const { Duration d; if (type == ECCT_All || m_resource == 0 || m_resource->resource()->type() == Resource::Type_Work) { foreach (const AppointmentInterval &i, m_intervals.map()) { d += i.effort(); } } return d; } // Returns the planned effort on the date Duration Appointment::plannedEffort(QDate date, EffortCostCalculationType type) const { Duration d; if (type == ECCT_All || m_resource == 0 || m_resource->resource()->type() == Resource::Type_Work) { QMultiMap::const_iterator it = m_intervals.map().constFind(date); for (; it != m_intervals.map().constEnd() && it.key() == date; ++it) { d += it.value().effort(); } } return d; } // Returns the planned effort on the date Duration Appointment::plannedEffort(const Resource *resource, QDate date, EffortCostCalculationType type) const { if (resource != m_resource->resource()) { return Duration::zeroDuration; } return plannedEffort(date, type); } // Returns the planned effort upto and including the date Duration Appointment::plannedEffortTo(QDate date, EffortCostCalculationType type) const { Duration d; QDate e(date.addDays(1)); if (type == ECCT_All || m_resource == 0 || m_resource->resource()->type() == Resource::Type_Work) { foreach (const AppointmentInterval &i, m_intervals.map()) { d += i.effort(e, true); // upto e, not including } } //debugPlan<resource()) { return Duration::zeroDuration; } return plannedEffortTo(date, type); } EffortCostMap Appointment::plannedPrDay(QDate pstart, QDate pend, EffortCostCalculationType type) const { //debugPlan<id()<<","<id(); EffortCostMap ec; QDate start = pstart.isValid() ? pstart : startTime().date(); QDate end = pend.isValid() ? pend : endTime().date(); double rate = m_resource && m_resource->resource() ? m_resource->normalRatePrHour() : 0.0; Resource::Type rt = m_resource && m_resource->resource() ? m_resource->resource()->type() : Resource::Type_Work; Duration zero; //debugPlan<::const_iterator it = m_intervals.map().lowerBound(start); for (; it != m_intervals.map().constEnd() && it.key() <= end; ++it) { //debugPlan<resource()) { switch (type) { case ECCT_Work: if (m_resource->resource()->type() != Resource::Type_Work) { break; } // fall through default: ec.setCost(ec.hours() * m_resource->resource()->normalRate()); //FIXME overtime break; } } return ec; } //Calculates the planned cost on date double Appointment::plannedCost(QDate date, EffortCostCalculationType type) { if (m_resource && m_resource->resource()) { switch (type) { case ECCT_Work: if (m_resource->resource()->type() != Resource::Type_Work) { break; } // fall through default: 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(QDate date, EffortCostCalculationType type) { if (m_resource && m_resource->resource()) { switch (type) { case ECCT_Work: if (m_resource->resource()->type() != Resource::Type_Work) { break; } // fall through default: return plannedEffortTo(date).toDouble(Duration::Unit_h) * m_resource->resource()->normalRate(); //FIXME overtime } } return 0.0; } bool Appointment::attach() { //debugPlan<<"("<attach(this); m_node->attach(this); return true; } warnPlan<<"Failed: "<<(m_resource ? "" : "resource=0 ") <<(m_node ? "" : "node=0"); return false; } void Appointment::detach() { //debugPlan<<"("<takeAppointment(this, m_calculationMode); // takes from node also } if (m_node) { m_node->takeAppointment(this, m_calculationMode); // to make it robust } } // Returns the effort from start to end Duration Appointment::effort(const DateTime &start, const DateTime &end, EffortCostCalculationType type) const { Duration e; if (type == ECCT_All || m_resource == 0 || m_resource->resource()->type() == Resource::Type_Work) { e = m_intervals.effort(start, end); } return e; } // Returns the effort from start for the duration Duration Appointment::effort(const DateTime &start, KPlato::Duration duration, EffortCostCalculationType type) const { Duration d; if (type == ECCT_All || m_resource == 0 || m_resource->resource()->type() == Resource::Type_Work) { foreach (const AppointmentInterval &i, m_intervals.map()) { d += i.effort(start, start+duration); } } return d; } Appointment &Appointment::operator=(const Appointment &app) { copy(app); return *this; } Appointment &Appointment::operator+=(const Appointment &app) { merge(app); return *this; } Appointment Appointment::operator+(const Appointment &app) { Appointment a(*this); a.merge(app); return a; } Appointment &Appointment::operator-=(const Appointment &app) { m_intervals -= app.m_intervals; return *this; } void Appointment::copy(const Appointment &app) { m_resource = 0; //app.resource(); // NOTE: Don't copy, the new appointment m_node = 0; //app.node(); // NOTE: doesn't belong to anyone yet. //TODO: incomplete but this is all we use atm m_calculationMode = app.calculationMode(); //m_repeatInterval = app.repeatInterval(); //m_repeatCount = app.repeatCount(); m_intervals.clear(); foreach (const AppointmentInterval &i, app.intervals().map()) { addInterval(i); } } void Appointment::merge(const Appointment &app) { //debugPlan<node()->name() : "no node")<<(app.node() ? app.node()->node()->name() : "no node"); if (app.isEmpty()) { return; } if (isEmpty()) { setIntervals(app.intervals()); return; } QList result; QList lst1 = m_intervals.map().values(); AppointmentInterval i1; QList lst2 = app.intervals().map().values(); //debugPlan<<"add"<= lst1.size()) { i2 = lst2[index2]; if (!from.isValid() || from < i2.startTime()) from = i2.startTime(); result.append(AppointmentInterval(from, i2.endTime(), i2.load())); //debugPlan<<"Interval+ (i2):"<= lst2.size()) { i1 = lst1[index1]; if (!from.isValid() || from < i1.startTime()) from = i1.startTime(); result.append(AppointmentInterval(from, i1.endTime(), i1.load())); //debugPlan<<"Interval+ (i1):"< 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 { if (!date.isValid()) { return CalendarDay::Undefined; } 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 = 0; 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/kptcommand.cpp b/src/libs/kernel/kptcommand.cpp index 0a5de2f0..9466de98 100644 --- a/src/libs/kernel/kptcommand.cpp +++ b/src/libs/kernel/kptcommand.cpp @@ -1,3769 +1,3785 @@ /* This file is part of the KDE project * Copyright (C) 2004 - 2007 Dag Andersen * Copyright (C) 2011 Dag Andersen * Copyright (C) 2016 Dag Andersen * Copyright (C) 2019 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 "kptcommand.h" #include "AddResourceCmd.h" #include "AddParentGroupCmd.h" #include "RemoveResourceCmd.h" #include "kptaccount.h" #include "kptappointment.h" #include "kptproject.h" #include "kpttask.h" #include "kptcalendar.h" #include "kptrelation.h" #include "kptresource.h" #include "kptdocuments.h" #include "kptlocale.h" #include "kptdebug.h" #include const QLoggingCategory &PLANCMDINSPROJECT_LOG() { static const QLoggingCategory category("calligra.plan.command.insertProject"); return category; } #define debugPlanInsertProject qCDebug(PLANCMDINSPROJECT_LOG) #define warnPlanInsertProject qCWarning(PLANCMDINSPROJECT_LOG) #define errorPlanInsertProject qCCritical(PLANCMDINSPROJECT_LOG) namespace KPlato { CalendarAddCmd::CalendarAddCmd(Project *project, Calendar *cal, int pos, Calendar *parent, const KUndo2MagicString& name) : NamedCommand(name), m_project(project), m_cal(cal), m_pos(pos), m_parent(parent), m_mine(true) { //debugPlan<name(); Q_ASSERT(project != 0); } CalendarAddCmd::~CalendarAddCmd() { if (m_mine) delete m_cal; } void CalendarAddCmd::execute() { if (m_project) { m_project->addCalendar(m_cal, m_parent, m_pos); m_mine = false; } //debugPlan<name()<<" added to:"<name(); } void CalendarAddCmd::unexecute() { if (m_project) { m_project->takeCalendar(m_cal); m_mine = true; } //debugPlan<name(); } CalendarRemoveCmd::CalendarRemoveCmd(Project *project, Calendar *cal, const KUndo2MagicString& name) : NamedCommand(name), m_project(project), m_parent(cal->parentCal()), m_cal(cal), m_index(-1), m_mine(false), m_cmd(new MacroCommand(KUndo2MagicString())) { Q_ASSERT(project != 0); m_index = m_parent ? m_parent->indexOf(cal) : project->indexOf(cal); foreach (Resource *r, project->resourceList()) { if (r->calendar(true) == cal) { m_cmd->addCommand(new ModifyResourceCalendarCmd(r, 0)); } } if (project->defaultCalendar() == cal) { m_cmd->addCommand(new ProjectModifyDefaultCalendarCmd(project, 0)); } foreach (Calendar *c, cal->calendars()) { m_cmd->addCommand(new CalendarRemoveCmd(project, c)); } } CalendarRemoveCmd::~CalendarRemoveCmd() { delete m_cmd; if (m_mine) delete m_cal; } void CalendarRemoveCmd::execute() { m_cmd->execute(); m_project->takeCalendar(m_cal); m_mine = true; } void CalendarRemoveCmd::unexecute() { m_project->addCalendar(m_cal, m_parent, m_index); m_cmd->unexecute(); m_mine = false; } CalendarMoveCmd::CalendarMoveCmd(Project *project, Calendar *cal, int position, Calendar *parent, const KUndo2MagicString& name) : NamedCommand(name), m_project(project), m_cal(cal), m_newpos(position), m_newparent(parent), m_oldparent(cal->parentCal()) { //debugPlan<name(); Q_ASSERT(project != 0); m_oldpos = m_oldparent ? m_oldparent->indexOf(cal) : project->indexOf(cal); } void CalendarMoveCmd::execute() { m_project->takeCalendar(m_cal); m_project->addCalendar(m_cal, m_newparent, m_newpos); } void CalendarMoveCmd::unexecute() { m_project->takeCalendar(m_cal); m_project->addCalendar(m_cal, m_oldparent, m_oldpos); } CalendarModifyNameCmd::CalendarModifyNameCmd(Calendar *cal, const QString& newvalue, const KUndo2MagicString& name) : NamedCommand(name), m_cal(cal) { m_oldvalue = cal->name(); m_newvalue = newvalue; //debugPlan<name(); } void CalendarModifyNameCmd::execute() { m_cal->setName(m_newvalue); //debugPlan<name(); } void CalendarModifyNameCmd::unexecute() { m_cal->setName(m_oldvalue); //debugPlan<name(); } CalendarModifyParentCmd::CalendarModifyParentCmd(Project *project, Calendar *cal, Calendar *newvalue, const KUndo2MagicString& name) : NamedCommand(name), m_project(project), m_cal(cal), m_cmd(new MacroCommand(KUndo2MagicString())), m_oldindex(-1), m_newindex(-1) { m_oldvalue = cal->parentCal(); m_newvalue = newvalue; m_oldindex = m_oldvalue ? m_oldvalue->indexOf(cal) : m_project->indexOf(cal); if (newvalue) { m_cmd->addCommand(new CalendarModifyTimeZoneCmd(cal, newvalue->timeZone())); } //debugPlan<name(); } CalendarModifyParentCmd::~CalendarModifyParentCmd() { delete m_cmd; } void CalendarModifyParentCmd::execute() { m_project->takeCalendar(m_cal); m_project->addCalendar(m_cal, m_newvalue, m_newindex); m_cmd->execute(); } void CalendarModifyParentCmd::unexecute() { m_cmd->unexecute(); m_project->takeCalendar(m_cal); m_project->addCalendar(m_cal, m_oldvalue, m_oldindex); } CalendarModifyTimeZoneCmd::CalendarModifyTimeZoneCmd(Calendar *cal, const QTimeZone &value, const KUndo2MagicString& name) : NamedCommand(name), m_cal(cal), m_newvalue(value), m_cmd(new MacroCommand(KUndo2MagicString())) { m_oldvalue = cal->timeZone(); foreach (Calendar *c, cal->calendars()) { m_cmd->addCommand(new CalendarModifyTimeZoneCmd(c, value)); } //debugPlan<name(); } CalendarModifyTimeZoneCmd::~CalendarModifyTimeZoneCmd() { delete m_cmd; } void CalendarModifyTimeZoneCmd::execute() { m_cmd->execute(); m_cal->setTimeZone(m_newvalue); } void CalendarModifyTimeZoneCmd::unexecute() { m_cal->setTimeZone(m_oldvalue); m_cmd->unexecute(); } #ifdef HAVE_KHOLIDAYS CalendarModifyHolidayRegionCmd::CalendarModifyHolidayRegionCmd(Calendar *cal, const QString &value, const KUndo2MagicString& name) : NamedCommand(name), m_cal(cal), m_newvalue(value) { m_oldvalue = cal->holidayRegionCode(); } CalendarModifyHolidayRegionCmd::~CalendarModifyHolidayRegionCmd() { } void CalendarModifyHolidayRegionCmd::execute() { m_cal->setHolidayRegion(m_newvalue); } void CalendarModifyHolidayRegionCmd::unexecute() { m_cal->setHolidayRegion(m_oldvalue); } #endif CalendarAddDayCmd::CalendarAddDayCmd(Calendar *cal, CalendarDay *newvalue, const KUndo2MagicString& name) : NamedCommand(name), m_cal(cal), m_mine(true) { m_newvalue = newvalue; //debugPlan<name(); } CalendarAddDayCmd::~CalendarAddDayCmd() { //debugPlan; if (m_mine) delete m_newvalue; } void CalendarAddDayCmd::execute() { //debugPlan<name(); m_cal->addDay(m_newvalue); m_mine = false; } void CalendarAddDayCmd::unexecute() { //debugPlan<name(); m_cal->takeDay(m_newvalue); m_mine = true; } CalendarRemoveDayCmd::CalendarRemoveDayCmd(Calendar *cal,CalendarDay *day, const KUndo2MagicString& name) : NamedCommand(name), m_cal(cal), m_value(day), m_mine(false) { //debugPlan<name(); // TODO check if any resources uses this calendar init(); } CalendarRemoveDayCmd::CalendarRemoveDayCmd(Calendar *cal, const QDate &day, const KUndo2MagicString& name) : NamedCommand(name), m_cal(cal), m_mine(false) { m_value = cal->findDay(day); //debugPlan<name(); // TODO check if any resources uses this calendar init(); } void CalendarRemoveDayCmd::init() { } void CalendarRemoveDayCmd::execute() { //debugPlan<name(); m_cal->takeDay(m_value); m_mine = true; } void CalendarRemoveDayCmd::unexecute() { //debugPlan<name(); m_cal->addDay(m_value); m_mine = false; } CalendarModifyDayCmd::CalendarModifyDayCmd(Calendar *cal, CalendarDay *value, const KUndo2MagicString& name) : NamedCommand(name), m_cal(cal), m_mine(true) { m_newvalue = value; m_oldvalue = cal->findDay(value->date()); //debugPlan<name()<<" old:("<takeDay(m_oldvalue); } m_cal->addDay(m_newvalue); m_mine = false; } void CalendarModifyDayCmd::unexecute() { //debugPlan; m_cal->takeDay(m_newvalue); if (m_oldvalue) { m_cal->addDay(m_oldvalue); } m_mine = true; } CalendarModifyStateCmd::CalendarModifyStateCmd(Calendar *calendar, CalendarDay *day, CalendarDay::State value, const KUndo2MagicString& name) : NamedCommand(name), m_calendar(calendar), m_day(day), m_cmd(new MacroCommand(KUndo2MagicString())) { m_newvalue = value; m_oldvalue = (CalendarDay::State)day->state(); if (value != CalendarDay::Working) { foreach (TimeInterval *ti, day->timeIntervals()) { m_cmd->addCommand(new CalendarRemoveTimeIntervalCmd(calendar, day, ti)); } } } CalendarModifyStateCmd::~CalendarModifyStateCmd() { delete m_cmd; } void CalendarModifyStateCmd::execute() { //debugPlan; m_cmd->execute(); m_calendar->setState(m_day, m_newvalue); } void CalendarModifyStateCmd::unexecute() { //debugPlan; m_calendar->setState(m_day, m_oldvalue); m_cmd->unexecute(); } CalendarModifyTimeIntervalCmd::CalendarModifyTimeIntervalCmd(Calendar *calendar, TimeInterval &newvalue, TimeInterval *value, const KUndo2MagicString& name) : NamedCommand(name), m_calendar(calendar) { m_value = value; // keep pointer m_oldvalue = *value; // save value m_newvalue = newvalue; } void CalendarModifyTimeIntervalCmd::execute() { //debugPlan; m_calendar->setWorkInterval(m_value, m_newvalue); } void CalendarModifyTimeIntervalCmd::unexecute() { //debugPlan; m_calendar->setWorkInterval(m_value, m_oldvalue); } CalendarAddTimeIntervalCmd::CalendarAddTimeIntervalCmd(Calendar *calendar, CalendarDay *day, TimeInterval *value, const KUndo2MagicString& name) : NamedCommand(name), m_calendar(calendar), m_day(day), m_value(value), m_mine(true) { } CalendarAddTimeIntervalCmd::~CalendarAddTimeIntervalCmd() { if (m_mine) delete m_value; } void CalendarAddTimeIntervalCmd::execute() { //debugPlan; m_calendar->addWorkInterval(m_day, m_value); m_mine = false; } void CalendarAddTimeIntervalCmd::unexecute() { //debugPlan; m_calendar->takeWorkInterval(m_day, m_value); m_mine = true; } CalendarRemoveTimeIntervalCmd::CalendarRemoveTimeIntervalCmd(Calendar *calendar, CalendarDay *day, TimeInterval *value, const KUndo2MagicString& name) : CalendarAddTimeIntervalCmd(calendar, day, value, name) { m_mine = false ; } void CalendarRemoveTimeIntervalCmd::execute() { CalendarAddTimeIntervalCmd::unexecute(); } void CalendarRemoveTimeIntervalCmd::unexecute() { CalendarAddTimeIntervalCmd::execute(); } CalendarModifyWeekdayCmd::CalendarModifyWeekdayCmd(Calendar *cal, int weekday, CalendarDay *value, const KUndo2MagicString& name) : NamedCommand(name), m_weekday(weekday), m_cal(cal), m_value(value), m_orig(*(cal->weekday(weekday))) { //debugPlan << cal->name() <<" (" << value <<")"; } CalendarModifyWeekdayCmd::~CalendarModifyWeekdayCmd() { //debugPlan << m_weekday <<":" << m_value; delete m_value; } void CalendarModifyWeekdayCmd::execute() { m_cal->setWeekday(m_weekday, *m_value); } void CalendarModifyWeekdayCmd::unexecute() { m_cal->setWeekday(m_weekday, m_orig); } CalendarModifyDateCmd::CalendarModifyDateCmd(Calendar *cal, CalendarDay *day, const QDate &value, const KUndo2MagicString& name) : NamedCommand(name), m_cal(cal), m_day(day), m_newvalue(value), m_oldvalue(day->date()) { //debugPlan << cal->name() <<" (" << value <<")"; } void CalendarModifyDateCmd::execute() { m_cal->setDate(m_day, m_newvalue); } void CalendarModifyDateCmd::unexecute() { m_cal->setDate(m_day, m_oldvalue); } ProjectModifyDefaultCalendarCmd::ProjectModifyDefaultCalendarCmd(Project *project, Calendar *cal, const KUndo2MagicString& name) : NamedCommand(name), m_project(project), m_newvalue(cal), m_oldvalue(project->defaultCalendar()) { //debugPlan << cal->name() <<" (" << value <<")"; } void ProjectModifyDefaultCalendarCmd::execute() { m_project->setDefaultCalendar(m_newvalue); } void ProjectModifyDefaultCalendarCmd::unexecute() { m_project->setDefaultCalendar(m_oldvalue); } NodeDeleteCmd::NodeDeleteCmd(Node *node, const KUndo2MagicString& name) : NamedCommand(name), m_node(node), m_index(-1), m_relCmd(0) { m_parent = node->parentNode(); m_mine = false; m_project = static_cast(node->projectNode()); if (m_project) { foreach (Schedule * s, m_project->schedules()) { if (s && s->isScheduled()) { // Only invalidate schedules this node is part of Schedule *ns = node->findSchedule(s->id()); if (ns && ! ns->isDeleted()) { addSchScheduled(s); } } } } m_cmd = new MacroCommand(KUndo2MagicString()); QList lst = node->childNodeIterator(); for (int i = lst.count(); i > 0; --i) { m_cmd->addCommand(new NodeDeleteCmd(lst[ i - 1 ])); } if (node->runningAccount()) { m_cmd->addCommand(new NodeModifyRunningAccountCmd(*node, node->runningAccount(), 0)); } if (node->startupAccount()) { m_cmd->addCommand(new NodeModifyRunningAccountCmd(*node, node->startupAccount(), 0)); } if (node->shutdownAccount()) { m_cmd->addCommand(new NodeModifyRunningAccountCmd(*node, node->shutdownAccount(), 0)); } } NodeDeleteCmd::~NodeDeleteCmd() { delete m_relCmd; // before node if (m_mine) { delete m_node; } delete m_cmd; while (!m_appointments.isEmpty()) delete m_appointments.takeFirst(); } void NodeDeleteCmd::execute() { if (m_parent && m_project) { m_index = m_parent->findChildNode(m_node); //debugPlan<name()<<""<dependChildNodes()) { m_relCmd->addCommand(new DeleteRelationCmd(*m_project, r)); } foreach (Relation * r, m_node->dependParentNodes()) { m_relCmd->addCommand(new DeleteRelationCmd(*m_project, r)); } } m_relCmd->execute(); if (m_cmd) { m_cmd->execute(); } m_project->takeTask(m_node); m_mine = true; setSchScheduled(false); } } void NodeDeleteCmd::unexecute() { if (m_parent && m_project) { //debugPlan<name()<<""<addSubTask(m_node, m_index, m_parent); if (m_cmd) { m_cmd->unexecute(); } m_relCmd->unexecute(); m_mine = false; setSchScheduled(); } } TaskAddCmd::TaskAddCmd(Project *project, Node *node, Node *after, const KUndo2MagicString& name) : NamedCommand(name), m_project(project), m_node(node), m_after(after), m_added(false) { // set some reasonable defaults for normally calculated values if (after && after->parentNode() && after->parentNode() != project) { node->setStartTime(after->parentNode() ->startTime()); node->setEndTime(node->startTime() + node->duration()); } else { if (project->constraint() == Node::MustFinishOn) { node->setEndTime(project->endTime()); node->setStartTime(node->endTime() - node->duration()); } else { node->setStartTime(project->startTime()); node->setEndTime(node->startTime() + node->duration()); } } node->setEarlyStart(node->startTime()); node->setLateFinish(node->endTime()); node->setWorkStartTime(node->startTime()); node->setWorkEndTime(node->endTime()); } TaskAddCmd::~TaskAddCmd() { if (!m_added) delete m_node; } void TaskAddCmd::execute() { //debugPlan<name(); m_project->addTask(m_node, m_after); m_added = true; } void TaskAddCmd::unexecute() { m_project->takeTask(m_node); m_added = false; } SubtaskAddCmd::SubtaskAddCmd(Project *project, Node *node, Node *parent, const KUndo2MagicString& name) : NamedCommand(name), m_project(project), m_node(node), m_parent(parent), m_added(false), m_cmd(0) { // set some reasonable defaults for normally calculated values node->setStartTime(parent->startTime()); node->setEndTime(node->startTime() + node->duration()); node->setEarlyStart(node->startTime()); node->setLateFinish(node->endTime()); node->setWorkStartTime(node->startTime()); node->setWorkEndTime(node->endTime()); // Summarytasks can't have resources, so remove resource requests from the new parent foreach (ResourceGroupRequest *r, parent->requests().requests()) { if (m_cmd == 0) m_cmd = new MacroCommand(KUndo2MagicString()); m_cmd->addCommand(new RemoveResourceGroupRequestCmd(r)); } // Also remove accounts if (parent->runningAccount()) { if (m_cmd == 0) m_cmd = new MacroCommand(KUndo2MagicString()); m_cmd->addCommand(new NodeModifyRunningAccountCmd(*parent, parent->runningAccount(), 0)); } if (parent->startupAccount()) { if (m_cmd == 0) m_cmd = new MacroCommand(KUndo2MagicString()); m_cmd->addCommand(new NodeModifyStartupAccountCmd(*parent, parent->startupAccount(), 0)); } if (parent->shutdownAccount()) { if (m_cmd == 0) m_cmd = new MacroCommand(KUndo2MagicString()); m_cmd->addCommand(new NodeModifyShutdownAccountCmd(*parent, parent->shutdownAccount(), 0)); } } SubtaskAddCmd::~SubtaskAddCmd() { delete m_cmd; if (!m_added) delete m_node; } void SubtaskAddCmd::execute() { m_project->addSubTask(m_node, m_parent); if (m_cmd) { m_cmd->execute(); } m_added = true; } void SubtaskAddCmd::unexecute() { m_project->takeTask(m_node); if (m_cmd) { m_cmd->unexecute(); } m_added = false; } NodeModifyNameCmd::NodeModifyNameCmd(Node &node, const QString& nodename, const KUndo2MagicString& name) : NamedCommand(name), m_node(node), newName(nodename), oldName(node.name()) { } void NodeModifyNameCmd::execute() { m_node.setName(newName); } void NodeModifyNameCmd::unexecute() { m_node.setName(oldName); } NodeModifyPriorityCmd::NodeModifyPriorityCmd(Node &node, int oldValue, int newValue, const KUndo2MagicString& name) : NamedCommand(name) , m_node(node) , m_oldValue(oldValue) , m_newValue(newValue) { } void NodeModifyPriorityCmd::execute() { m_node.setPriority(m_newValue); } void NodeModifyPriorityCmd::unexecute() { m_node.setPriority(m_oldValue); } NodeModifyLeaderCmd::NodeModifyLeaderCmd(Node &node, const QString& leader, const KUndo2MagicString& name) : NamedCommand(name), m_node(node), newLeader(leader), oldLeader(node.leader()) { } void NodeModifyLeaderCmd::execute() { m_node.setLeader(newLeader); } void NodeModifyLeaderCmd::unexecute() { m_node.setLeader(oldLeader); } NodeModifyDescriptionCmd::NodeModifyDescriptionCmd(Node &node, const QString& description, const KUndo2MagicString& name) : NamedCommand(name), m_node(node), newDescription(description), oldDescription(node.description()) { } void NodeModifyDescriptionCmd::execute() { m_node.setDescription(newDescription); } void NodeModifyDescriptionCmd::unexecute() { m_node.setDescription(oldDescription); } NodeModifyConstraintCmd::NodeModifyConstraintCmd(Node &node, Node::ConstraintType c, const KUndo2MagicString& name) : NamedCommand(name), m_node(node), newConstraint(c), oldConstraint(static_cast(node.constraint())) { } void NodeModifyConstraintCmd::execute() { m_node.setConstraint(newConstraint); } void NodeModifyConstraintCmd::unexecute() { m_node.setConstraint(oldConstraint); } NodeModifyConstraintStartTimeCmd::NodeModifyConstraintStartTimeCmd(Node &node, const QDateTime& dt, const KUndo2MagicString& name) : NamedCommand(name), m_node(node), newTime(dt), oldTime(node.constraintStartTime()) { if (node.projectNode()) { m_timeZone = static_cast(node.projectNode())->timeZone(); } } void NodeModifyConstraintStartTimeCmd::execute() { m_node.setConstraintStartTime(DateTime(newTime, m_timeZone)); } void NodeModifyConstraintStartTimeCmd::unexecute() { m_node.setConstraintStartTime(oldTime); } NodeModifyConstraintEndTimeCmd::NodeModifyConstraintEndTimeCmd(Node &node, const QDateTime& dt, const KUndo2MagicString& name) : NamedCommand(name), m_node(node), newTime(dt), oldTime(node.constraintEndTime()) { if (node.projectNode()) { m_timeZone = static_cast(node.projectNode())->timeZone(); } } void NodeModifyConstraintEndTimeCmd::execute() { m_node.setConstraintEndTime(DateTime(newTime, m_timeZone)); } void NodeModifyConstraintEndTimeCmd::unexecute() { m_node.setConstraintEndTime(oldTime); } NodeModifyStartTimeCmd::NodeModifyStartTimeCmd(Node &node, const QDateTime& dt, const KUndo2MagicString& name) : NamedCommand(name), m_node(node), newTime(dt), oldTime(node.startTime()) { m_timeZone = static_cast(node.projectNode())->timeZone(); } void NodeModifyStartTimeCmd::execute() { m_node.setStartTime(DateTime(newTime, m_timeZone)); } void NodeModifyStartTimeCmd::unexecute() { m_node.setStartTime(oldTime); } NodeModifyEndTimeCmd::NodeModifyEndTimeCmd(Node &node, const QDateTime& dt, const KUndo2MagicString& name) : NamedCommand(name), m_node(node), newTime(dt), oldTime(node.endTime()) { m_timeZone = static_cast(node.projectNode())->timeZone(); } void NodeModifyEndTimeCmd::execute() { m_node.setEndTime(DateTime(newTime, m_timeZone)); } void NodeModifyEndTimeCmd::unexecute() { m_node.setEndTime(oldTime); } NodeModifyIdCmd::NodeModifyIdCmd(Node &node, const QString& id, const KUndo2MagicString& name) : NamedCommand(name), m_node(node), newId(id), oldId(node.id()) { } void NodeModifyIdCmd::execute() { m_node.setId(newId); } void NodeModifyIdCmd::unexecute() { m_node.setId(oldId); } NodeIndentCmd::NodeIndentCmd(Node &node, const KUndo2MagicString& name) : NamedCommand(name), m_node(node), m_newparent(0), m_newindex(-1), m_cmd(0) { } NodeIndentCmd::~NodeIndentCmd() { delete m_cmd; } void NodeIndentCmd::execute() { m_oldparent = m_node.parentNode(); m_oldindex = m_oldparent->findChildNode(&m_node); Project *p = dynamic_cast(m_node.projectNode()); if (p && p->indentTask(&m_node, m_newindex)) { m_newparent = m_node.parentNode(); m_newindex = m_newparent->findChildNode(&m_node); // Summarytasks can't have resources, so remove resource requests from the new parent if (m_cmd == 0) { foreach (ResourceGroupRequest *r, m_newparent->requests().requests()) { if (m_cmd == 0) m_cmd = new MacroCommand(KUndo2MagicString()); m_cmd->addCommand(new RemoveResourceGroupRequestCmd(r)); } // Also remove accounts if (m_newparent->runningAccount()) { if (m_cmd == 0) m_cmd = new MacroCommand(KUndo2MagicString()); m_cmd->addCommand(new NodeModifyRunningAccountCmd(*m_newparent, m_newparent->runningAccount(), 0)); } if (m_newparent->startupAccount()) { if (m_cmd == 0) m_cmd = new MacroCommand(KUndo2MagicString()); m_cmd->addCommand(new NodeModifyStartupAccountCmd(*m_newparent, m_newparent->startupAccount(), 0)); } if (m_newparent->shutdownAccount()) { if (m_cmd == 0) m_cmd = new MacroCommand(KUndo2MagicString()); m_cmd->addCommand(new NodeModifyShutdownAccountCmd(*m_newparent, m_newparent->shutdownAccount(), 0)); } } if (m_cmd) { m_cmd->execute(); } } } void NodeIndentCmd::unexecute() { Project * p = dynamic_cast(m_node.projectNode()); if (m_newindex != -1 && p && p->unindentTask(&m_node)) { m_newindex = -1; if (m_cmd) { m_cmd->unexecute(); } } } NodeUnindentCmd::NodeUnindentCmd(Node &node, const KUndo2MagicString& name) : NamedCommand(name), m_node(node), m_newparent(0), m_newindex(-1) {} void NodeUnindentCmd::execute() { m_oldparent = m_node.parentNode(); m_oldindex = m_oldparent->findChildNode(&m_node); Project *p = dynamic_cast(m_node.projectNode()); if (p && p->unindentTask(&m_node)) { m_newparent = m_node.parentNode(); m_newindex = m_newparent->findChildNode(&m_node); } } void NodeUnindentCmd::unexecute() { Project * p = dynamic_cast(m_node.projectNode()); if (m_newindex != -1 && p && p->indentTask(&m_node, m_oldindex)) { m_newindex = -1; } } NodeMoveUpCmd::NodeMoveUpCmd(Node &node, const KUndo2MagicString& name) : NamedCommand(name), m_node(node), m_moved(false) { m_project = static_cast(m_node.projectNode()); } void NodeMoveUpCmd::execute() { if (m_project) { m_moved = m_project->moveTaskUp(&m_node); } } void NodeMoveUpCmd::unexecute() { if (m_project && m_moved) { m_project->moveTaskDown(&m_node); } m_moved = false; } NodeMoveDownCmd::NodeMoveDownCmd(Node &node, const KUndo2MagicString& name) : NamedCommand(name), m_node(node), m_moved(false) { m_project = static_cast(m_node.projectNode()); } void NodeMoveDownCmd::execute() { if (m_project) { m_moved = m_project->moveTaskDown(&m_node); } } void NodeMoveDownCmd::unexecute() { if (m_project && m_moved) { m_project->moveTaskUp(&m_node); } m_moved = false; } NodeMoveCmd::NodeMoveCmd(Project *project, Node *node, Node *newParent, int newPos, const KUndo2MagicString& name) : NamedCommand(name), m_project(project), m_node(node), m_newparent(newParent), m_newpos(newPos), m_moved(false) { m_oldparent = node->parentNode(); Q_ASSERT(m_oldparent); } void NodeMoveCmd::execute() { if (m_project) { m_oldpos = m_oldparent->indexOf(m_node); m_moved = m_project->moveTask(m_node, m_newparent, m_newpos); if (m_moved) { if (m_cmd.isEmpty()) { // Summarytasks can't have resources, so remove resource requests from the new parent foreach (ResourceGroupRequest *r, m_newparent->requests().requests()) { m_cmd.addCommand(new RemoveResourceGroupRequestCmd(r)); } // TODO appointments ?? } m_cmd.execute(); } } } void NodeMoveCmd::unexecute() { if (m_project && m_moved) { m_moved = m_project->moveTask(m_node, m_oldparent, m_oldpos); m_cmd.unexecute(); } m_moved = false; } AddRelationCmd::AddRelationCmd(Project &project, Relation *rel, const KUndo2MagicString& name) : NamedCommand(name), m_rel(rel), m_project(project) { m_taken = true; } AddRelationCmd::~AddRelationCmd() { if (m_taken) delete m_rel; } void AddRelationCmd::execute() { //debugPlan<parent()<<" to"<child(); m_taken = false; m_project.addRelation(m_rel, false); } void AddRelationCmd::unexecute() { m_taken = true; m_project.takeRelation(m_rel); } DeleteRelationCmd::DeleteRelationCmd(Project &project, Relation *rel, const KUndo2MagicString& name) : NamedCommand(name), m_rel(rel), m_project(project) { m_taken = false; } DeleteRelationCmd::~DeleteRelationCmd() { if (m_taken) { // do not access nodes, the may already be deleted m_rel->setParent(0); m_rel->setChild(0); delete m_rel; } } void DeleteRelationCmd::execute() { //debugPlan<parent()<<" to"<child(); m_taken = true; m_project.takeRelation(m_rel); } void DeleteRelationCmd::unexecute() { m_taken = false; m_project.addRelation(m_rel, false); } ModifyRelationTypeCmd::ModifyRelationTypeCmd(Relation *rel, Relation::Type type, const KUndo2MagicString& name) : NamedCommand(name), m_rel(rel), m_newtype(type) { m_oldtype = rel->type(); m_project = dynamic_cast(rel->parent() ->projectNode()); } void ModifyRelationTypeCmd::execute() { if (m_project) { m_project->setRelationType(m_rel, m_newtype); } } void ModifyRelationTypeCmd::unexecute() { if (m_project) { m_project->setRelationType(m_rel, m_oldtype); } } ModifyRelationLagCmd::ModifyRelationLagCmd(Relation *rel, Duration lag, const KUndo2MagicString& name) : NamedCommand(name), m_rel(rel), m_newlag(lag) { m_oldlag = rel->lag(); m_project = dynamic_cast(rel->parent() ->projectNode()); } void ModifyRelationLagCmd::execute() { if (m_project) { m_project->setRelationLag(m_rel, m_newlag); } } void ModifyRelationLagCmd::unexecute() { if (m_project) { m_project->setRelationLag(m_rel, m_oldlag); } } +AddResourceRequestCmd::AddResourceRequestCmd(ResourceRequestCollection *requests, ResourceRequest *request, ResourceGroupRequest *group, const KUndo2MagicString& name) + : NamedCommand(name) + , m_collection(requests) + , m_group(group) + , m_request(request) +{ + m_mine = true; +} AddResourceRequestCmd::AddResourceRequestCmd(ResourceGroupRequest *group, ResourceRequest *request, const KUndo2MagicString& name) - : NamedCommand(name), - m_group(group), - m_request(request) + : NamedCommand(name), + m_group(group), + m_request(request) { - + Q_ASSERT(group->parent()); + m_collection = group->parent(); m_mine = true; } AddResourceRequestCmd::~AddResourceRequestCmd() { if (m_mine) delete m_request; } void AddResourceRequestCmd::execute() { //debugPlan<<"group="<addCommand(new RemoveCompletionEntryCmd(completion, date)); cmd->addCommand(new AddCompletionEntryCmd(completion, date, value)); } ModifyCompletionEntryCmd::~ModifyCompletionEntryCmd() { delete cmd; } void ModifyCompletionEntryCmd::execute() { cmd->execute(); } void ModifyCompletionEntryCmd::unexecute() { cmd->unexecute(); } AddCompletionUsedEffortCmd::AddCompletionUsedEffortCmd(Completion &completion, const Resource *resource, Completion::UsedEffort *value, const KUndo2MagicString& name) : NamedCommand(name), m_completion(completion), m_resource(resource), newvalue(value), m_newmine(true), m_oldmine(false) { oldvalue = m_completion.usedEffort(resource); } AddCompletionUsedEffortCmd::~AddCompletionUsedEffortCmd() { if (m_oldmine) delete oldvalue; if (m_newmine) delete newvalue; } void AddCompletionUsedEffortCmd::execute() { if (oldvalue) { m_completion.takeUsedEffort(m_resource); m_oldmine = true; } m_completion.addUsedEffort(m_resource, newvalue); m_newmine = false; } void AddCompletionUsedEffortCmd::unexecute() { m_completion.takeUsedEffort(m_resource); if (oldvalue) { m_completion.addUsedEffort(m_resource, oldvalue); } m_newmine = true; m_oldmine = false; } AddCompletionActualEffortCmd::AddCompletionActualEffortCmd(Task *task, Resource *resource, const QDate &date, const Completion::UsedEffort::ActualEffort &value, const KUndo2MagicString& name) : NamedCommand(name) , m_task(task) , m_resource(resource) , m_date(date) , newvalue(value) { oldvalue = task->completion().getActualEffort(resource, date); } AddCompletionActualEffortCmd::~AddCompletionActualEffortCmd() { } void AddCompletionActualEffortCmd::execute() { m_task->completion().setActualEffort(m_resource, m_date, newvalue); } void AddCompletionActualEffortCmd::unexecute() { m_task->completion().setActualEffort(m_resource, m_date, oldvalue); } AddAccountCmd::AddAccountCmd(Project &project, Account *account, const QString& parent, int index, const KUndo2MagicString& name) : NamedCommand(name), m_project(project), m_account(account), m_parent(0), m_index(index), m_parentName(parent) { m_mine = true; } AddAccountCmd::AddAccountCmd(Project &project, Account *account, Account *parent, int index, const KUndo2MagicString& name) : NamedCommand(name), m_project(project), m_account(account), m_parent(parent), m_index(index) { m_mine = true; } AddAccountCmd::~AddAccountCmd() { if (m_mine) delete m_account; } void AddAccountCmd::execute() { if (m_parent == 0 && !m_parentName.isEmpty()) { m_parent = m_project.accounts().findAccount(m_parentName); } m_project.accounts().insert(m_account, m_parent, m_index); m_mine = false; } void AddAccountCmd::unexecute() { m_project.accounts().take(m_account); m_mine = true; } RemoveAccountCmd::RemoveAccountCmd(Project &project, Account *account, const KUndo2MagicString& name) : NamedCommand(name), m_project(project), m_account(account), m_parent(account->parent()) { if (m_parent) { m_index = m_parent->accountList().indexOf(account); } else { m_index = project.accounts().accountList().indexOf(account); } m_mine = false; m_isDefault = account == project.accounts().defaultAccount(); for (Account::CostPlace *cp : m_account->costPlaces()) { if (cp->node()) { if (cp->running()) { m_cmd.addCommand(new NodeModifyRunningAccountCmd(*cp->node(), cp->node()->runningAccount(), 0)); } if (cp->startup()) { m_cmd.addCommand(new NodeModifyStartupAccountCmd(*cp->node(), cp->node()->startupAccount(), 0)); } if (cp->shutdown()) { m_cmd.addCommand(new NodeModifyShutdownAccountCmd(*cp->node(), cp->node()->shutdownAccount(), 0)); } } else if (cp->resource()) { m_cmd.addCommand(new ResourceModifyAccountCmd(*cp->resource(), cp->resource()->account(), 0)); } } for (int i = account->accountList().count()-1; i >= 0; --i) { m_cmd.addCommand(new RemoveAccountCmd(project, account->accountList().at(i))); } } RemoveAccountCmd::~RemoveAccountCmd() { if (m_mine) delete m_account; } void RemoveAccountCmd::execute() { if (m_isDefault) { m_project.accounts().setDefaultAccount(0); } m_cmd.execute(); // remove costplaces and children m_project.accounts().take(m_account); m_mine = true; } void RemoveAccountCmd::unexecute() { m_project.accounts().insert(m_account, m_parent, m_index); m_cmd.unexecute(); // add costplaces && children if (m_isDefault) { m_project.accounts().setDefaultAccount(m_account); } m_mine = false; } RenameAccountCmd::RenameAccountCmd(Account *account, const QString& value, const KUndo2MagicString& name) : NamedCommand(name), m_account(account) { m_oldvalue = account->name(); m_newvalue = value; } void RenameAccountCmd::execute() { m_account->setName(m_newvalue); } void RenameAccountCmd::unexecute() { m_account->setName(m_oldvalue); } ModifyAccountDescriptionCmd::ModifyAccountDescriptionCmd(Account *account, const QString& value, const KUndo2MagicString& name) : NamedCommand(name), m_account(account) { m_oldvalue = account->description(); m_newvalue = value; } void ModifyAccountDescriptionCmd::execute() { m_account->setDescription(m_newvalue); } void ModifyAccountDescriptionCmd::unexecute() { m_account->setDescription(m_oldvalue); } NodeModifyStartupCostCmd::NodeModifyStartupCostCmd(Node &node, double value, const KUndo2MagicString& name) : NamedCommand(name), m_node(node) { m_oldvalue = node.startupCost(); m_newvalue = value; } void NodeModifyStartupCostCmd::execute() { m_node.setStartupCost(m_newvalue); } void NodeModifyStartupCostCmd::unexecute() { m_node.setStartupCost(m_oldvalue); } NodeModifyShutdownCostCmd::NodeModifyShutdownCostCmd(Node &node, double value, const KUndo2MagicString& name) : NamedCommand(name), m_node(node) { m_oldvalue = node.shutdownCost(); m_newvalue = value; } void NodeModifyShutdownCostCmd::execute() { m_node.setShutdownCost(m_newvalue); } void NodeModifyShutdownCostCmd::unexecute() { m_node.setShutdownCost(m_oldvalue); } NodeModifyRunningAccountCmd::NodeModifyRunningAccountCmd(Node &node, Account *oldvalue, Account *newvalue, const KUndo2MagicString& name) : NamedCommand(name), m_node(node) { m_oldvalue = oldvalue; m_newvalue = newvalue; //debugPlan; } void NodeModifyRunningAccountCmd::execute() { //debugPlan; if (m_oldvalue) { m_oldvalue->removeRunning(m_node); } if (m_newvalue) { m_newvalue->addRunning(m_node); } } void NodeModifyRunningAccountCmd::unexecute() { //debugPlan; if (m_newvalue) { m_newvalue->removeRunning(m_node); } if (m_oldvalue) { m_oldvalue->addRunning(m_node); } } NodeModifyStartupAccountCmd::NodeModifyStartupAccountCmd(Node &node, Account *oldvalue, Account *newvalue, const KUndo2MagicString& name) : NamedCommand(name), m_node(node) { m_oldvalue = oldvalue; m_newvalue = newvalue; //debugPlan; } void NodeModifyStartupAccountCmd::execute() { //debugPlan; if (m_oldvalue) { m_oldvalue->removeStartup(m_node); } if (m_newvalue) { m_newvalue->addStartup(m_node); } } void NodeModifyStartupAccountCmd::unexecute() { //debugPlan; if (m_newvalue) { m_newvalue->removeStartup(m_node); } if (m_oldvalue) { m_oldvalue->addStartup(m_node); } } NodeModifyShutdownAccountCmd::NodeModifyShutdownAccountCmd(Node &node, Account *oldvalue, Account *newvalue, const KUndo2MagicString& name) : NamedCommand(name), m_node(node) { m_oldvalue = oldvalue; m_newvalue = newvalue; //debugPlan; } void NodeModifyShutdownAccountCmd::execute() { //debugPlan; if (m_oldvalue) { m_oldvalue->removeShutdown(m_node); } if (m_newvalue) { m_newvalue->addShutdown(m_node); } } void NodeModifyShutdownAccountCmd::unexecute() { //debugPlan; if (m_newvalue) { m_newvalue->removeShutdown(m_node); } if (m_oldvalue) { m_oldvalue->addShutdown(m_node); } } ModifyDefaultAccountCmd::ModifyDefaultAccountCmd(Accounts &acc, Account *oldvalue, Account *newvalue, const KUndo2MagicString& name) : NamedCommand(name), m_accounts(acc) { m_oldvalue = oldvalue; m_newvalue = newvalue; //debugPlan; } void ModifyDefaultAccountCmd::execute() { //debugPlan; m_accounts.setDefaultAccount(m_newvalue); } void ModifyDefaultAccountCmd::unexecute() { //debugPlan; m_accounts.setDefaultAccount(m_oldvalue); } ResourceModifyAccountCmd::ResourceModifyAccountCmd(Resource &resource, Account *oldvalue, Account *newvalue, const KUndo2MagicString& name) : NamedCommand(name), m_resource(resource) { m_oldvalue = oldvalue; m_newvalue = newvalue; } void ResourceModifyAccountCmd::execute() { //debugPlan; if (m_oldvalue) { m_oldvalue->removeRunning(m_resource); } if (m_newvalue) { m_newvalue->addRunning(m_resource); } } void ResourceModifyAccountCmd::unexecute() { //debugPlan; if (m_newvalue) { m_newvalue->removeRunning(m_resource); } if (m_oldvalue) { m_oldvalue->addRunning(m_resource); } } ProjectModifyConstraintCmd::ProjectModifyConstraintCmd(Project &node, Node::ConstraintType c, const KUndo2MagicString& name) : NamedCommand(name), m_node(node), newConstraint(c), oldConstraint(static_cast(node.constraint())) { } void ProjectModifyConstraintCmd::execute() { m_node.setConstraint(newConstraint); } void ProjectModifyConstraintCmd::unexecute() { m_node.setConstraint(oldConstraint); } ProjectModifyStartTimeCmd::ProjectModifyStartTimeCmd(Project &node, const QDateTime& dt, const KUndo2MagicString& name) : NamedCommand(name), m_node(node), newTime(dt), oldTime(node.startTime()) { m_timeZone = node.timeZone(); } void ProjectModifyStartTimeCmd::execute() { m_node.setConstraintStartTime(DateTime(newTime, m_timeZone)); } void ProjectModifyStartTimeCmd::unexecute() { m_node.setConstraintStartTime(oldTime); } ProjectModifyEndTimeCmd::ProjectModifyEndTimeCmd(Project &node, const QDateTime& dt, const KUndo2MagicString& name) : NamedCommand(name), m_node(node), newTime(dt), oldTime(node.endTime()) { m_timeZone = node.timeZone(); } void ProjectModifyEndTimeCmd::execute() { m_node.setEndTime(DateTime(newTime, m_timeZone)); m_node.setConstraintEndTime(DateTime(newTime, m_timeZone)); } void ProjectModifyEndTimeCmd::unexecute() { m_node.setConstraintEndTime(oldTime); } ProjectModifyWorkPackageInfoCmd::ProjectModifyWorkPackageInfoCmd(Project &project, const Project::WorkPackageInfo &wpi, const KUndo2MagicString& name) : NamedCommand(name) , m_node(project) , m_newWpi(wpi) , m_oldWpi(project.workPackageInfo()) { } void ProjectModifyWorkPackageInfoCmd::execute() { m_node.setWorkPackageInfo(m_newWpi); } void ProjectModifyWorkPackageInfoCmd::unexecute() { m_node.setWorkPackageInfo(m_oldWpi); } //---------------------------- SwapScheduleManagerCmd::SwapScheduleManagerCmd(Project &project, ScheduleManager *from, ScheduleManager *to, const KUndo2MagicString& name) : NamedCommand(name), m_node(project), m_from(from), m_to(to) { } SwapScheduleManagerCmd::~SwapScheduleManagerCmd() { } void SwapScheduleManagerCmd::execute() { m_node.swapScheduleManagers(m_from, m_to); } void SwapScheduleManagerCmd::unexecute() { m_node.swapScheduleManagers(m_to, m_from); } AddScheduleManagerCmd::AddScheduleManagerCmd(Project &node, ScheduleManager *sm, int index, const KUndo2MagicString& name) : NamedCommand(name), m_node(node), m_parent(sm->parentManager()), m_sm(sm), m_index(index), m_exp(sm->expected()), m_mine(true) { } AddScheduleManagerCmd::AddScheduleManagerCmd(ScheduleManager *parent, ScheduleManager *sm, int index, const KUndo2MagicString& name) : NamedCommand(name), m_node(parent->project()), m_parent(parent), m_sm(sm), m_index(index), m_exp(sm->expected()), m_mine(true) { if (parent->schedulingMode() == ScheduleManager::AutoMode) { m_cmd.addCommand(new ModifyScheduleManagerSchedulingModeCmd(*parent, ScheduleManager::ManualMode)); } } AddScheduleManagerCmd::~AddScheduleManagerCmd() { if (m_mine) { m_sm->setParentManager(0); delete m_sm; } } void AddScheduleManagerCmd::execute() { m_cmd.redo(); m_node.addScheduleManager(m_sm, m_parent, m_index); m_sm->setExpected(m_exp); m_mine = false; } void AddScheduleManagerCmd::unexecute() { m_node.takeScheduleManager(m_sm); m_sm->setExpected(0); m_mine = true; m_cmd.undo(); } DeleteScheduleManagerCmd::DeleteScheduleManagerCmd(Project &node, ScheduleManager *sm, const KUndo2MagicString& name) : AddScheduleManagerCmd(node, sm, -1, name) { m_mine = false; m_index = m_parent ? m_parent->indexOf(sm) : node.indexOf(sm); foreach (ScheduleManager *s, sm->children()) { cmd.addCommand(new DeleteScheduleManagerCmd(node, s)); } } void DeleteScheduleManagerCmd::execute() { cmd.execute(); AddScheduleManagerCmd::unexecute(); } void DeleteScheduleManagerCmd::unexecute() { AddScheduleManagerCmd::execute(); cmd.unexecute(); } MoveScheduleManagerCmd::MoveScheduleManagerCmd(ScheduleManager *sm, ScheduleManager *newparent, int newindex, const KUndo2MagicString& name) : NamedCommand(name), m_sm(sm), m_oldparent(sm->parentManager()), m_newparent(newparent), m_newindex(newindex) { m_oldindex = sm->parentManager() ? sm->parentManager()->indexOf(sm) : sm->project().indexOf(sm); } void MoveScheduleManagerCmd::execute() { m_sm->project().moveScheduleManager(m_sm, m_newparent, m_newindex); } void MoveScheduleManagerCmd::unexecute() { m_sm->project().moveScheduleManager(m_sm, m_oldparent, m_oldindex); } ModifyScheduleManagerNameCmd::ModifyScheduleManagerNameCmd(ScheduleManager &sm, const QString& value, const KUndo2MagicString& name) : NamedCommand(name), m_sm(sm), oldvalue(sm.name()), newvalue(value) { } void ModifyScheduleManagerNameCmd::execute() { m_sm.setName(newvalue); } void ModifyScheduleManagerNameCmd::unexecute() { m_sm.setName(oldvalue); } ModifyScheduleManagerSchedulingModeCmd::ModifyScheduleManagerSchedulingModeCmd(ScheduleManager &sm, int value, const KUndo2MagicString& name) : NamedCommand(name), m_sm(sm), oldvalue(sm.schedulingMode()), newvalue(value) { if (value == ScheduleManager::AutoMode) { // Allow only one for (ScheduleManager *m : sm.project().allScheduleManagers()) { if (m->schedulingMode() == ScheduleManager::AutoMode) { m_cmd.addCommand(new ModifyScheduleManagerSchedulingModeCmd(*m, ScheduleManager::ManualMode)); break; } } } } void ModifyScheduleManagerSchedulingModeCmd::execute() { m_cmd.redo(); m_sm.setSchedulingMode(newvalue); } void ModifyScheduleManagerSchedulingModeCmd::unexecute() { m_sm.setSchedulingMode(oldvalue); m_cmd.undo(); } ModifyScheduleManagerAllowOverbookingCmd::ModifyScheduleManagerAllowOverbookingCmd(ScheduleManager &sm, bool value, const KUndo2MagicString& name) : NamedCommand(name), m_sm(sm), oldvalue(sm.allowOverbooking()), newvalue(value) { } void ModifyScheduleManagerAllowOverbookingCmd::execute() { m_sm.setAllowOverbooking(newvalue); } void ModifyScheduleManagerAllowOverbookingCmd::unexecute() { m_sm.setAllowOverbooking(oldvalue); } ModifyScheduleManagerDistributionCmd::ModifyScheduleManagerDistributionCmd(ScheduleManager &sm, bool value, const KUndo2MagicString& name) : NamedCommand(name), m_sm(sm), oldvalue(sm.usePert()), newvalue(value) { } void ModifyScheduleManagerDistributionCmd::execute() { m_sm.setUsePert(newvalue); } void ModifyScheduleManagerDistributionCmd::unexecute() { m_sm.setUsePert(oldvalue); } ModifyScheduleManagerSchedulingDirectionCmd::ModifyScheduleManagerSchedulingDirectionCmd(ScheduleManager &sm, bool value, const KUndo2MagicString& name) : NamedCommand(name), m_sm(sm), oldvalue(sm.schedulingDirection()), newvalue(value) { } void ModifyScheduleManagerSchedulingDirectionCmd::execute() { m_sm.setSchedulingDirection(newvalue); } void ModifyScheduleManagerSchedulingDirectionCmd::unexecute() { m_sm.setSchedulingDirection(oldvalue); } ModifyScheduleManagerSchedulerCmd::ModifyScheduleManagerSchedulerCmd(ScheduleManager &sm, int value, const KUndo2MagicString& name) : NamedCommand(name), m_sm(sm), oldvalue(sm.schedulerPluginIndex()), newvalue(value) { } void ModifyScheduleManagerSchedulerCmd::execute() { m_sm.setSchedulerPlugin(newvalue); } void ModifyScheduleManagerSchedulerCmd::unexecute() { m_sm.setSchedulerPlugin(oldvalue); } ModifyScheduleManagerSchedulingGranularityCmd::ModifyScheduleManagerSchedulingGranularityCmd(ScheduleManager &sm, int value, const KUndo2MagicString& name) : NamedCommand(name), m_sm(sm), oldvalue(sm.granularity()), newvalue(value) { } void ModifyScheduleManagerSchedulingGranularityCmd::execute() { m_sm.setGranularity(newvalue); } void ModifyScheduleManagerSchedulingGranularityCmd::unexecute() { m_sm.setGranularity(oldvalue); } CalculateScheduleCmd::CalculateScheduleCmd(Project &node, ScheduleManager *sm, const KUndo2MagicString& name) : NamedCommand(name), m_node(node), m_sm(sm), m_first(true), m_newexpected(0) { if (sm->recalculate() && sm->isScheduled()) { m_sm = new ScheduleManager(node); m_sm->setRecalculate(true); m_sm->setGranularity(sm->granularity()); m_sm->setUsePert(sm->usePert()); m_sm->setSchedulerPluginId(sm->schedulerPluginId()); m_sm->setAllowOverbooking(sm->allowOverbooking()); m_sm->setName(sm->name()); preCmd.addCommand(new AddScheduleManagerCmd(sm, m_sm)); postCmd.addCommand(new SwapScheduleManagerCmd(node, sm, m_sm)); postCmd.addCommand(new MoveScheduleManagerCmd(m_sm, sm->parentManager(), sm->parentManager() ? sm->parentManager()->indexOf(sm)+1 : node.indexOf(sm)+1)); postCmd.addCommand(new DeleteScheduleManagerCmd(node, sm)); } m_oldexpected = m_sm->expected(); } CalculateScheduleCmd::~CalculateScheduleCmd() { if (m_sm->scheduling()) { m_sm->haltCalculation(); } } void CalculateScheduleCmd::execute() { Q_ASSERT(m_sm); preCmd.redo(); if (m_first) { m_sm->calculateSchedule(); if (m_sm->calculationResult() != ScheduleManager::CalculationCanceled) { m_first = false; } m_newexpected = m_sm->expected(); } else { m_sm->setExpected(m_newexpected); } postCmd.redo(); } void CalculateScheduleCmd::unexecute() { if (m_sm->scheduling()) { // terminate scheduling QApplication::setOverrideCursor(Qt::WaitCursor); m_sm->haltCalculation(); m_first = true; QApplication::restoreOverrideCursor(); } postCmd.undo(); m_sm->setExpected(m_oldexpected); preCmd.undo(); } //------------------------ BaselineScheduleCmd::BaselineScheduleCmd(ScheduleManager &sm, const KUndo2MagicString& name) : NamedCommand(name), m_sm(sm) { } void BaselineScheduleCmd::execute() { m_sm.setBaselined(true); } void BaselineScheduleCmd::unexecute() { m_sm.setBaselined(false); } ResetBaselineScheduleCmd::ResetBaselineScheduleCmd(ScheduleManager &sm, const KUndo2MagicString& name) : NamedCommand(name), m_sm(sm) { } void ResetBaselineScheduleCmd::execute() { m_sm.setBaselined(false); } void ResetBaselineScheduleCmd::unexecute() { m_sm.setBaselined(true); } //------------------------ ModifyStandardWorktimeYearCmd::ModifyStandardWorktimeYearCmd(StandardWorktime *wt, double oldvalue, double newvalue, const KUndo2MagicString& name) : NamedCommand(name), swt(wt), m_oldvalue(oldvalue), m_newvalue(newvalue) { } void ModifyStandardWorktimeYearCmd::execute() { swt->setYear(m_newvalue); } void ModifyStandardWorktimeYearCmd::unexecute() { swt->setYear(m_oldvalue); } ModifyStandardWorktimeMonthCmd::ModifyStandardWorktimeMonthCmd(StandardWorktime *wt, double oldvalue, double newvalue, const KUndo2MagicString& name) : NamedCommand(name), swt(wt), m_oldvalue(oldvalue), m_newvalue(newvalue) { } void ModifyStandardWorktimeMonthCmd::execute() { swt->setMonth(m_newvalue); } void ModifyStandardWorktimeMonthCmd::unexecute() { swt->setMonth(m_oldvalue); } ModifyStandardWorktimeWeekCmd::ModifyStandardWorktimeWeekCmd(StandardWorktime *wt, double oldvalue, double newvalue, const KUndo2MagicString& name) : NamedCommand(name), swt(wt), m_oldvalue(oldvalue), m_newvalue(newvalue) { } void ModifyStandardWorktimeWeekCmd::execute() { swt->setWeek(m_newvalue); } void ModifyStandardWorktimeWeekCmd::unexecute() { swt->setWeek(m_oldvalue); } ModifyStandardWorktimeDayCmd::ModifyStandardWorktimeDayCmd(StandardWorktime *wt, double oldvalue, double newvalue, const KUndo2MagicString& name) : NamedCommand(name), swt(wt), m_oldvalue(oldvalue), m_newvalue(newvalue) { } void ModifyStandardWorktimeDayCmd::execute() { swt->setDay(m_newvalue); } void ModifyStandardWorktimeDayCmd::unexecute() { swt->setDay(m_oldvalue); } //---------------- DocumentAddCmd::DocumentAddCmd(Documents &docs, Document *value, const KUndo2MagicString& name) : NamedCommand(name), m_docs(docs), m_mine(true) { Q_ASSERT(value); m_value = value; } DocumentAddCmd::~DocumentAddCmd() { //debugPlan; if (m_mine) delete m_value; } void DocumentAddCmd::execute() { m_docs.addDocument(m_value); m_mine = false; } void DocumentAddCmd::unexecute() { m_docs.takeDocument(m_value); m_mine = true; } //---------------- DocumentRemoveCmd::DocumentRemoveCmd(Documents &docs, Document *value, const KUndo2MagicString& name) : NamedCommand(name), m_docs(docs), m_mine(false) { Q_ASSERT(value); m_value = value; } DocumentRemoveCmd::~DocumentRemoveCmd() { //debugPlan; if (m_mine) delete m_value; } void DocumentRemoveCmd::execute() { m_docs.takeDocument(m_value); m_mine = true; } void DocumentRemoveCmd::unexecute() { m_docs.addDocument(m_value); m_mine = false; } //---------------- DocumentModifyUrlCmd::DocumentModifyUrlCmd(Document *doc, const QUrl &value, const KUndo2MagicString& name) : NamedCommand(name), m_doc(doc) { Q_ASSERT(doc); m_value = value; m_oldvalue = doc->url(); } void DocumentModifyUrlCmd::execute() { m_doc->setUrl(m_value); } void DocumentModifyUrlCmd::unexecute() { m_doc->setUrl(m_oldvalue); } //---------------- DocumentModifyNameCmd::DocumentModifyNameCmd(Document *doc, const QString &value, const KUndo2MagicString& name) : NamedCommand(name), m_doc(doc) { Q_ASSERT(doc); m_value = value; m_oldvalue = doc->name(); } void DocumentModifyNameCmd::execute() { m_doc->setName(m_value); } void DocumentModifyNameCmd::unexecute() { m_doc->setName(m_oldvalue); } //---------------- DocumentModifyTypeCmd::DocumentModifyTypeCmd(Document *doc, Document::Type value, const KUndo2MagicString& name) : NamedCommand(name), m_doc(doc) { Q_ASSERT(doc); m_value = value; m_oldvalue = doc->type(); } void DocumentModifyTypeCmd::execute() { m_doc->setType(m_value); } void DocumentModifyTypeCmd::unexecute() { m_doc->setType(m_oldvalue); } //---------------- DocumentModifyStatusCmd::DocumentModifyStatusCmd(Document *doc, const QString &value, const KUndo2MagicString& name) : NamedCommand(name), m_doc(doc) { Q_ASSERT(doc); m_value = value; m_oldvalue = doc->type(); } void DocumentModifyStatusCmd::execute() { m_doc->setStatus(m_value); } void DocumentModifyStatusCmd::unexecute() { m_doc->setStatus(m_oldvalue); } //---------------- DocumentModifySendAsCmd::DocumentModifySendAsCmd(Document *doc, const Document::SendAs value, const KUndo2MagicString& name) : NamedCommand(name), m_doc(doc) { Q_ASSERT(doc); m_value = value; m_oldvalue = doc->sendAs(); } void DocumentModifySendAsCmd::execute() { m_doc->setSendAs(m_value); } void DocumentModifySendAsCmd::unexecute() { m_doc->setSendAs(m_oldvalue); } //---------------- WBSDefinitionModifyCmd::WBSDefinitionModifyCmd(Project &project, const WBSDefinition value, const KUndo2MagicString& name) : NamedCommand(name), m_project(project) { m_newvalue = value; m_oldvalue = m_project.wbsDefinition(); } void WBSDefinitionModifyCmd::execute() { m_project.setWbsDefinition(m_newvalue); } void WBSDefinitionModifyCmd::unexecute() { m_project.setWbsDefinition(m_oldvalue); } //---------------- InsertProjectCmd::InsertProjectCmd(Project &fromProject, Node *parent, Node *after, const KUndo2MagicString& name) : MacroCommand(name), m_project(static_cast(parent->projectNode())), m_parent(parent) { Q_ASSERT(&fromProject != m_project); if (m_project->defaultCalendar()) { fromProject.setDefaultCalendar(0); // or else m_project default calendar may be overwitten } QString defaultAccount; if (! m_project->accounts().defaultAccount() && fromProject.accounts().defaultAccount()) { defaultAccount = fromProject.accounts().defaultAccount()->name(); } QHash startupaccountmap; QHash shutdownaccountmap; QHash runningaccountmap; QHash nodecalendarmap; // remove unhandled info in tasks and get accounts and calendars foreach (Node *n, fromProject.allNodes()) { if (n->type() == Node::Type_Task) { Task *t = static_cast(n); t->workPackage().clear(); while (! t->workPackageLog().isEmpty()) { WorkPackage *wp = t->workPackageLog().at(0); t->removeWorkPackage(wp); delete wp; } } if (n->startupAccount()) { startupaccountmap.insert(n, n->startupAccount()->name()); n->setStartupAccount(0); } if (n->shutdownAccount()) { shutdownaccountmap.insert(n, n->shutdownAccount()->name()); n->setShutdownAccount(0); } if (n->runningAccount()) { runningaccountmap.insert(n, n->runningAccount()->name()); n->setRunningAccount(0); } if (n->estimate()->calendar()) { nodecalendarmap.insert(n, n->estimate()->calendar()->id()); n->estimate()->setCalendar(0); } } // get resources pointing to calendars and accounts QHash resaccountmap; QHash rescalendarmap; foreach (Resource *r, fromProject.resourceList()) { if (r->account()) { resaccountmap.insert(r, r->account()->name()); r->setAccount(0); } if (r->calendar()) { rescalendarmap.insert(r, r->calendar()->id()); r->setCalendar(0); } } // create add account commands and keep track of used and unused accounts QList unusedAccounts; QMap accountsmap; foreach (Account *a, m_project->accounts().allAccounts()) { accountsmap.insert(a->name(), a); } foreach (Account *a, fromProject.accounts().accountList()) { addAccounts(a, 0, unusedAccounts, accountsmap); } // create add calendar commands and keep track of used and unused calendars QList unusedCalendars; QMap calendarsmap; foreach (Calendar *c, m_project->allCalendars()) { calendarsmap.insert(c->id(), c); } foreach (Calendar *c, fromProject.calendars()) { addCalendars(c, 0, unusedCalendars, calendarsmap); } // get all requests from fromProject before resources are merged QHash > greqs; QHash > rreqs; foreach (Node *n, fromProject.allNodes()) { QList resReq; if (n->type() != (int)Node::Type_Task || n->requests().isEmpty()) { continue; } while (ResourceGroupRequest *gr = n->requests().requests().value(0)) { while (ResourceRequest *rr = gr->resourceRequests(false).value(0)) { debugPlanInsertProject<<"Get resource request:"<(rr, rr->resource())); // all resource requests shall be reinserted rr->unregisterRequest(); gr->takeResourceRequest(rr); } // all group requests shall be reinserted greqs[ gr ] = QPair(n, gr->group()); gr->group()->unregisterRequest(gr); int i = n->requests().takeRequest(gr); Q_ASSERT(i >= 0); #ifdef NDEBUG Q_UNUSED(i); #endif } } debugPlanInsertProject<<"fromProject group requests:"< allGroups; QList newGroups; QHash existingGroups; // QHash foreach (ResourceGroup *g, fromProject.resourceGroups()) { ResourceGroup *gr = m_project->findResourceGroup(g->id()); if (gr == 0) { gr = g; newGroups << gr; } else { existingGroups[ gr ] = g; } allGroups << gr; } debugPlanInsertProject<<"fromProject resources:"< allResources; QList newResources; // resource in fromProject that does not exist in toProject QHash existingResources; // hash[toProject resource, fromProject resource] foreach (Resource *r, fromProject.resourceList()) { while (Schedule *s = r->schedules().values().value(0)) { r->deleteSchedule(s); // schedules not handled } Resource *res = m_project->resource(r->id()); if (res == nullptr) { newResources << r; allResources << r; } else { existingResources[ res ] = r; allResources << res; } } // Add new resources for (Resource *r : newResources) { debugPlanInsertProject<<"AddResourceCmd:"<name()<parentGroups(); addCommand(new AddResourceCmd(m_project, r, kundo2_noi18n("Resource"))); } // Add new groups for (ResourceGroup *g : newGroups) { debugPlanInsertProject<<"AddResourceGroupCmd:"<name()<resources(); addCommand(new AddResourceGroupCmd(m_project, g, kundo2_noi18n("ResourceGroup"))); for (Resource *r : g->resources()) { addCommand(new AddParentGroupCmd(r, g)); } } // Update resource account {QHash::const_iterator it = resaccountmap.constBegin(); QHash::const_iterator end = resaccountmap.constEnd(); for (; it != end; ++it) { Resource *r = it.key(); if (newResources.contains(r)) { Q_ASSERT(allResources.contains(r)); addCommand(new ResourceModifyAccountCmd(*r, 0, accountsmap.value(it.value()))); } }} // Update resource calendar {QHash::const_iterator it = rescalendarmap.constBegin(); QHash::const_iterator end = rescalendarmap.constEnd(); for (; it != end; ++it) { Resource *r = it.key(); if (newResources.contains(r)) { Q_ASSERT(allResources.contains(r)); addCommand(new ModifyResourceCalendarCmd(r, calendarsmap.value(it.value()))); } }} debugPlanInsertProject<<"Requests: clean up requests to resources already in m_project"; debugPlanInsertProject<<"All groups:"< >::const_iterator gregsIt; for (gregsIt = greqs.constBegin(); gregsIt != greqs.constEnd(); ++gregsIt) { ResourceGroupRequest *gr = gregsIt.key(); QPair pair = gregsIt.value(); Node *n = pair.first; + n->requests().removeRequests(); ResourceGroup *newGroup = pair.second; if (ResourceGroup *ng = existingGroups.key(newGroup)) { newGroup = ng; debugPlanInsertProject<<"Using existing group:"<requests(); } else { debugPlanInsertProject<<"Using group from inserted project:"<requests(); } Q_ASSERT(allGroups.contains(newGroup)); gr->setGroup(newGroup); + gr->setId(0); + gr->setParent(nullptr); addCommand(new AddResourceGroupRequestCmd(static_cast(*n), gr, kundo2_noi18n("Group %1", ++gi))); debugPlanInsertProject<<"Add resource group request: task:"<wbsCode()<name()<<":"<name()<<"requests:"<requests(); QHash >::const_iterator i = rreqs.constFind(gr); for (; i != rreqs.constEnd() && i.key() == gr; ++i) { ResourceRequest *rr = i.value().first; + rr->setId(0); + rr->setCollection(nullptr); + rr->setParent(nullptr); Resource *newRes = i.value().second; debugPlanInsertProject<<"Resource exists:"<id()<<(void*)newRes<<':'<resource(newRes->id()); if (Resource *nr = existingResources.key(newRes)) { newRes = nr; debugPlanInsertProject<<"Resource existed:"<wbsCode()<name()<<":"<name()<<":"<wbsCode()<name()<<"group:"<name()<<"resource:"<requiredResources().isEmpty()) { // the resource request may have required resources that needs mapping QList required; foreach (Resource *r, rr->requiredResources()) { if (newResources.contains(r)) { required << r; debugPlanInsertProject<<"Request: required (new)"<name(); continue; } Resource *r2 = existingResources.key(r); Q_ASSERT(allResources.contains(r2)); if (r2) { debugPlanInsertProject<<"Request: required (existing)"<name(); required << r2; } } rr->setRequiredResources(required); } Q_ASSERT(allResources.contains(newRes)); // all resource requests shall be reinserted rr->setResource(newRes); - addCommand(new AddResourceRequestCmd(gr, rr, kundo2_noi18n("Resource %1", ++ri))); + addCommand(new AddResourceRequestCmd(&n->requests(), rr, gr, kundo2_noi18n("Resource %1", ++ri))); } } // Add nodes (ids are unique, no need to check) Node *node_after = after; for (int i = 0; i < fromProject.numChildren(); ++i) { Node *n = fromProject.childNode(i); Q_ASSERT(n); while (Schedule *s = n->schedules().values().value(0)) { n->takeSchedule(s); // schedules not handled delete s; } n->setParentNode(0); if (node_after) { addCommand(new TaskAddCmd(m_project, n, node_after, kundo2_noi18n("Task"))); node_after = n; } else { addCommand(new SubtaskAddCmd(m_project, n, parent, kundo2_noi18n("Subtask"))); } addChildNodes(n); } // Dependencies: foreach (Node *n, fromProject.allNodes()) { while (n->numDependChildNodes() > 0) { Relation *r = n->dependChildNodes().at(0); n->takeDependChildNode(r); r->child()->takeDependParentNode(r); addCommand(new AddRelationCmd(*m_project, r)); } } // node calendar {QHash::const_iterator it = nodecalendarmap.constBegin(); QHash::const_iterator end = nodecalendarmap.constEnd(); for (; it != end; ++it) { addCommand(new ModifyEstimateCalendarCmd(*(it.key()), 0, calendarsmap.value(it.value()))); }} // node startup account {QHash::const_iterator it = startupaccountmap.constBegin(); QHash::const_iterator end = startupaccountmap.constEnd(); for (; it != end; ++it) { addCommand(new NodeModifyStartupAccountCmd(*(it.key()), 0, accountsmap.value(it.value()))); }} // node shutdown account {QHash::const_iterator it = shutdownaccountmap.constBegin(); QHash::const_iterator end = shutdownaccountmap.constEnd(); for (; it != end; ++it) { addCommand(new NodeModifyShutdownAccountCmd(*(it.key()), 0, accountsmap.value(it.value()))); }} // node running account {QHash::const_iterator it = runningaccountmap.constBegin(); QHash::const_iterator end = runningaccountmap.constEnd(); for (; it != end; ++it) { addCommand(new NodeModifyRunningAccountCmd(*(it.key()), 0, accountsmap.value(it.value()))); }} if (! defaultAccount.isEmpty()) { Account *a = accountsmap.value(defaultAccount); if (a && a->list()) { addCommand(new ModifyDefaultAccountCmd(m_project->accounts(), 0, a)); } } debugPlanInsertProject<<"Cleanup unused stuff from inserted project:"<<&fromProject; // Cleanup // Remove nodes from fromProject so they are not deleted while (Node *ch = fromProject.childNode(0)) { fromProject.takeChildNode(ch); } foreach (Node *n, fromProject.allNodes()) { fromProject.removeId(n->id()); } // Remove calendars from fromProject while (fromProject.calendarCount() > 0) { fromProject.takeCalendar(fromProject.calendarAt(0)); } qDeleteAll(unusedCalendars); // Remove accounts from fromProject while (fromProject.accounts().accountCount() > 0) { fromProject.accounts().take(fromProject.accounts().accountAt(0)); } qDeleteAll(unusedAccounts); for (Resource *r = fromProject.resourceList().value(0); r; r = fromProject.resourceList().value(0)) { debugPlanInsertProject<<"remove all resources from fromProject:"< 0) { ResourceGroup *g = fromProject.resourceGroupAt(0); debugPlanInsertProject<<"Take used group:"<requests(); fromProject.takeResourceGroup(g); } debugPlanInsertProject<<"Delete unused resources:"< &unused, QMap &calendarsmap) { Calendar *par = 0; if (parent) { par = calendarsmap.value(parent->id()); } if (par == 0) { par = parent; } Calendar *cal = calendarsmap.value(calendar->id()); if (cal == 0) { calendarsmap.insert(calendar->id(), calendar); addCommand(new CalendarAddCmd(m_project, calendar, -1, par)); } else { unused << calendar; } foreach (Calendar *c, calendar->calendars()) { addCalendars(c, calendar, unused, calendarsmap); } } void InsertProjectCmd::addAccounts(Account *account, Account *parent, QList &unused, QMap &accountsmap) { Account *par = 0; if (parent) { par = accountsmap.value(parent->name()); } if (par == 0) { par = parent; } Account *acc = accountsmap.value(account->name()); if (acc == 0) { debugPlanInsertProject<<"Move to new project:"<name(); accountsmap.insert(account->name(), account); addCommand(new AddAccountCmd(*m_project, account, par, -1, kundo2_noi18n("Add account %1", account->name()))); } else { debugPlanInsertProject<<"Already exists:"<name(); unused << account; } while (! account->accountList().isEmpty()) { Account *a = account->accountList().first(); account->list()->take(a); addAccounts(a, account, unused, accountsmap); } } void InsertProjectCmd::addChildNodes(Node *node) { // schedules not handled while (Schedule *s = node->schedules().values().value(0)) { node->takeSchedule(s); // schedules not handled delete s; } foreach (Node *n, node->childNodeIterator()) { n->setParentNode(0); addCommand(new SubtaskAddCmd(m_project, n, node, kundo2_noi18n("Subtask"))); addChildNodes(n); } // Remove child nodes so they are not added twice while (Node *ch = node->childNode(0)) { node->takeChildNode(ch); } } void InsertProjectCmd::execute() { debugPlanInsertProject<<"before execute:"<resourceGroups()<resourceList(); QApplication::setOverrideCursor(Qt::WaitCursor); MacroCommand::execute(); QApplication::restoreOverrideCursor(); debugPlanInsertProject<<"after execute:"<resourceGroups()<resourceList(); } void InsertProjectCmd::unexecute() { QApplication::setOverrideCursor(Qt::WaitCursor); MacroCommand::unexecute(); QApplication::restoreOverrideCursor(); } WorkPackageAddCmd::WorkPackageAddCmd(Project *project, Node *node, WorkPackage *value, const KUndo2MagicString& name) : NamedCommand(name), m_project(project), m_node(node), m_wp(value), m_mine(true) { } WorkPackageAddCmd::~WorkPackageAddCmd() { if (m_mine) { delete m_wp; } } void WorkPackageAddCmd::execute() { // FIXME use project //m_project->addWorkPackage(m_node, m_wp); static_cast(m_node)->addWorkPackage(m_wp); } void WorkPackageAddCmd::unexecute() { // FIXME use project //m_project->removeWorkPackage(m_node, m_wp); static_cast(m_node)->removeWorkPackage(m_wp); } ModifyProjectLocaleCmd::ModifyProjectLocaleCmd(Project &project, const KUndo2MagicString& name) : MacroCommand(name), m_project(project) { }; void ModifyProjectLocaleCmd::execute() { MacroCommand::execute(); m_project.emitLocaleChanged(); } void ModifyProjectLocaleCmd::unexecute() { MacroCommand::unexecute(); m_project.emitLocaleChanged(); } ModifyCurrencySymolCmd::ModifyCurrencySymolCmd(Locale *locale, const QString &value, const KUndo2MagicString& name) : NamedCommand(name), m_locale(locale), m_newvalue(value), m_oldvalue(locale->currencySymbol()) { }; void ModifyCurrencySymolCmd::execute() { m_locale->setCurrencySymbol(m_newvalue); } void ModifyCurrencySymolCmd::unexecute() { m_locale->setCurrencySymbol(m_oldvalue); } ModifyCurrencyFractionalDigitsCmd::ModifyCurrencyFractionalDigitsCmd(Locale *locale, int value, const KUndo2MagicString& name) : NamedCommand(name), m_locale(locale), m_newvalue(value), m_oldvalue(locale->monetaryDecimalPlaces()) { }; void ModifyCurrencyFractionalDigitsCmd::execute() { m_locale->setMonetaryDecimalPlaces(m_newvalue); } void ModifyCurrencyFractionalDigitsCmd::unexecute() { m_locale->setMonetaryDecimalPlaces(m_oldvalue); } AddExternalAppointmentCmd::AddExternalAppointmentCmd(Resource *resource, const QString &pid, const QString &pname, const QDateTime &start, const QDateTime &end, double load, const KUndo2MagicString& name) : NamedCommand(name), m_resource(resource), m_pid(pid), m_pname(pname), m_start(start), m_end(end), m_load(load) { } void AddExternalAppointmentCmd::execute() { m_resource->addExternalAppointment(m_pid, m_pname, m_start, m_end, m_load); } void AddExternalAppointmentCmd::unexecute() { m_resource->subtractExternalAppointment(m_pid, m_start, m_end, m_load); // FIXME do this smarter if (! m_resource->externalAppointments(m_pid).isEmpty()) { m_resource->takeExternalAppointment(m_pid); } } ClearExternalAppointmentCmd::ClearExternalAppointmentCmd(Resource *resource, const QString &pid, const KUndo2MagicString &name) : NamedCommand(name), m_resource(resource), m_pid(pid), m_appointments(0) { } ClearExternalAppointmentCmd::~ClearExternalAppointmentCmd() { delete m_appointments; } void ClearExternalAppointmentCmd::execute() { // debugPlan<name()<takeExternalAppointment(m_pid); } void ClearExternalAppointmentCmd::unexecute() { // debugPlan<name()<addExternalAppointment(m_pid, m_appointments); } m_appointments = 0; } ClearAllExternalAppointmentsCmd::ClearAllExternalAppointmentsCmd(Project *project, const KUndo2MagicString &name) : NamedCommand(name), m_project(project) { foreach (Resource *r, project->resourceList()) { const QMap map = r->externalProjects(); QMap::const_iterator it; for (it = map.constBegin(); it != map.constEnd(); ++it) { m_cmd.addCommand(new ClearExternalAppointmentCmd(r, it.key())); } } } void ClearAllExternalAppointmentsCmd::execute() { m_cmd.redo(); } void ClearAllExternalAppointmentsCmd::unexecute() { m_cmd.undo(); } SharedResourcesFileCmd::SharedResourcesFileCmd(Project *project, const QString &newValue, const KUndo2MagicString& name) : NamedCommand(name) , m_project(project) , m_oldValue(project->sharedResourcesFile()) , m_newValue(newValue) { } void SharedResourcesFileCmd::execute() { m_project->setSharedResourcesFile(m_newValue); } void SharedResourcesFileCmd::unexecute() { m_project->setSharedResourcesFile(m_oldValue); } UseSharedResourcesCmd::UseSharedResourcesCmd(Project *project, bool newValue, const KUndo2MagicString& name) : NamedCommand(name) , m_project(project) , m_oldValue(project->useSharedResources()) , m_newValue(newValue) { } void UseSharedResourcesCmd::execute() { m_project->setUseSharedResources(m_newValue); } void UseSharedResourcesCmd::unexecute() { m_project->setUseSharedResources(m_oldValue); } SharedProjectsUrlCmd::SharedProjectsUrlCmd(Project *project, const QUrl &newValue, const KUndo2MagicString& name) : NamedCommand(name) , m_project(project) , m_oldValue(project->sharedProjectsUrl()) , m_newValue(newValue) { } void SharedProjectsUrlCmd::execute() { m_project->setSharedProjectsUrl(m_newValue); } void SharedProjectsUrlCmd::unexecute() { m_project->setSharedProjectsUrl(m_oldValue); } LoadProjectsAtStartupCmd::LoadProjectsAtStartupCmd(Project *project, bool newValue, const KUndo2MagicString& name) : NamedCommand(name) , m_project(project) , m_oldValue(project->loadProjectsAtStartup()) , m_newValue(newValue) { } void LoadProjectsAtStartupCmd::execute() { m_project->setLoadProjectsAtStartup(m_newValue); } void LoadProjectsAtStartupCmd::unexecute() { m_project->setLoadProjectsAtStartup(m_oldValue); } } //KPlato namespace diff --git a/src/libs/kernel/kptcommand.h b/src/libs/kernel/kptcommand.h index 17133bb1..bf0ca835 100644 --- a/src/libs/kernel/kptcommand.h +++ b/src/libs/kernel/kptcommand.h @@ -1,1973 +1,1976 @@ /* This file is part of the KDE project * Copyright (C) 2004-2007 Dag Andersen * Copyright (C) 2011 Dag Andersen * Copyright (C) 2016 Dag Andersen * Copyright (C) 2019 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 KPTCOMMAND_H #define KPTCOMMAND_H #include "plankernel_export.h" #include "NamedCommand.h" #include "MacroCommand.h" #include #include #include #include "kptappointment.h" #include "kptnode.h" #include "kptproject.h" #include "kptduration.h" #include "kpttask.h" #include "kptwbsdefinition.h" class QString; /** * @file * This file includes undo/redo commands for kernel data structures */ /// The main namespace namespace KPlato { class Locale; class Account; class Accounts; class Calendar; class CalendarDay; class Relation; class ResourceGroupRequest; class ResourceRequest; class ResourceGroup; class Resource; class Schedule; class StandardWorktime; class PLANKERNEL_EXPORT CalendarAddCmd : public NamedCommand { public: CalendarAddCmd(Project *project, Calendar *cal, int pos, Calendar *parent, const KUndo2MagicString& name = KUndo2MagicString()); ~CalendarAddCmd() override; void execute() override; void unexecute() override; private: Project *m_project; Calendar *m_cal; int m_pos; Calendar *m_parent; bool m_mine; }; class PLANKERNEL_EXPORT CalendarRemoveCmd : public NamedCommand { public: CalendarRemoveCmd(Project *project, Calendar *cal, const KUndo2MagicString& name = KUndo2MagicString()); ~CalendarRemoveCmd() override; void execute() override; void unexecute() override; private: Project *m_project; Calendar *m_parent; Calendar *m_cal; int m_index; bool m_mine; MacroCommand *m_cmd; }; class PLANKERNEL_EXPORT CalendarMoveCmd : public NamedCommand { public: CalendarMoveCmd(Project *project, Calendar *cal, int position, Calendar *parent, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Project *m_project; Calendar *m_cal; int m_newpos; int m_oldpos; Calendar *m_newparent; Calendar *m_oldparent; }; class PLANKERNEL_EXPORT CalendarModifyNameCmd : public NamedCommand { public: CalendarModifyNameCmd(Calendar *cal, const QString& newvalue, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Calendar *m_cal; QString m_newvalue; QString m_oldvalue; }; class PLANKERNEL_EXPORT CalendarModifyParentCmd : public NamedCommand { public: CalendarModifyParentCmd(Project *project, Calendar *cal, Calendar *newvalue, const KUndo2MagicString& name = KUndo2MagicString()); ~CalendarModifyParentCmd() override; void execute() override; void unexecute() override; private: Project *m_project; Calendar *m_cal; Calendar *m_newvalue; Calendar *m_oldvalue; MacroCommand *m_cmd; int m_oldindex; int m_newindex; }; class PLANKERNEL_EXPORT CalendarModifyTimeZoneCmd : public NamedCommand { public: CalendarModifyTimeZoneCmd(Calendar *cal, const QTimeZone &value, const KUndo2MagicString& name = KUndo2MagicString()); ~CalendarModifyTimeZoneCmd() override; void execute() override; void unexecute() override; private: Calendar *m_cal; QTimeZone m_newvalue; QTimeZone m_oldvalue; MacroCommand *m_cmd; }; #ifdef HAVE_KHOLIDAYS class PLANKERNEL_EXPORT CalendarModifyHolidayRegionCmd : public NamedCommand { public: CalendarModifyHolidayRegionCmd(Calendar *cal, const QString &value, const KUndo2MagicString& name = KUndo2MagicString()); ~CalendarModifyHolidayRegionCmd() override; void execute() override; void unexecute() override; private: Calendar *m_cal; QString m_newvalue; QString m_oldvalue; }; #endif class PLANKERNEL_EXPORT CalendarAddDayCmd : public NamedCommand { public: CalendarAddDayCmd(Calendar *cal, CalendarDay *newvalue, const KUndo2MagicString& name = KUndo2MagicString()); ~CalendarAddDayCmd() override; void execute() override; void unexecute() override; protected: Calendar *m_cal; CalendarDay *m_newvalue; bool m_mine; }; class PLANKERNEL_EXPORT CalendarRemoveDayCmd : public NamedCommand { public: CalendarRemoveDayCmd(Calendar *cal, CalendarDay *day, const KUndo2MagicString& name = KUndo2MagicString()); CalendarRemoveDayCmd(Calendar *cal, const QDate &day, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; protected: Calendar *m_cal; CalendarDay *m_value; bool m_mine; private: void init(); }; class PLANKERNEL_EXPORT CalendarModifyDayCmd : public NamedCommand { public: CalendarModifyDayCmd(Calendar *cal, CalendarDay *value, const KUndo2MagicString& name = KUndo2MagicString()); ~CalendarModifyDayCmd() override; void execute() override; void unexecute() override; private: Calendar *m_cal; CalendarDay *m_newvalue; CalendarDay *m_oldvalue; bool m_mine; }; class PLANKERNEL_EXPORT CalendarModifyStateCmd : public NamedCommand { public: CalendarModifyStateCmd(Calendar *calendar, CalendarDay *day, CalendarDay::State value, const KUndo2MagicString& name = KUndo2MagicString()); ~CalendarModifyStateCmd() override; void execute() override; void unexecute() override; private: Calendar *m_calendar; CalendarDay *m_day; CalendarDay::State m_newvalue; CalendarDay::State m_oldvalue; MacroCommand *m_cmd; }; class PLANKERNEL_EXPORT CalendarModifyTimeIntervalCmd : public NamedCommand { public: CalendarModifyTimeIntervalCmd(Calendar *calendar, TimeInterval &newvalue, TimeInterval *value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Calendar *m_calendar; TimeInterval *m_value; TimeInterval m_newvalue; TimeInterval m_oldvalue; }; class PLANKERNEL_EXPORT CalendarAddTimeIntervalCmd : public NamedCommand { public: CalendarAddTimeIntervalCmd(Calendar *calendar, CalendarDay *day, TimeInterval *value, const KUndo2MagicString& name = KUndo2MagicString()); ~CalendarAddTimeIntervalCmd() override; void execute() override; void unexecute() override; protected: Calendar *m_calendar; CalendarDay *m_day; TimeInterval *m_value; bool m_mine; }; class PLANKERNEL_EXPORT CalendarRemoveTimeIntervalCmd : public CalendarAddTimeIntervalCmd { public: CalendarRemoveTimeIntervalCmd(Calendar *calendar, CalendarDay *day, TimeInterval *value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; }; class PLANKERNEL_EXPORT CalendarModifyWeekdayCmd : public NamedCommand { public: CalendarModifyWeekdayCmd(Calendar *cal, int weekday, CalendarDay *value, const KUndo2MagicString& name = KUndo2MagicString()); ~CalendarModifyWeekdayCmd() override; void execute() override; void unexecute() override; private: int m_weekday; Calendar *m_cal; CalendarDay *m_value; CalendarDay m_orig; }; class PLANKERNEL_EXPORT CalendarModifyDateCmd : public NamedCommand { public: CalendarModifyDateCmd(Calendar *cal, CalendarDay *day, const QDate &value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Calendar *m_cal; CalendarDay *m_day; QDate m_newvalue, m_oldvalue; }; class PLANKERNEL_EXPORT ProjectModifyDefaultCalendarCmd : public NamedCommand { public: ProjectModifyDefaultCalendarCmd(Project *project, Calendar *cal, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Project *m_project; Calendar *m_newvalue, *m_oldvalue; }; class PLANKERNEL_EXPORT NodeDeleteCmd : public NamedCommand { public: explicit NodeDeleteCmd(Node *node, const KUndo2MagicString& name = KUndo2MagicString()); ~NodeDeleteCmd() override; void execute() override; void unexecute() override; private: Node *m_node; Node *m_parent; Project *m_project; int m_index; bool m_mine; QList m_appointments; MacroCommand *m_cmd; MacroCommand *m_relCmd; }; class PLANKERNEL_EXPORT TaskAddCmd : public NamedCommand { public: TaskAddCmd(Project *project, Node *node, Node *after, const KUndo2MagicString& name = KUndo2MagicString()); ~TaskAddCmd() override; void execute() override; void unexecute() override; private: Project *m_project; Node *m_node; Node *m_after; bool m_added; }; class PLANKERNEL_EXPORT SubtaskAddCmd : public NamedCommand { public: SubtaskAddCmd(Project *project, Node *node, Node *parent, const KUndo2MagicString& name = KUndo2MagicString()); ~SubtaskAddCmd() override; void execute() override; void unexecute() override; private: Project *m_project; Node *m_node; Node *m_parent; bool m_added; MacroCommand *m_cmd; }; class PLANKERNEL_EXPORT NodeModifyNameCmd : public NamedCommand { public: NodeModifyNameCmd(Node &node, const QString& nodename, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Node &m_node; QString newName; QString oldName; }; class PLANKERNEL_EXPORT NodeModifyPriorityCmd : public NamedCommand { public: NodeModifyPriorityCmd(Node &node, int oldValue, int newValue, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Node &m_node; int m_oldValue; int m_newValue; }; class PLANKERNEL_EXPORT NodeModifyLeaderCmd : public NamedCommand { public: NodeModifyLeaderCmd(Node &node, const QString& leader, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Node &m_node; QString newLeader; QString oldLeader; }; class PLANKERNEL_EXPORT NodeModifyDescriptionCmd : public NamedCommand { public: NodeModifyDescriptionCmd(Node &node, const QString& description, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Node &m_node; QString newDescription; QString oldDescription; }; class PLANKERNEL_EXPORT NodeModifyConstraintCmd : public NamedCommand { public: NodeModifyConstraintCmd(Node &node, Node::ConstraintType c, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Node &m_node; Node::ConstraintType newConstraint; Node::ConstraintType oldConstraint; }; class PLANKERNEL_EXPORT NodeModifyConstraintStartTimeCmd : public NamedCommand { public: NodeModifyConstraintStartTimeCmd(Node &node, const QDateTime& dt, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Node &m_node; QDateTime newTime; DateTime oldTime; QTimeZone m_timeZone; }; class PLANKERNEL_EXPORT NodeModifyConstraintEndTimeCmd : public NamedCommand { public: NodeModifyConstraintEndTimeCmd(Node &node, const QDateTime& dt, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Node &m_node; QDateTime newTime; DateTime oldTime; QTimeZone m_timeZone; }; class PLANKERNEL_EXPORT NodeModifyStartTimeCmd : public NamedCommand { public: NodeModifyStartTimeCmd(Node &node, const QDateTime& dt, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Node &m_node; QDateTime newTime; DateTime oldTime; QTimeZone m_timeZone; }; class PLANKERNEL_EXPORT NodeModifyEndTimeCmd : public NamedCommand { public: NodeModifyEndTimeCmd(Node &node, const QDateTime& dt, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Node &m_node; QDateTime newTime; DateTime oldTime; QTimeZone m_timeZone; }; class PLANKERNEL_EXPORT NodeModifyIdCmd : public NamedCommand { public: NodeModifyIdCmd(Node &node, const QString& id, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Node &m_node; QString newId; QString oldId; }; class PLANKERNEL_EXPORT NodeIndentCmd : public NamedCommand { public: explicit NodeIndentCmd(Node &node, const KUndo2MagicString& name = KUndo2MagicString()); ~NodeIndentCmd() override; void execute() override; void unexecute() override; private: Node &m_node; Node *m_oldparent, *m_newparent; int m_oldindex, m_newindex; MacroCommand *m_cmd; }; class PLANKERNEL_EXPORT NodeUnindentCmd : public NamedCommand { public: explicit NodeUnindentCmd(Node &node, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Node &m_node; Node *m_oldparent, *m_newparent; int m_oldindex, m_newindex; }; class PLANKERNEL_EXPORT NodeMoveUpCmd : public NamedCommand { public: explicit NodeMoveUpCmd(Node &node, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Node &m_node; Project *m_project; bool m_moved; }; class PLANKERNEL_EXPORT NodeMoveDownCmd : public NamedCommand { public: explicit NodeMoveDownCmd(Node &node, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Node &m_node; Project *m_project; bool m_moved; }; class PLANKERNEL_EXPORT NodeMoveCmd : public NamedCommand { public: NodeMoveCmd(Project *project, Node *node, Node *newParent, int newPos, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Project *m_project; Node *m_node; Node *m_newparent; Node *m_oldparent; int m_newpos; int m_oldpos; bool m_moved; MacroCommand m_cmd; }; class PLANKERNEL_EXPORT AddRelationCmd : public NamedCommand { public: AddRelationCmd(Project &project, Relation *rel, const KUndo2MagicString& name = KUndo2MagicString()); ~AddRelationCmd() override; void execute() override; void unexecute() override; private: Relation *m_rel; Project &m_project; bool m_taken; }; class PLANKERNEL_EXPORT DeleteRelationCmd : public NamedCommand { public: DeleteRelationCmd(Project &project, Relation *rel, const KUndo2MagicString& name = KUndo2MagicString()); ~DeleteRelationCmd() override; void execute() override; void unexecute() override; private: Relation *m_rel; Project &m_project; bool m_taken; }; class PLANKERNEL_EXPORT ModifyRelationTypeCmd : public NamedCommand { public: ModifyRelationTypeCmd(Relation *rel, Relation::Type type, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Project *m_project; Relation *m_rel; Relation::Type m_newtype; Relation::Type m_oldtype; }; class PLANKERNEL_EXPORT ModifyRelationLagCmd : public NamedCommand { public: ModifyRelationLagCmd(Relation *rel, Duration lag, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Project *m_project; Relation *m_rel; Duration m_newlag; Duration m_oldlag; }; class PLANKERNEL_EXPORT AddResourceRequestCmd : public NamedCommand { public: + AddResourceRequestCmd(ResourceRequestCollection *requests, ResourceRequest *request, ResourceGroupRequest *group = nullptr, const KUndo2MagicString& name = KUndo2MagicString()); AddResourceRequestCmd(ResourceGroupRequest *group, ResourceRequest *request, const KUndo2MagicString& name = KUndo2MagicString()); ~AddResourceRequestCmd() override; void execute() override; void unexecute() override; private: + ResourceRequestCollection *m_collection; ResourceGroupRequest *m_group; ResourceRequest *m_request; bool m_mine; }; class PLANKERNEL_EXPORT RemoveResourceRequestCmd : public NamedCommand { public: RemoveResourceRequestCmd(ResourceGroupRequest *group, ResourceRequest *request, const KUndo2MagicString& name = KUndo2MagicString()); ~RemoveResourceRequestCmd() override; void execute() override; void unexecute() override; private: + ResourceRequestCollection *m_collection; ResourceGroupRequest *m_group; ResourceRequest *m_request; bool m_mine; }; class PLANKERNEL_EXPORT ModifyResourceRequestUnitsCmd : public NamedCommand { public: ModifyResourceRequestUnitsCmd(ResourceRequest *request, int oldvalue, int newvalue, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: ResourceRequest *m_request; int m_oldvalue, m_newvalue; }; class PLANKERNEL_EXPORT ModifyResourceRequestRequiredCmd : public NamedCommand { public: ModifyResourceRequestRequiredCmd(ResourceRequest *request, const QList &value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: ResourceRequest *m_request; QList m_oldvalue, m_newvalue; }; class PLANKERNEL_EXPORT ModifyResourceGroupRequestUnitsCmd : public NamedCommand { public: ModifyResourceGroupRequestUnitsCmd(ResourceGroupRequest *request, int oldvalue, int newvalue, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: ResourceGroupRequest *m_request; int m_oldvalue, m_newvalue; }; class PLANKERNEL_EXPORT ModifyEstimateCmd : public NamedCommand { public: ModifyEstimateCmd(Node &node, double oldvalue, double newvalue, const KUndo2MagicString& name = KUndo2MagicString()); ~ModifyEstimateCmd() override; void execute() override; void unexecute() override; private: Estimate *m_estimate; double m_oldvalue, m_newvalue; int m_optimistic, m_pessimistic; MacroCommand *m_cmd; }; class PLANKERNEL_EXPORT EstimateModifyOptimisticRatioCmd : public NamedCommand { public: EstimateModifyOptimisticRatioCmd(Node &node, int oldvalue, int newvalue, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Estimate *m_estimate; int m_oldvalue, m_newvalue; }; class PLANKERNEL_EXPORT EstimateModifyPessimisticRatioCmd : public NamedCommand { public: EstimateModifyPessimisticRatioCmd(Node &node, int oldvalue, int newvalue, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Estimate *m_estimate; int m_oldvalue, m_newvalue; }; class PLANKERNEL_EXPORT ModifyEstimateTypeCmd : public NamedCommand { public: ModifyEstimateTypeCmd(Node &node, int oldvalue, int newvalue, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Estimate *m_estimate; int m_oldvalue, m_newvalue; }; class PLANKERNEL_EXPORT ModifyEstimateUnitCmd : public NamedCommand { public: ModifyEstimateUnitCmd(Node &node, Duration::Unit oldvalue, Duration::Unit newvalue, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Estimate *m_estimate; Duration::Unit m_oldvalue, m_newvalue; }; class PLANKERNEL_EXPORT EstimateModifyRiskCmd : public NamedCommand { public: EstimateModifyRiskCmd(Node &node, int oldvalue, int newvalue, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Estimate *m_estimate; int m_oldvalue, m_newvalue; }; class PLANKERNEL_EXPORT ModifyEstimateCalendarCmd : public NamedCommand { public: ModifyEstimateCalendarCmd(Node &node, Calendar *oldvalue, Calendar *newvalue, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Estimate *m_estimate; Calendar *m_oldvalue, *m_newvalue; }; class PLANKERNEL_EXPORT AddResourceGroupRequestCmd : public NamedCommand { public: AddResourceGroupRequestCmd(Task &task, ResourceGroupRequest *request, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Task &m_task; ResourceGroupRequest *m_request; bool m_mine; }; class PLANKERNEL_EXPORT RemoveResourceGroupRequestCmd : public NamedCommand { public: explicit RemoveResourceGroupRequestCmd(ResourceGroupRequest *request, const KUndo2MagicString& name = KUndo2MagicString()); RemoveResourceGroupRequestCmd(Task &task, ResourceGroupRequest *request, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Task &m_task; ResourceGroupRequest *m_request; bool m_mine; }; class PLANKERNEL_EXPORT MoveResourceCmd : public NamedCommand { public: MoveResourceCmd(ResourceGroup *group, Resource *resource, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Project &m_project; Resource *m_resource; ResourceGroup *m_oldvalue, *m_newvalue; MacroCommand cmd; }; class PLANKERNEL_EXPORT ModifyResourceNameCmd : public NamedCommand { public: ModifyResourceNameCmd(Resource *resource, const QString& value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Resource *m_resource; QString m_newvalue; QString m_oldvalue; }; class PLANKERNEL_EXPORT ModifyResourceInitialsCmd : public NamedCommand { public: ModifyResourceInitialsCmd(Resource *resource, const QString& value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Resource *m_resource; QString m_newvalue; QString m_oldvalue; }; class PLANKERNEL_EXPORT ModifyResourceEmailCmd : public NamedCommand { public: ModifyResourceEmailCmd(Resource *resource, const QString& value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Resource *m_resource; QString m_newvalue; QString m_oldvalue; }; class PLANKERNEL_EXPORT ModifyResourceAutoAllocateCmd : public NamedCommand { public: ModifyResourceAutoAllocateCmd(Resource *resource, bool value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Resource *m_resource; bool m_newvalue; bool m_oldvalue; }; class PLANKERNEL_EXPORT ModifyResourceTypeCmd : public NamedCommand { public: ModifyResourceTypeCmd(Resource *resource, int value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Resource *m_resource; int m_newvalue; int m_oldvalue; }; class PLANKERNEL_EXPORT ModifyResourceUnitsCmd : public NamedCommand { public: ModifyResourceUnitsCmd(Resource *resource, int value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Resource *m_resource; int m_newvalue; int m_oldvalue; }; class PLANKERNEL_EXPORT ModifyResourceAvailableFromCmd : public NamedCommand { public: ModifyResourceAvailableFromCmd(Resource *resource, const QDateTime& value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Resource *m_resource; QDateTime m_newvalue; DateTime m_oldvalue; QTimeZone m_timeZone; }; class PLANKERNEL_EXPORT ModifyResourceAvailableUntilCmd : public NamedCommand { public: ModifyResourceAvailableUntilCmd(Resource *resource, const QDateTime& value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Resource *m_resource; QDateTime m_newvalue; DateTime m_oldvalue; QTimeZone m_timeZone; }; class PLANKERNEL_EXPORT ModifyResourceNormalRateCmd : public NamedCommand { public: ModifyResourceNormalRateCmd(Resource *resource, double value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Resource *m_resource; double m_newvalue; double m_oldvalue; }; class PLANKERNEL_EXPORT ModifyResourceOvertimeRateCmd : public NamedCommand { public: ModifyResourceOvertimeRateCmd(Resource *resource, double value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Resource *m_resource; double m_newvalue; double m_oldvalue; }; class PLANKERNEL_EXPORT ModifyResourceCalendarCmd : public NamedCommand { public: ModifyResourceCalendarCmd(Resource *resource, Calendar *value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Resource *m_resource; Calendar *m_newvalue; Calendar *m_oldvalue; }; class PLANKERNEL_EXPORT ModifyRequiredResourcesCmd : public NamedCommand { public: ModifyRequiredResourcesCmd(Resource *resource, const QStringList &value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Resource *m_resource; QStringList m_newvalue; QStringList m_oldvalue; }; class PLANKERNEL_EXPORT ModifyResourceAccountCmd : public NamedCommand { public: ModifyResourceAccountCmd(Resource *resource, Account *account, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Resource *m_resource; Account *m_newvalue; Account *m_oldvalue; }; class PLANKERNEL_EXPORT AddResourceTeamCmd : public NamedCommand { public: AddResourceTeamCmd(Resource *team, const QString &member, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Resource *m_team; QString m_member; }; class PLANKERNEL_EXPORT RemoveResourceTeamCmd : public NamedCommand { public: RemoveResourceTeamCmd(Resource *team, const QString &member, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Resource *m_team; QString m_member; }; class PLANKERNEL_EXPORT RemoveResourceGroupCmd : public NamedCommand { public: RemoveResourceGroupCmd(Project *project, ResourceGroup *group, const KUndo2MagicString& name = KUndo2MagicString()); ~RemoveResourceGroupCmd() override; void execute() override; void unexecute() override; protected: ResourceGroup *m_group; Project *m_project; int m_index; bool m_mine; MacroCommand *m_cmd; }; class PLANKERNEL_EXPORT AddResourceGroupCmd : public RemoveResourceGroupCmd { public: AddResourceGroupCmd(Project *project, ResourceGroup *group, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; }; class PLANKERNEL_EXPORT ModifyResourceGroupNameCmd : public NamedCommand { public: ModifyResourceGroupNameCmd(ResourceGroup *group, const QString& value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: ResourceGroup *m_group; QString m_newvalue; QString m_oldvalue; }; class PLANKERNEL_EXPORT ModifyResourceGroupTypeCmd : public NamedCommand { public: ModifyResourceGroupTypeCmd(ResourceGroup *group, int value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: ResourceGroup *m_group; int m_newvalue; int m_oldvalue; }; class PLANKERNEL_EXPORT ModifyCompletionEntrymodeCmd : public NamedCommand { public: ModifyCompletionEntrymodeCmd(Completion &completion, Completion::Entrymode value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Completion &m_completion; Completion::Entrymode oldvalue; Completion::Entrymode newvalue; }; class PLANKERNEL_EXPORT ModifyCompletionStartedCmd : public NamedCommand { public: ModifyCompletionStartedCmd(Completion &completion, bool value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Completion &m_completion; bool oldvalue; bool newvalue; }; class PLANKERNEL_EXPORT ModifyCompletionFinishedCmd : public NamedCommand { public: ModifyCompletionFinishedCmd(Completion &completion, bool value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Completion &m_completion; bool oldvalue; bool newvalue; }; class PLANKERNEL_EXPORT ModifyCompletionStartTimeCmd : public NamedCommand { public: ModifyCompletionStartTimeCmd(Completion &completion, const QDateTime &value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Completion &m_completion; DateTime oldvalue; QDateTime newvalue; QTimeZone m_timeZone; }; class PLANKERNEL_EXPORT ModifyCompletionFinishTimeCmd : public NamedCommand { public: ModifyCompletionFinishTimeCmd(Completion &completion, const QDateTime &value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Completion &m_completion; DateTime oldvalue; QDateTime newvalue; QTimeZone m_timeZone; }; class PLANKERNEL_EXPORT AddCompletionEntryCmd : public NamedCommand { public: AddCompletionEntryCmd(Completion &completion, const QDate &date, Completion::Entry *value, const KUndo2MagicString& name = KUndo2MagicString()); ~AddCompletionEntryCmd() override; void execute() override; void unexecute() override; private: Completion &m_completion; QDate m_date; Completion::Entry *newvalue; bool m_newmine; }; class PLANKERNEL_EXPORT RemoveCompletionEntryCmd : public NamedCommand { public: RemoveCompletionEntryCmd(Completion &completion, const QDate& date, const KUndo2MagicString& name = KUndo2MagicString()); ~RemoveCompletionEntryCmd() override; void execute() override; void unexecute() override; private: Completion &m_completion; QDate m_date; Completion::Entry *value; bool m_mine; }; class PLANKERNEL_EXPORT ModifyCompletionEntryCmd : public NamedCommand { public: ModifyCompletionEntryCmd(Completion &completion, const QDate &date, Completion::Entry *value, const KUndo2MagicString& name = KUndo2MagicString()); ~ModifyCompletionEntryCmd() override; void execute() override; void unexecute() override; private: MacroCommand *cmd; }; class PLANKERNEL_EXPORT ModifyCompletionPercentFinishedCmd : public NamedCommand { public: ModifyCompletionPercentFinishedCmd(Completion &completion, const QDate &date, int value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Completion &m_completion; QDate m_date; int m_newvalue, m_oldvalue; MacroCommand cmd; }; class PLANKERNEL_EXPORT ModifyCompletionRemainingEffortCmd : public NamedCommand { public: ModifyCompletionRemainingEffortCmd(Completion &completion, const QDate &date, const Duration &value, const KUndo2MagicString &name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Completion &m_completion; QDate m_date; Duration m_newvalue, m_oldvalue; MacroCommand cmd; }; class PLANKERNEL_EXPORT ModifyCompletionActualEffortCmd : public NamedCommand { public: ModifyCompletionActualEffortCmd(Completion &completion, const QDate &date, const Duration &value, const KUndo2MagicString &name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Completion &m_completion; QDate m_date; Duration m_newvalue, m_oldvalue; MacroCommand cmd; }; /** * Add used effort for @p resource. * Note that the used effort definition in @p value must contain entries for *all* dates. * If used effort is already defined it will be replaced. */ class PLANKERNEL_EXPORT AddCompletionUsedEffortCmd : public NamedCommand { public: AddCompletionUsedEffortCmd(Completion &completion, const Resource *resource, Completion::UsedEffort *value, const KUndo2MagicString& name = KUndo2MagicString()); ~AddCompletionUsedEffortCmd() override; void execute() override; void unexecute() override; private: Completion &m_completion; const Resource *m_resource; Completion::UsedEffort *oldvalue; Completion::UsedEffort *newvalue; bool m_newmine, m_oldmine; }; class PLANKERNEL_EXPORT AddCompletionActualEffortCmd : public NamedCommand { public: AddCompletionActualEffortCmd(Task *task, Resource *resource, const QDate &date, const Completion::UsedEffort::ActualEffort &value, const KUndo2MagicString& name = KUndo2MagicString()); ~AddCompletionActualEffortCmd() override; void execute() override; void unexecute() override; private: Task *m_task; Resource *m_resource; QDate m_date; Completion::UsedEffort::ActualEffort oldvalue; Completion::UsedEffort::ActualEffort newvalue; }; class PLANKERNEL_EXPORT AddAccountCmd : public NamedCommand { public: AddAccountCmd(Project &project, Account *account, Account *parent = 0, int index = -1, const KUndo2MagicString& name = KUndo2MagicString()); AddAccountCmd(Project &project, Account *account, const QString& parent, int index = -1, const KUndo2MagicString& name = KUndo2MagicString()); ~AddAccountCmd() override; void execute() override; void unexecute() override; protected: bool m_mine; private: Project &m_project; Account *m_account; Account *m_parent; int m_index; QString m_parentName; }; class PLANKERNEL_EXPORT RemoveAccountCmd : public NamedCommand { public: RemoveAccountCmd(Project &project, Account *account, const KUndo2MagicString& name = KUndo2MagicString()); ~RemoveAccountCmd() override; void execute() override; void unexecute() override; private: Project &m_project; Account *m_account; Account *m_parent; int m_index; bool m_isDefault; bool m_mine; MacroCommand m_cmd; }; class PLANKERNEL_EXPORT RenameAccountCmd : public NamedCommand { public: RenameAccountCmd(Account *account, const QString& value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Account *m_account; QString m_oldvalue; QString m_newvalue; }; class PLANKERNEL_EXPORT ModifyAccountDescriptionCmd : public NamedCommand { public: ModifyAccountDescriptionCmd(Account *account, const QString& value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Account *m_account; QString m_oldvalue; QString m_newvalue; }; class PLANKERNEL_EXPORT NodeModifyStartupCostCmd : public NamedCommand { public: NodeModifyStartupCostCmd(Node &node, double value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Node &m_node; double m_oldvalue; double m_newvalue; }; class PLANKERNEL_EXPORT NodeModifyShutdownCostCmd : public NamedCommand { public: NodeModifyShutdownCostCmd(Node &node, double value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Node &m_node; double m_oldvalue; double m_newvalue; }; class PLANKERNEL_EXPORT NodeModifyRunningAccountCmd : public NamedCommand { public: NodeModifyRunningAccountCmd(Node &node, Account *oldvalue, Account *newvalue, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Node &m_node; Account *m_oldvalue; Account *m_newvalue; }; class PLANKERNEL_EXPORT NodeModifyStartupAccountCmd : public NamedCommand { public: NodeModifyStartupAccountCmd(Node &node, Account *oldvalue, Account *newvalue, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Node &m_node; Account *m_oldvalue; Account *m_newvalue; }; class PLANKERNEL_EXPORT NodeModifyShutdownAccountCmd : public NamedCommand { public: NodeModifyShutdownAccountCmd(Node &node, Account *oldvalue, Account *newvalue, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Node &m_node; Account *m_oldvalue; Account *m_newvalue; }; class PLANKERNEL_EXPORT ModifyDefaultAccountCmd : public NamedCommand { public: ModifyDefaultAccountCmd(Accounts &acc, Account *oldvalue, Account *newvalue, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Accounts &m_accounts; Account *m_oldvalue; Account *m_newvalue; }; class PLANKERNEL_EXPORT ResourceModifyAccountCmd : public NamedCommand { public: ResourceModifyAccountCmd(Resource &resource, Account *oldvalue, Account *newvalue, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Resource &m_resource; Account *m_oldvalue; Account *m_newvalue; }; class PLANKERNEL_EXPORT ProjectModifyConstraintCmd : public NamedCommand { public: ProjectModifyConstraintCmd(Project &node, Node::ConstraintType c, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Project &m_node; Node::ConstraintType newConstraint; Node::ConstraintType oldConstraint; }; class PLANKERNEL_EXPORT ProjectModifyStartTimeCmd : public NamedCommand { public: ProjectModifyStartTimeCmd(Project &node, const QDateTime& dt, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Project &m_node; QDateTime newTime; DateTime oldTime; QTimeZone m_timeZone; }; class PLANKERNEL_EXPORT ProjectModifyEndTimeCmd : public NamedCommand { public: ProjectModifyEndTimeCmd(Project &project, const QDateTime& dt, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Project &m_node; QDateTime newTime; DateTime oldTime; QTimeZone m_timeZone; }; class PLANKERNEL_EXPORT ProjectModifyWorkPackageInfoCmd : public NamedCommand { public: ProjectModifyWorkPackageInfoCmd(Project &project, const Project::WorkPackageInfo &wpi, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Project &m_node; Project::WorkPackageInfo m_newWpi; Project::WorkPackageInfo m_oldWpi; }; class PLANKERNEL_EXPORT SwapScheduleManagerCmd : public NamedCommand { public: SwapScheduleManagerCmd(Project &project, ScheduleManager *from, ScheduleManager *to, const KUndo2MagicString& name = KUndo2MagicString()); ~SwapScheduleManagerCmd() override; void execute() override; void unexecute() override; protected: Project &m_node; ScheduleManager *m_from; ScheduleManager *m_to; }; class PLANKERNEL_EXPORT AddScheduleManagerCmd : public NamedCommand { public: AddScheduleManagerCmd(Project &project, ScheduleManager *sm, int index = -1, const KUndo2MagicString& name = KUndo2MagicString()); AddScheduleManagerCmd(ScheduleManager *parent, ScheduleManager *sm, int index = -1, const KUndo2MagicString& name = KUndo2MagicString()); ~AddScheduleManagerCmd() override; void execute() override; void unexecute() override; protected: Project &m_node; ScheduleManager *m_parent; ScheduleManager *m_sm; int m_index; MainSchedule *m_exp; bool m_mine; MacroCommand m_cmd; }; class PLANKERNEL_EXPORT DeleteScheduleManagerCmd : public AddScheduleManagerCmd { public: DeleteScheduleManagerCmd(Project &project, ScheduleManager *sm, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: MacroCommand cmd; }; class PLANKERNEL_EXPORT MoveScheduleManagerCmd : public NamedCommand { public: MoveScheduleManagerCmd(ScheduleManager *sm, ScheduleManager *newparent, int newindex, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: ScheduleManager *m_sm; ScheduleManager *m_oldparent; int m_oldindex; ScheduleManager *m_newparent; int m_newindex; MacroCommand m_cmd; }; class PLANKERNEL_EXPORT ModifyScheduleManagerNameCmd : public NamedCommand { public: ModifyScheduleManagerNameCmd(ScheduleManager &sm, const QString& value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: ScheduleManager &m_sm; QString oldvalue, newvalue; }; class PLANKERNEL_EXPORT ModifyScheduleManagerSchedulingModeCmd : public NamedCommand { public: ModifyScheduleManagerSchedulingModeCmd(ScheduleManager &sm, int value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: ScheduleManager &m_sm; int oldvalue, newvalue; MacroCommand m_cmd; }; class PLANKERNEL_EXPORT ModifyScheduleManagerAllowOverbookingCmd : public NamedCommand { public: ModifyScheduleManagerAllowOverbookingCmd(ScheduleManager &sm, bool value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: ScheduleManager &m_sm; bool oldvalue, newvalue; }; class PLANKERNEL_EXPORT ModifyScheduleManagerDistributionCmd : public NamedCommand { public: ModifyScheduleManagerDistributionCmd(ScheduleManager &sm, bool value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: ScheduleManager &m_sm; bool oldvalue, newvalue; }; class PLANKERNEL_EXPORT CalculateScheduleCmd : public NamedCommand { public: CalculateScheduleCmd(Project &project, ScheduleManager *sm, const KUndo2MagicString& name = KUndo2MagicString()); ~CalculateScheduleCmd() override; void execute() override; void unexecute() override; private: Project &m_node; QPointer m_sm; bool m_first; MainSchedule *m_oldexpected; MainSchedule *m_newexpected; MacroCommand preCmd; MacroCommand postCmd; }; class PLANKERNEL_EXPORT BaselineScheduleCmd : public NamedCommand { public: explicit BaselineScheduleCmd(ScheduleManager &sm, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: ScheduleManager &m_sm; }; class PLANKERNEL_EXPORT ResetBaselineScheduleCmd : public NamedCommand { public: explicit ResetBaselineScheduleCmd(ScheduleManager &sm, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: ScheduleManager &m_sm; }; class PLANKERNEL_EXPORT ModifyScheduleManagerSchedulingDirectionCmd : public NamedCommand { public: ModifyScheduleManagerSchedulingDirectionCmd(ScheduleManager &sm, bool value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: ScheduleManager &m_sm; bool oldvalue, newvalue; }; class PLANKERNEL_EXPORT ModifyScheduleManagerSchedulerCmd : public NamedCommand { public: ModifyScheduleManagerSchedulerCmd(ScheduleManager &sm, int value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: ScheduleManager &m_sm; int oldvalue, newvalue; }; class PLANKERNEL_EXPORT ModifyScheduleManagerSchedulingGranularityCmd : public NamedCommand { public: ModifyScheduleManagerSchedulingGranularityCmd(ScheduleManager &sm, int value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: ScheduleManager &m_sm; int oldvalue, newvalue; }; class PLANKERNEL_EXPORT ModifyStandardWorktimeYearCmd : public NamedCommand { public: ModifyStandardWorktimeYearCmd(StandardWorktime *wt, double oldvalue, double newvalue, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: StandardWorktime *swt; double m_oldvalue; double m_newvalue; }; class PLANKERNEL_EXPORT ModifyStandardWorktimeMonthCmd : public NamedCommand { public: ModifyStandardWorktimeMonthCmd(StandardWorktime *wt, double oldvalue, double newvalue, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: StandardWorktime *swt; double m_oldvalue; double m_newvalue; }; class PLANKERNEL_EXPORT ModifyStandardWorktimeWeekCmd : public NamedCommand { public: ModifyStandardWorktimeWeekCmd(StandardWorktime *wt, double oldvalue, double newvalue, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: StandardWorktime *swt; double m_oldvalue; double m_newvalue; }; class PLANKERNEL_EXPORT ModifyStandardWorktimeDayCmd : public NamedCommand { public: ModifyStandardWorktimeDayCmd(StandardWorktime *wt, double oldvalue, double newvalue, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: StandardWorktime *swt; double m_oldvalue; double m_newvalue; }; class PLANKERNEL_EXPORT DocumentAddCmd : public NamedCommand { public: DocumentAddCmd(Documents& docs, Document *value, const KUndo2MagicString& name = KUndo2MagicString()); ~DocumentAddCmd() override; void execute() override; void unexecute() override; private: Documents& m_docs; Document *m_value; bool m_mine; }; class PLANKERNEL_EXPORT DocumentRemoveCmd : public NamedCommand { public: DocumentRemoveCmd(Documents& docs, Document *value, const KUndo2MagicString& name = KUndo2MagicString()); ~DocumentRemoveCmd() override; void execute() override; void unexecute() override; private: Documents& m_docs; Document *m_value; bool m_mine; }; class PLANKERNEL_EXPORT DocumentModifyUrlCmd : public NamedCommand { public: DocumentModifyUrlCmd(Document *doc, const QUrl &url, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Document *m_doc; QUrl m_value; QUrl m_oldvalue; }; class PLANKERNEL_EXPORT DocumentModifyNameCmd : public NamedCommand { public: DocumentModifyNameCmd(Document *doc, const QString &value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Document *m_doc; QString m_value; QString m_oldvalue; }; class PLANKERNEL_EXPORT DocumentModifyTypeCmd : public NamedCommand { public: DocumentModifyTypeCmd(Document *doc, Document::Type value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Document *m_doc; Document::Type m_value; Document::Type m_oldvalue; }; class PLANKERNEL_EXPORT DocumentModifyStatusCmd : public NamedCommand { public: DocumentModifyStatusCmd(Document *doc, const QString &value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Document *m_doc; QString m_value; QString m_oldvalue; }; class PLANKERNEL_EXPORT DocumentModifySendAsCmd : public NamedCommand { public: DocumentModifySendAsCmd(Document *doc, const Document::SendAs value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Document *m_doc; Document::SendAs m_value; Document::SendAs m_oldvalue; }; class PLANKERNEL_EXPORT WBSDefinitionModifyCmd : public NamedCommand { public: WBSDefinitionModifyCmd(Project &project, const WBSDefinition value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Project &m_project; WBSDefinition m_newvalue, m_oldvalue; }; class PLANKERNEL_EXPORT InsertProjectCmd : public MacroCommand { public: InsertProjectCmd(Project &project, Node *parent, Node *after, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; protected: void addAccounts(Account *account, Account *parent, QList &unused, QMap &all); void addCalendars(Calendar *calendar, Calendar *parent, QList &unused, QMap &all); void addChildNodes(Node *node); private: Project *m_project; Node *m_parent; Node *m_after; }; class PLANKERNEL_EXPORT WorkPackageAddCmd : public NamedCommand { public: WorkPackageAddCmd(Project *project, Node *node, WorkPackage *wp, const KUndo2MagicString& name = KUndo2MagicString()); ~WorkPackageAddCmd() override; void execute() override; void unexecute() override; private: Project *m_project; Node *m_node; WorkPackage *m_wp; bool m_mine; }; class PLANKERNEL_EXPORT ModifyProjectLocaleCmd : public MacroCommand { public: ModifyProjectLocaleCmd(Project &project, const KUndo2MagicString &name); void execute() override; void unexecute() override; private: Project &m_project; }; class PLANKERNEL_EXPORT ModifyCurrencySymolCmd : public NamedCommand { public: ModifyCurrencySymolCmd(Locale *locale, const QString &value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Locale *m_locale; QString m_newvalue; QString m_oldvalue; }; class PLANKERNEL_EXPORT ModifyCurrencyFractionalDigitsCmd : public NamedCommand { public: ModifyCurrencyFractionalDigitsCmd(Locale *locale, int value, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Locale *m_locale; int m_newvalue; int m_oldvalue; }; class PLANKERNEL_EXPORT AddExternalAppointmentCmd : public NamedCommand { public: AddExternalAppointmentCmd(Resource *resource, const QString &pid, const QString &pname, const QDateTime &start, const QDateTime &end, double load, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Resource *m_resource; QString m_pid; QString m_pname; QDateTime m_start; QDateTime m_end; double m_load; }; class PLANKERNEL_EXPORT ClearExternalAppointmentCmd : public NamedCommand { public: ClearExternalAppointmentCmd(Resource *resource, const QString &pid, const KUndo2MagicString& name = KUndo2MagicString()); ~ClearExternalAppointmentCmd() override; void execute() override; void unexecute() override; private: Resource *m_resource; QString m_pid; Appointment *m_appointments; }; class PLANKERNEL_EXPORT ClearAllExternalAppointmentsCmd : public NamedCommand { public: explicit ClearAllExternalAppointmentsCmd(Project *project, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Project *m_project; MacroCommand m_cmd; }; class PLANKERNEL_EXPORT SharedResourcesFileCmd : public NamedCommand { public: explicit SharedResourcesFileCmd(Project *project, const QString &newValue, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Project *m_project; QString m_oldValue; QString m_newValue; }; class PLANKERNEL_EXPORT UseSharedResourcesCmd : public NamedCommand { public: explicit UseSharedResourcesCmd(Project *project, bool newValue, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Project *m_project; bool m_oldValue; bool m_newValue; }; class PLANKERNEL_EXPORT SharedProjectsUrlCmd : public NamedCommand { public: explicit SharedProjectsUrlCmd(Project *project, const QUrl &newValue, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Project *m_project; QUrl m_oldValue; QUrl m_newValue; }; class PLANKERNEL_EXPORT LoadProjectsAtStartupCmd : public NamedCommand { public: explicit LoadProjectsAtStartupCmd(Project *project, bool newValue, const KUndo2MagicString& name = KUndo2MagicString()); void execute() override; void unexecute() override; private: Project *m_project; bool m_oldValue; bool m_newValue; }; } //KPlato namespace #endif //COMMAND_H diff --git a/src/libs/kernel/kptnode.cpp b/src/libs/kernel/kptnode.cpp index 45efdfd1..0e921b0c 100644 --- a/src/libs/kernel/kptnode.cpp +++ b/src/libs/kernel/kptnode.cpp @@ -1,1901 +1,1901 @@ /* This file is part of the KDE project Copyright (C) 2001 Thomas zander Copyright (C) 2002 - 2010, 2012 Dag Andersen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ // clazy:excludeall=qstring-arg #include "kptnode.h" #include "kptappointment.h" #include "kptaccount.h" #include "kptwbsdefinition.h" #include "kptresource.h" #include "kptschedule.h" #include "kptxmlloaderobject.h" #include "XmlSaveContext.h" #include "kptdebug.h" #include #include #include namespace KPlato { Node::Node(Node *parent) : QObject(0), // We don't use qobjects parent m_nodes(), m_dependChildNodes(), m_dependParentNodes(), m_estimate(0), m_blockChanged(false) { //debugPlan<<"("<removeRunning(*this); if (m_startupAccount) m_startupAccount->removeStartup(*this); if (m_shutdownAccount) m_shutdownAccount->removeShutdown(*this); foreach (Schedule *s, m_schedules) { delete s; } m_schedules.clear(); m_parent = 0; //safety } void Node::init() { m_priority = 0; m_documents.node = this; m_currentSchedule = 0; m_name=""; m_constraint = Node::ASAP; m_estimate = 0; m_visitedForward = false; m_visitedBackward = false; m_runningAccount = 0; m_startupAccount = 0; m_shutdownAccount = 0; m_startupCost = 0.0; m_shutdownCost = 0.0; } void Node::setPriority(int priority) { m_priority = priority; changed(PriorityProperty); } int Node::priority() const { return m_priority; } QString Node::typeToString(bool trans) const { return typeToString((Node::NodeTypes)type(), trans); } // static QString Node::typeToString(Node::NodeTypes typ, bool trans) { return typeToStringList(trans).at(typ); } // static QStringList Node::typeToStringList(bool trans) { return QStringList() << (trans ? i18n("None") : QString("None")) << (trans ? i18n("Project") : QString("Project")) << (trans ? i18n("Sub-Project") : QString("Sub-Project")) << (trans ? i18n("Task") : QString("Task")) << (trans ? i18n("Milestone") : QString("Milestone")) << (trans ? i18n("Periodic") : QString("Periodic")) << (trans ? i18n("Summary") : QString("Summary-Task")); } void Node::setName(const QString &n) { #ifndef NDEBUG setObjectName(n); #endif m_name = n; changed(this, NameProperty); } void Node::setLeader(const QString &l) { m_leader = l; changed(this, LeaderProperty); } void Node::setDescription(const QString &d) { m_description = d; changed(this, DescriptionProperty); } Node *Node::projectNode() { if ((type() == Type_Project) || (type() == Type_Subproject)) { return this; } if (m_parent) return m_parent->projectNode(); // This happens for default tasks return 0; } const Node *Node::projectNode() const { if ((type() == Type_Project) || (type() == Type_Subproject)) { return this; } if (m_parent) return m_parent->projectNode(); // This happens for default tasks return 0; } void Node::takeChildNode(Node *node) { //debugPlan<<"find="<setParentNode(0); if (t != type()) { changed(TypeProperty); } } void Node::takeChildNode(int number) { int t = type(); if (number >= 0 && number < m_nodes.size()) { Node *n = m_nodes.takeAt(number); //debugPlan<<(n?n->id():"null")<<" :"<<(n?n->name():""); if (n) { n->setParentNode(0); } } if (t != type()) { changed(TypeProperty); } } void Node::insertChildNode(int index, Node *node) { int t = type(); if (index == -1) m_nodes.append(node); else m_nodes.insert(index,node); node->setParentNode(this); if (t != type()) { changed(TypeProperty); } } void Node::addChildNode(Node *node, Node *after) { int t = type(); int index = m_nodes.indexOf(after); if (index == -1) { m_nodes.append(node); node->setParentNode(this); if (t != type()) { changed(TypeProperty); } return; } m_nodes.insert(index+1, node); node->setParentNode(this); if (t != type()) { changed(TypeProperty); } } int Node::findChildNode(const Node* node) const { return m_nodes.indexOf(const_cast(node)); } bool Node::isChildOf(const Node* node) const { if (node == 0 || m_parent == 0) { return false; } if (node == m_parent) { return true; } return m_parent->isChildOf(node); } Node* Node::childNode(int number) { //debugPlan<= m_nodes.count()) { return 0; } return m_nodes.at(number); } int Node::indexOf(const Node *node) const { return m_nodes.indexOf(const_cast(node)); } Duration *Node::getDelay() { /* TODO Calculate the delay of this node. Use the calculated startTime and the set startTime. */ return 0L; } void Node::addDependChildNode(Node *node, Relation::Type p) { addDependChildNode(node,p,Duration()); } void Node::addDependChildNode(Node *node, Relation::Type p, Duration lag) { Relation *relation = new Relation(this, node, p, lag); if (node->addDependParentNode(relation)) m_dependChildNodes.append(relation); else delete relation; } void Node::insertDependChildNode(unsigned int index, Node *node, Relation::Type p) { Relation *relation = new Relation(this, node, p, Duration()); if (node->addDependParentNode(relation)) m_dependChildNodes.insert(index, relation); else delete relation; } bool Node::addDependChildNode(Relation *relation) { if(m_dependChildNodes.indexOf(relation) != -1) return false; m_dependChildNodes.append(relation); return true; } void Node::takeDependChildNode(Relation *rel) { int i = m_dependChildNodes.indexOf(rel); if (i != -1) { //debugPlan<addDependChildNode(relation)) m_dependParentNodes.append(relation); else delete relation; } void Node::insertDependParentNode(unsigned int index, Node *node, Relation::Type p) { Relation *relation = new Relation(this, node, p, Duration()); if (node->addDependChildNode(relation)) m_dependParentNodes.insert(index,relation); else delete relation; } bool Node::addDependParentNode(Relation *relation) { if(m_dependParentNodes.indexOf(relation) != -1) return false; m_dependParentNodes.append(relation); return true; } void Node::takeDependParentNode(Relation *rel) { int i = m_dependParentNodes.indexOf(rel); if (i != -1) { //debugPlan<(node)) != -1) return true; QListIterator nit(childNodeIterator()); while (nit.hasNext()) { if (nit.next()->isParentOf(node)) return true; } return false; } Relation *Node::findParentRelation(const Node *node) const { for (int i=0; iparent() == node) return rel; } return (Relation *)0; } Relation *Node::findChildRelation(const Node *node) const { for (int i=0; ichild() == node) return rel; } return (Relation *)0; } Relation *Node::findRelation(const Node *node) const { Relation *rel = findParentRelation(node); if (!rel) rel = findChildRelation(node); return rel; } bool Node::isDependChildOf(const Node *node) const { //debugPlan<<" '"<name()<<"'"; for (int i=0; iparent() == node) return true; if (rel->parent()->isDependChildOf(node)) return true; } return false; } QList Node::getParentNodes() { this->m_parentNodes.clear(); foreach(Relation * currentRelation, this->dependParentNodes()) { if (!this->m_parentNodes.contains(currentRelation->parent())) { this->m_parentNodes.append(currentRelation->parent()); } } return this->m_parentNodes; } bool Node::canMoveTo(const Node *newParent) const { if (m_parent == newParent) { return true; } if (newParent->isChildOf(this)) { return false; } if (isDependChildOf(newParent) || newParent->isDependChildOf(this)) { debugPlan<<"Can't move, node is dependent on new parent"; return false; } foreach (Node *n, m_nodes) { if (!n->canMoveTo(newParent)) { return false; } } return true; } void Node::makeAppointments() { QListIterator nit(m_nodes); while (nit.hasNext()) { nit.next()->makeAppointments(); } } void Node::calcResourceOverbooked() { QListIterator nit(m_nodes); while (nit.hasNext()) { nit.next()->calcResourceOverbooked(); } } // Returns the (previously) calculated duration Duration Node::duration(long id) const { Schedule *s = schedule(id); return s ? s->duration : Duration::zeroDuration; } double Node::variance(long id, Duration::Unit unit) const { double d = deviation(id, unit); return d * d; } double Node::deviation(long id, Duration::Unit unit) const { Schedule *s = schedule(id); double d = 0.0; if (s && m_estimate) { d = s->duration.toDouble(unit); double o = (d * (100 + m_estimate->optimisticRatio())) / 100; double p = (d * (100 + m_estimate->pessimisticRatio())) / 100; d = (p - o) / 6; } return d; } DateTime Node::startTime(long id) const { Schedule *s = schedule(id); return s ? s->startTime : DateTime(); } DateTime Node::endTime(long id) const { Schedule *s = schedule(id); return s ? s->endTime : DateTime(); } DateTime Node::appointmentStartTime(long id) const { Schedule *s = schedule(id); return s ? s->appointmentStartTime() : DateTime(); } DateTime Node::appointmentEndTime(long id) const { Schedule *s = schedule(id); return s ? s->appointmentEndTime() : DateTime(); } void Node::setDuration(const Duration &duration, long id) { Schedule *s = schedule(id); if (s) { s->duration = duration; } } void Node::setEarlyStart(const DateTime &dt, long id) { Schedule *s = schedule(id); if (s) s->earlyStart = dt; } DateTime Node::earlyStart(long id) const { Schedule *s = schedule(id); return s ? s->earlyStart : DateTime(); } void Node::setLateStart(const DateTime &dt, long id) { Schedule *s = schedule(id); if (s) s->lateStart = dt; } DateTime Node::lateStart(long id) const { Schedule *s = schedule(id); return s ? s->lateStart : DateTime(); } void Node::setEarlyFinish(const DateTime &dt, long id) { Schedule *s = schedule(id); if (s) s->earlyFinish = dt; } DateTime Node::earlyFinish(long id) const { Schedule *s = schedule(id); return s ? s->earlyFinish : DateTime(); } void Node::setLateFinish(const DateTime &dt, long id) { Schedule *s = schedule(id); if (s) s->lateFinish = dt; } DateTime Node::lateFinish(long id) const { Schedule *s = schedule(id); return s ? s->lateFinish : DateTime(); } DateTime Node::workStartTime(long id) const { Schedule *s = schedule(id); return s ? s->workStartTime : DateTime(); } void Node::setWorkStartTime(const DateTime &dt, long id) { Schedule *s = schedule(id); if (s) s->workStartTime = dt; } DateTime Node::workEndTime(long id) const { Schedule *s = schedule(id); return s ? s->workEndTime : DateTime(); } void Node::setWorkEndTime(const DateTime &dt, long id) { Schedule *s = schedule(id); if (s) s->workEndTime = dt; } bool Node::inCriticalPath(long id) const { Schedule *s = schedule(id); return s ? s->inCriticalPath : false; } QStringList Node::schedulingStatus(long id, bool trans) const { Schedule *s = schedule(id); QStringList lst; if (s) { lst = s->state(); } if (lst.isEmpty()) { lst.append(trans ? i18n("Not scheduled") : QString("Not scheduled")); } return lst; } bool Node::resourceError(long id) const { Schedule *s = schedule(id); return s ? s->resourceError : false; } bool Node::resourceOverbooked(long id) const { Schedule *s = schedule(id); return s ? s->resourceOverbooked : false; } bool Node::resourceNotAvailable(long id) const { Schedule *s = schedule(id); return s ? s->resourceNotAvailable : false; } bool Node::constraintError(long id) const { Schedule *s = schedule(id); return s ? s->constraintError : false; } bool Node::schedulingError(long id) const { Schedule *s = schedule(id); return s ? s->schedulingError : false; } bool Node::notScheduled(long id) const { if (type() == Type_Summarytask) { // i am scheduled if al least on child is scheduled foreach (Node *n, m_nodes) { if (! n->notScheduled(id)) { return false; } } return true; } Schedule *s = schedule(id); return s == 0 || s->isDeleted() || s->notScheduled; } QStringList Node::overbookedResources(long id) const { Schedule *s = schedule(id); return s ? s->overbookedResources() : QStringList(); } void Node::saveWorkPackageXML(QDomElement &, long) const { return; } void Node::saveRelations(QDomElement &element, const XmlSaveContext &context) const { QListIterator it(m_dependChildNodes); while (it.hasNext()) { Relation *r = it.next(); - if (context.saveNode(r->child())) { + if (context.saveRelation(r)) { r->save(element, context); } } QListIterator nodes(m_nodes); while (nodes.hasNext()) { nodes.next()->saveRelations(element, context); } } void Node::setConstraint(Node::ConstraintType type) { m_constraint = type; changed(this, ConstraintTypeProperty); } void Node::setConstraint(const QString &type) { // Do not i18n these, they are used in load() if (type == "ASAP") setConstraint(ASAP); else if (type == "ALAP") setConstraint(ALAP); else if (type == "MustStartOn") setConstraint(MustStartOn); else if (type == "MustFinishOn") setConstraint(MustFinishOn); else if (type == "StartNotEarlier") setConstraint(StartNotEarlier); else if (type == "FinishNotLater") setConstraint(FinishNotLater); else if (type == "FixedInterval") setConstraint(FixedInterval); else setConstraint(ASAP); // default } QString Node::constraintToString(bool trans) const { return constraintList(trans).at(m_constraint); } QStringList Node::constraintList(bool trans) { // keep theses in the same order as the enum! return QStringList() << (trans ? i18n("As Soon As Possible") : QString("ASAP")) << (trans ? i18n("As Late As Possible") : QString("ALAP")) << (trans ? i18n("Must Start On") : QString("MustStartOn")) << (trans ? i18n("Must Finish On") : QString("MustFinishOn")) << (trans ? i18n("Start Not Earlier") : QString("StartNotEarlier")) << (trans ? i18n("Finish Not Later") : QString("FinishNotLater")) << (trans ? i18n("Fixed Interval") : QString("FixedInterval")); } void Node::propagateEarliestStart(DateTime &time) { if (m_currentSchedule == 0) { return; } if (type() != Type_Project) { m_currentSchedule->earlyStart = time; if (m_currentSchedule->lateStart.isValid() && m_currentSchedule->lateStart < time) { m_currentSchedule->lateStart = time; } //m_currentSchedule->logDebug("propagateEarliestStart: " + time.toString()); switch (m_constraint) { case FinishNotLater: case MustFinishOn: if (m_constraintEndTime < time) { m_currentSchedule->logWarning("Task constraint outside project constraint"); #ifndef PLAN_NLOGDEBUG m_currentSchedule->logDebug(QString("%1: end constraint %2 < %3").arg(constraintToString(true), m_constraintEndTime.toString(), time.toString())); #endif } break; case MustStartOn: case FixedInterval: if (m_constraintStartTime < time) { m_currentSchedule->logWarning("Task constraint outside project constraint"); #ifndef PLAN_NLOGDEBUG m_currentSchedule->logDebug(QString("%1: start constraint %2 < %3").arg(constraintToString(true), m_constraintEndTime.toString(), time.toString())); #endif } break; default: break; } } //debugPlan<earlyStart; QListIterator it = m_nodes; while (it.hasNext()) { it.next()->propagateEarliestStart(time); } } void Node::propagateLatestFinish(DateTime &time) { if (m_currentSchedule == 0) { return; } if (type() != Type_Project) { m_currentSchedule->lateFinish = time; if (m_currentSchedule->earlyFinish.isValid() && m_currentSchedule->earlyFinish > time) { m_currentSchedule->earlyFinish = time; } switch (m_constraint) { case StartNotEarlier: case MustStartOn: if (m_constraintStartTime > time) { m_currentSchedule->logWarning("Task constraint outside project constraint"); #ifndef PLAN_NLOGDEBUG m_currentSchedule->logDebug(QString("%1: start constraint %2 < %3").arg(constraintToString(true), m_constraintEndTime.toString(), time.toString())); #endif } break; case MustFinishOn: case FixedInterval: if (m_constraintEndTime > time) { m_currentSchedule->logWarning("Task constraint outside project constraint"); #ifndef PLAN_NLOGDEBUG m_currentSchedule->logDebug(QString("%1: end constraint %2 > %3").arg(constraintToString(true), m_constraintEndTime.toString(), time.toString())); #endif } break; default: break; } } //debugPlan<lateFinish; QListIterator it = m_nodes; while (it.hasNext()) { it.next()->propagateLatestFinish(time); } } void Node::moveEarliestStart(DateTime &time) { if (m_currentSchedule == 0) return; if (m_currentSchedule->earlyStart < time) { //m_currentSchedule->logDebug("moveEarliestStart: " + m_currentSchedule->earlyStart.toString() + " -> " + time.toString()); m_currentSchedule->earlyStart = time; } QListIterator it = m_nodes; while (it.hasNext()) { it.next()->moveEarliestStart(time); } } void Node::moveLatestFinish(DateTime &time) { if (m_currentSchedule == 0) return; if (m_currentSchedule->lateFinish > time) m_currentSchedule->lateFinish = time; QListIterator it = m_nodes; while (it.hasNext()) { it.next()->moveLatestFinish(time); } } void Node::initiateCalculation(MainSchedule &sch) { m_visitedForward = false; m_visitedBackward = false; m_durationForward = Duration::zeroDuration; m_durationBackward = Duration::zeroDuration; m_earlyStart = DateTime(); m_earlyFinish = DateTime(); m_lateFinish = DateTime(); QListIterator it = m_nodes; while (it.hasNext()) { it.next()->initiateCalculation(sch); } } void Node::resetVisited() { m_visitedForward = false; m_visitedBackward = false; QListIterator it = m_nodes; while (it.hasNext()) { it.next()->resetVisited(); } } Node *Node::siblingBefore() { //debugPlan; if (parentNode()) return parentNode()->childBefore(this); return 0; } Node *Node::childBefore(Node *node) { //debugPlan; int index = m_nodes.indexOf(node); if (index > 0){ return m_nodes.at(index-1); } return 0; } Node *Node::siblingAfter() { //debugPlan; if (parentNode()) return parentNode()->childAfter(this); return 0; } Node *Node::childAfter(Node *node) { //debugPlan; Q_ASSERT(m_nodes.contains(node)); int index = m_nodes.indexOf(node); if (index < m_nodes.count()-1) { return m_nodes.at(index+1); } return 0; } bool Node::moveChildUp(Node* node) { if (findChildNode(node) == -1) return false; // not my node! Node *sib = node->siblingBefore(); if (!sib) return false; sib = sib->siblingBefore(); takeChildNode(node); if (sib) { addChildNode(node, sib); } else { insertChildNode(0, node); } return true; } bool Node::moveChildDown(Node* node) { if (findChildNode(node) == -1) return false; // not my node! Node *sib = node->siblingAfter(); if (!sib) return false; takeChildNode(node); addChildNode(node, sib); return true; } bool Node::legalToLink(const Node *node) const { Node *p = const_cast(this)->projectNode(); if (p) return p->legalToLink(this, node); return false; } bool Node::isEndNode() const { return m_dependChildNodes.isEmpty(); } bool Node::isStartNode() const { return m_dependParentNodes.isEmpty(); } void Node::setId(const QString& id) { //debugPlan<startTime = startTime; } void Node::setEndTime(const DateTime &endTime, long id) { Schedule *s = schedule(id); if (s) s->endTime = endTime; } void Node::saveAppointments(QDomElement &element, long id) const { //debugPlan<id()<<","<add(appointment); } void Node::addAppointment(ResourceSchedule *resource, const DateTime &start, const DateTime &end, double load) { Schedule *node = findSchedule(resource->id()); if (node == 0) { node = createSchedule(resource->parent()); } node->setCalculationMode(resource->calculationMode()); node->addAppointment(resource, start, end, load); } bool Node::isBaselined(long id) const { Schedule *s = schedule(id); return s ? s->isBaselined() : false; } void Node::takeSchedule(const Schedule *schedule) { if (schedule == 0) return; if (m_currentSchedule == schedule) m_currentSchedule = 0; m_schedules.take(schedule->id()); } void Node::addSchedule(Schedule *schedule) { if (schedule == 0) return; m_schedules.insert(schedule->id(), schedule); } Schedule *Node::createSchedule(const QString& name, Schedule::Type type, long id) { //debugPlan<removeStartup(*this); } m_startupAccount = acc; changed(StartupAccountProperty); } void Node::setShutdownCost(double cost) { m_shutdownCost = cost; changed(ShutdownCostProperty); } void Node::setShutdownAccount(Account *acc) { //debugPlan<removeShutdown(*this); } m_shutdownAccount = acc; changed(ShutdownAccountProperty); } void Node::setRunningAccount(Account *acc) { //debugPlan<removeRunning(*this); } m_runningAccount = acc; changed(RunningAccountProperty); } void Node::blockChanged(bool on) { m_blockChanged = on; } void Node::changed(Node *node, int property) { if (m_blockChanged) { return; } switch (property) { case TypeProperty: case StartupCostProperty: case ShutdownCostProperty: case CompletionEntryProperty: case CompletionStartedProperty: case CompletionFinishedProperty: case CompletionStartTimeProperty: case CompletionFinishTimeProperty: case CompletionPercentageProperty: case CompletionRemainingEffortProperty: case CompletionActualEffortProperty: case CompletionUsedEffortProperty: foreach (Schedule *s, m_schedules) { s->clearPerformanceCache(); } break; default: break; } if (m_parent) { m_parent->changed(node, property); } } Duration Node::plannedEffort(const Resource *resource, long id, EffortCostCalculationType type) const { Duration e; foreach (Node *n, m_nodes) { e += n->plannedEffort(resource, id, type); } return e; } Duration Node::plannedEffort(const Resource *resource, QDate date, long id, EffortCostCalculationType type) const { Duration e; foreach (Node *n, m_nodes) { e += n->plannedEffort(resource, date, id, type); } return e; } Duration Node::plannedEffortTo(const Resource *resource, QDate date, long id, EffortCostCalculationType type) const { Duration e; foreach (Node *n, m_nodes) { e += n->plannedEffortTo(resource, date, id, type); } return e; } EffortCost Node::plannedCost(long id, EffortCostCalculationType type) const { EffortCost ec; foreach (Node *n, m_nodes) { ec += n->plannedCost(id, type); } return ec; } EffortCostMap Node::bcwsPrDay(long int id, EffortCostCalculationType type) const { return const_cast(this)->bcwsPrDay(id, type); } EffortCostMap Node::bcwsPrDay(long int id, EffortCostCalculationType type) { Schedule *s = schedule(id); if (s == 0) { return EffortCostMap(); } EffortCostCache &ec = s->bcwsPrDayCache(type); if (! ec.cached) { ec.effortcostmap = EffortCostMap(); foreach (Node *n, m_nodes) { ec.effortcostmap += n->bcwsPrDay(id, type); } ec.cached = true; } return ec.effortcostmap; } EffortCostMap Node::bcwpPrDay(long int id, EffortCostCalculationType type) const { return const_cast(this)->bcwpPrDay(id, type); } EffortCostMap Node::bcwpPrDay(long int id, EffortCostCalculationType type) { Schedule *s = schedule(id); if (s == 0) { return EffortCostMap(); } EffortCostCache &ec = s->bcwpPrDayCache(type); if (! ec.cached) { ec.effortcostmap = EffortCostMap(); foreach (Node *n, m_nodes) { ec.effortcostmap += n->bcwpPrDay(id, type); } ec.cached = true; } return ec.effortcostmap; } EffortCostMap Node::acwp(long id, EffortCostCalculationType type) const { return const_cast(this)->acwp(id, type); } EffortCostMap Node::acwp(long id, EffortCostCalculationType type) { Schedule *s = schedule(id); if (s == 0) { return EffortCostMap(); } EffortCostCache &ec = s->acwpCache(type); if (! ec.cached) { ec.effortcostmap = EffortCostMap(); foreach (Node *n, m_nodes) { ec.effortcostmap += n->acwp(id, type); } ec.cached = true; } return ec.effortcostmap; } EffortCost Node::acwp(QDate date, long id) const { EffortCost ec; foreach (Node *n, m_nodes) { ec += n->acwp(date, id); } return ec; } void Node::slotStandardWorktimeChanged(KPlato::StandardWorktime*) { //debugPlan<m_expectedCached = false; m_estimate->m_optimisticCached = false; m_estimate->m_pessimisticCached = false; } } void Node::emitDocumentAdded(Node *node, Document *doc, int idx) { if (m_parent) { m_parent->emitDocumentAdded(node, doc, idx); } } void Node::emitDocumentRemoved(Node *node, Document *doc, int idx) { if (m_parent) { m_parent->emitDocumentRemoved(node, doc, idx); } } void Node::emitDocumentChanged(Node *node, Document *doc, int idx) { if (m_parent) { m_parent->emitDocumentChanged(node, doc, idx); } } ////////////////////////// Estimate ///////////////////////////////// Estimate::Estimate(Node *parent) : m_parent(parent) { m_pertCached = false; setUnit(Duration::Unit_h); setExpectedEstimate(8.0); setPessimisticEstimate(8.0); setOptimisticEstimate(8.0); m_type = Type_Effort; m_calendar = 0; m_risktype = Risk_None; } Estimate::Estimate(const Estimate &estimate, Node *parent) : m_parent(parent) { copy(estimate); } Estimate::~Estimate() { } void Estimate::clear() { m_pertCached = false; setExpectedEstimate(0.0); setPessimisticEstimate(0.0); setOptimisticEstimate(0.0); m_type = Type_Effort; m_calendar = 0; m_risktype = Risk_None; m_unit = Duration::Unit_h; changed(Node::EstimateProperty); } Estimate &Estimate::operator=(const Estimate &estimate) { copy(estimate); return *this; } void Estimate::copy(const Estimate &estimate) { //m_parent = 0; // don't touch m_expectedEstimate = estimate.m_expectedEstimate; m_optimisticEstimate = estimate.m_optimisticEstimate; m_pessimisticEstimate = estimate.m_pessimisticEstimate; m_expectedValue = estimate.m_expectedValue; m_optimisticValue = estimate.m_optimisticValue; m_pessimisticValue = estimate.m_pessimisticValue; m_expectedCached = estimate.m_expectedCached; m_optimisticCached = estimate.m_optimisticCached; m_pessimisticCached = estimate.m_pessimisticCached; m_pertExpected = estimate.m_pertExpected; m_pertCached = estimate.m_pertCached; m_type = estimate.m_type; m_calendar = estimate.m_calendar; m_risktype = estimate.m_risktype; m_unit = estimate.m_unit; changed(Node::EstimateProperty); } double Estimate::variance() const { double d = deviation(); return d * d; } double Estimate::variance(Duration::Unit unit) const { double d = deviation(unit); return d * d; } double Estimate::deviation() const { return (m_pessimisticEstimate - m_optimisticEstimate) / 6; } double Estimate::deviation(Duration::Unit unit) const { if (unit == m_unit) { return deviation(); } double p = pessimisticValue().toDouble(unit); double o = optimisticValue().toDouble(unit); double v = (p - o) / 6; return v; } Duration Estimate::pertExpected() const { if (m_risktype == Risk_Low) { if (! m_pertCached) { m_pertExpected = (optimisticValue() + pessimisticValue() + (expectedValue()*4))/6; m_pertCached = true; } return m_pertExpected; } else if (m_risktype == Risk_High) { if (! m_pertCached) { m_pertExpected = (optimisticValue() + (pessimisticValue()*2) + (expectedValue()*4))/7; m_pertCached = true; } return m_pertExpected; } return expectedValue(); // risk==none } Duration Estimate::pertOptimistic() const { if (m_risktype != Risk_None) { return pertExpected() - Duration(variance(Duration::Unit_ms)); } return optimisticValue(); } Duration Estimate::pertPessimistic() const { if (m_risktype != Risk_None) { return pertExpected() + Duration(variance(Duration::Unit_ms)); } return pessimisticValue(); } Duration Estimate::value(int valueType, bool pert) const { if (valueType == Estimate::Use_Expected) { return pert ? pertExpected() : expectedValue(); } else if (valueType == Estimate::Use_Optimistic) { return pert ? pertOptimistic() : optimisticValue(); } else if (valueType == Estimate::Use_Pessimistic) { return pert ? pertPessimistic() : pessimisticValue(); } return expectedValue(); } void Estimate::setUnit(Duration::Unit unit) { m_unit = unit; m_expectedCached = false; m_optimisticCached = false; m_pessimisticCached = false; m_pertCached = false; changed(Node::EstimateProperty); } bool Estimate::load(KoXmlElement &element, XMLLoaderObject &status) { setType(element.attribute("type")); setRisktype(element.attribute("risk")); if (status.version() <= "0.6") { m_unit = (Duration::Unit)(element.attribute("display-unit", QString().number(Duration::Unit_h)).toInt()); QList s = status.project().standardWorktime()->scales(); m_expectedEstimate = scale(Duration::fromString(element.attribute("expected")), m_unit, s); m_optimisticEstimate = scale(Duration::fromString(element.attribute("optimistic")), m_unit, s); m_pessimisticEstimate = scale(Duration::fromString(element.attribute("pessimistic")), m_unit, s); } else { if (status.version() <= "0.6.2") { // 0 pos in unit is now Unit_Y, so add 3 to get the correct new unit m_unit = (Duration::Unit)(element.attribute("unit", QString().number(Duration::Unit_ms - 3)).toInt() + 3); } else { m_unit = Duration::unitFromString(element.attribute("unit")); } m_expectedEstimate = element.attribute("expected", "0.0").toDouble(); m_optimisticEstimate = element.attribute("optimistic", "0.0").toDouble(); m_pessimisticEstimate = element.attribute("pessimistic", "0.0").toDouble(); m_calendar = status.project().findCalendar(element.attribute("calendar-id")); } return true; } void Estimate::save(QDomElement &element) const { QDomElement me = element.ownerDocument().createElement("estimate"); element.appendChild(me); me.setAttribute("expected", QString::number(m_expectedEstimate)); me.setAttribute("optimistic", QString::number(m_optimisticEstimate)); me.setAttribute("pessimistic", QString::number(m_pessimisticEstimate)); me.setAttribute("type", typeToString()); if (m_calendar) { me.setAttribute("calendar-id", m_calendar->id()); } me.setAttribute("risk", risktypeToString()); me.setAttribute("unit", Duration::unitToString(m_unit)); } QString Estimate::typeToString(bool trans) const { return typeToStringList(trans).at(m_type); } QString Estimate::typeToString(Estimate::Type typ, bool trans) { return typeToStringList(trans).value(typ); } QStringList Estimate::typeToStringList(bool trans) { return QStringList() << (trans ? i18n("Effort") : QString("Effort")) << (trans ? i18n("Duration") : QString("Duration")); } void Estimate::setType(Type type) { m_type = type; m_expectedCached = false; m_optimisticCached = false; m_pessimisticCached = false; m_pertCached = false; changed(Node::EstimateProperty); } void Estimate::setType(const QString& type) { if (type == "Effort") setType(Type_Effort); else if (type == "Duration" || /*old format*/ type == "FixedDuration") setType(Type_Duration); else if (/*old format*/type == "Length") setType(Type_Duration); else if (type == "Type_FixedDuration") // Typo, keep old xml files working setType(Type_Duration); else setType(Type_Effort); // default } QString Estimate::risktypeToString(bool trans) const { return risktypeToStringList(trans).at(m_risktype); } QStringList Estimate::risktypeToStringList(bool trans) { return QStringList() << (trans ? i18n("None") : QString("None")) << (trans ? i18n("Low") : QString("Low")) << (trans ? i18n("High") : QString("High")); } void Estimate::setRisktype(const QString& type) { if (type == "High") setRisktype(Risk_High); else if (type == "Low") setRisktype(Risk_Low); else setRisktype(Risk_None); // default } void Estimate::setRisktype(Risktype type) { m_pertCached = false; m_risktype = type; changed(Node::EstimateRiskProperty); } void Estimate::setCalendar(Calendar *calendar) { m_calendar = calendar; m_expectedCached = false; m_optimisticCached = false; m_pessimisticCached = false; m_pertCached = false; changed(Node::EstimateProperty); } void Estimate::setExpectedEstimate(double value) { m_expectedEstimate = value; m_expectedCached = false; m_pertCached = false; changed(Node::EstimateProperty); } void Estimate::setOptimisticEstimate(double value) { m_optimisticEstimate = value; m_optimisticCached = false; m_pertCached = false; changed(Node::EstimateOptimisticProperty); } void Estimate::setPessimisticEstimate(double value) { m_pessimisticEstimate = value; m_pessimisticCached = false; m_pertCached = false; changed(Node::EstimatePessimisticProperty); } void Estimate::setOptimisticRatio(int percent) { int p = percent>0 ? -percent : percent; m_optimisticValue = expectedValue()*(100+p)/100; m_optimisticEstimate = scale(m_optimisticValue, m_unit, scales()); m_optimisticCached = true; m_pertCached = false; changed(Node::EstimateOptimisticProperty); } int Estimate::optimisticRatio() const { if (m_expectedEstimate == 0.0) return 0; return (int)((optimisticValue()*100)/expectedValue())-100; } void Estimate::setPessimisticRatio(int percent) { int p = percent<0 ? -percent : percent; m_pessimisticValue = expectedValue()*(100+p)/100; m_pessimisticEstimate = scale(m_pessimisticValue, m_unit, scales()); m_pessimisticCached = true; m_pertCached = false; changed(Node::EstimatePessimisticProperty); } int Estimate::pessimisticRatio() const { if (m_expectedEstimate == 0.0) return 0; return (int)((pessimisticValue()*100)/expectedValue())-100; } // internal void Estimate::setOptimisticValue() { m_optimisticValue = scale(m_optimisticEstimate, m_unit, scales()); m_optimisticCached = true; m_pertCached = false; } // internal void Estimate::setExpectedValue() { m_expectedValue = scale(m_expectedEstimate, m_unit, scales()); m_expectedCached = true; m_pertCached = false; } // internal void Estimate::setPessimisticValue() { m_pessimisticValue = scale(m_pessimisticEstimate, m_unit, scales()); m_pessimisticCached = true; m_pertCached = false; } Duration Estimate::optimisticValue() const { if (! m_optimisticCached) { const_cast(this)->setOptimisticValue(); } return m_optimisticValue; } Duration Estimate::pessimisticValue() const { if (! m_pessimisticCached) { const_cast(this)->setPessimisticValue(); } return m_pessimisticValue; } Duration Estimate::expectedValue() const { if (! m_expectedCached) { const_cast(this)->setExpectedValue(); } return m_expectedValue; } double Estimate::scale(const Duration &value, Duration::Unit unit, const QList &scales) { //debugPlan< lst = scales; switch (lst.count()) { case Duration::Unit_Y: lst << (qint64)(365 * 24) * 60 * 60 * 1000; // add milliseconds in a year Q_FALLTHROUGH(); case Duration::Unit_M: lst << (qint64)(30 * 24) * 60 * 60 * 1000; // add milliseconds in a month Q_FALLTHROUGH(); case Duration::Unit_w: lst << (qint64)(7 * 24) * 60 * 60 * 1000; // add milliseconds in a week Q_FALLTHROUGH(); case Duration::Unit_d: lst << 24 * 60 * 60 * 1000; // add milliseconds in day Q_FALLTHROUGH(); case Duration::Unit_h: lst << 60 * 60 * 1000; // add milliseconds in hour Q_FALLTHROUGH(); case Duration::Unit_m: lst << 60 * 1000; // add milliseconds in minute Q_FALLTHROUGH(); case Duration::Unit_s: lst << 1000; // add milliseconds in second Q_FALLTHROUGH(); case Duration::Unit_ms: lst << 1; // add milliseconds in a millisecond Q_FALLTHROUGH(); default: break; } double v = (double)(value.milliseconds()); v /= lst[ unit ]; //debugPlan< &scales) { //debugPlan< lst = scales; switch (lst.count()) { case Duration::Unit_Y: lst << (qint64)(365 * 24) * 60 * 60 * 1000; // add milliseconds in a year Q_FALLTHROUGH(); case Duration::Unit_M: lst << (qint64)(30 * 24) * 60 * 60 * 1000; // add milliseconds in a month Q_FALLTHROUGH(); case Duration::Unit_w: lst << (qint64)(7 * 24) * 60 * 60 * 1000; // add milliseconds in a week Q_FALLTHROUGH(); case Duration::Unit_d: lst << 24 * 60 * 60 * 1000; // add milliseconds in day Q_FALLTHROUGH(); case Duration::Unit_h: lst << 60 * 60 * 1000; // add milliseconds in hour Q_FALLTHROUGH(); case Duration::Unit_m: lst << 60 * 1000; // add milliseconds in minute Q_FALLTHROUGH(); case Duration::Unit_s: lst << 1000; // add milliseconds in second Q_FALLTHROUGH(); case Duration::Unit_ms: lst << 1; // add milliseconds in a millisecond Q_FALLTHROUGH(); default: break; } qint64 v = (qint64)(value * lst[ unit ]); //debugPlan< Estimate::defaultScales() { QList lst; lst << (qint64)(365 * 24) * 60 * 60 * 1000 // add milliseconds in a year << (qint64)(30 * 24) * 60 * 60 * 1000 // add milliseconds in a month << (qint64)(7 * 24) * 60 * 60 * 1000 // add milliseconds in a week << 24 * 60 * 60 * 1000 // add milliseconds in day << 60 * 60 * 1000 // add milliseconds in hour << 60 * 1000 // add milliseconds in minute << 1000 // add milliseconds in second << 1; // add milliseconds in a millisecond return lst; } QList Estimate::scales() const { QList s; if (m_type == Type_Duration && m_calendar == 0) { return s; // Use default scaling (24h a day...) } if (m_parent == 0) { return s; } Project *p = static_cast(m_parent->projectNode()); if (p == 0) { return s; } s << p->standardWorktime()->scales(); return s; } } //KPlato namespace diff --git a/src/libs/kernel/kptproject.cpp b/src/libs/kernel/kptproject.cpp index 0e28021f..bc01c835 100644 --- a/src/libs/kernel/kptproject.cpp +++ b/src/libs/kernel/kptproject.cpp @@ -1,3291 +1,3539 @@ /* This file is part of the KDE project Copyright (C) 2001 Thomas zander Copyright (C) 2004 - 2010, 2012 Dag Andersen Copyright (C) 2007 Florian Piquemal Copyright (C) 2007 Alexis Ménard Copyright (C) 2019 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 "kptproject.h" #include "kptlocale.h" #include "kptappointment.h" #include "kpttask.h" #include "kptdatetime.h" #include "kpteffortcostmap.h" #include "kptschedule.h" #include "kptwbsdefinition.h" #include "kptxmlloaderobject.h" #include "XmlSaveContext.h" #include "kptschedulerplugin.h" #include "kptdebug.h" #include #include #include #include #include #include namespace KPlato { QString generateId() { return QDateTime::currentDateTime().toString(QStringLiteral("yyyyMMddHHmmss")) + KRandom::randomString(10); } Project::Project(Node *parent) : Node(parent), m_accounts(*this), m_defaultCalendar(0), m_config(&emptyConfig), m_schedulerPlugins(), m_useSharedResources(false), m_sharedResourcesLoaded(false), m_loadProjectsAtStartup(false), m_useLocalTaskModules(true) { //debugPlan<<"("<setDefaultValues(*this); } Project::Project(ConfigBase &config, bool useDefaultValues, Node *parent) : Node(parent), m_accounts(*this), m_defaultCalendar(0), m_config(&config), m_schedulerPlugins(), m_useSharedResources(false), m_sharedResourcesLoaded(false), m_loadProjectsAtStartup(false), m_useLocalTaskModules(true) { debugPlan<<"("<setDefaultValues(*this); } } void Project::init() { m_refCount = 1; // always used by creator m_constraint = Node::MustStartOn; m_standardWorktime = new StandardWorktime(); m_timeZone = QTimeZone::systemTimeZone(); // local timezone as default //debugPlan<= 0); if (m_refCount <= 0) { emit aboutToBeDeleted(); deleteLater(); } } Project::~Project() { debugPlan<<"("<blockChanged(); } for (Resource *r : qAsConst(m_resources)) { r->blockChanged(); } for (ResourceGroup *g : qAsConst(resourceGroupIdDict)) { g->blockChanged(); } delete m_standardWorktime; for (Resource *r : m_resources) { delete r; } while (!m_resourceGroups.isEmpty()) delete m_resourceGroups.takeFirst(); while (!m_calendars.isEmpty()) delete m_calendars.takeFirst(); while (!m_managers.isEmpty()) delete m_managers.takeFirst(); m_config = 0; //not mine, don't delete } int Project::type() const { return Node::Type_Project; } void Project::generateUniqueNodeIds() { foreach (Node *n, nodeIdDict) { debugPlan<name()<<"old"<id(); QString uid = uniqueNodeId(); nodeIdDict.remove(n->id()); n->setId(uid); nodeIdDict[ uid ] = n; debugPlan<name()<<"new"<id(); } } void Project::generateUniqueIds() { generateUniqueNodeIds(); foreach (ResourceGroup *g, resourceGroupIdDict) { if (g->isShared()) { continue; } resourceGroupIdDict.remove(g->id()); g->setId(uniqueResourceGroupId()); resourceGroupIdDict[ g->id() ] = g; } foreach (Resource *r, resourceIdDict) { if (r->isShared()) { continue; } resourceIdDict.remove(r->id()); r->setId(uniqueResourceId()); resourceIdDict[ r->id() ] = r; } foreach (Calendar *c, calendarIdDict) { if (c->isShared()) { continue; } calendarIdDict.remove(c->id()); c->setId(uniqueCalendarId()); calendarIdDict[ c->id() ] = c; } } void Project::calculate(Schedule *schedule, const DateTime &dt) { if (schedule == 0) { errorPlan << "Schedule == 0, cannot calculate"; return ; } m_currentSchedule = schedule; calculate(dt); } void Project::calculate(const DateTime &dt) { if (m_currentSchedule == 0) { errorPlan << "No current schedule to calculate"; return ; } stopcalculation = false; QLocale locale; DateTime time = dt.isValid() ? dt : DateTime(QDateTime::currentDateTime()); MainSchedule *cs = static_cast(m_currentSchedule); Estimate::Use estType = (Estimate::Use) cs->type(); if (type() == Type_Project) { cs->setPhaseName(0, i18n("Init")); cs->logInfo(i18n("Schedule project from: %1", locale.toString(dt, QLocale::ShortFormat)), 0); initiateCalculation(*cs); initiateCalculationLists(*cs); // must be after initiateCalculation() !! propagateEarliestStart(time); // Calculate lateFinish from time. If a task has started, remainingEffort is used. cs->setPhaseName(1, i18nc("Schedule project forward", "Forward")); cs->logInfo(i18n("Calculate finish"), 1); cs->lateFinish = calculateForward(estType); cs->lateFinish = checkEndConstraints(cs->lateFinish); propagateLatestFinish(cs->lateFinish); // Calculate earlyFinish. If a task has started, remainingEffort is used. cs->setPhaseName(2, i18nc("Schedule project backward","Backward")); cs->logInfo(i18n("Calculate start"), 2); calculateBackward(estType); // Schedule. If a task has started, remainingEffort is used and appointments are copied from parent cs->setPhaseName(3, i18n("Schedule")); cs->logInfo(i18n("Schedule tasks forward"), 3); cs->endTime = scheduleForward(cs->startTime, estType); cs->logInfo(i18n("Scheduled finish: %1", locale.toString(cs->endTime, QLocale::ShortFormat)), 3); if (cs->endTime > m_constraintEndTime) { cs->logError(i18n("Could not finish project in time: %1", locale.toString(m_constraintEndTime, QLocale::ShortFormat)), 3); } else if (cs->endTime == m_constraintEndTime) { cs->logWarning(i18n("Finished project exactly on time: %1", locale.toString(m_constraintEndTime, QLocale::ShortFormat)), 3); } else { cs->logInfo(i18n("Finished project before time: %1", locale.toString(m_constraintEndTime, QLocale::ShortFormat)), 3); } calcCriticalPath(false); calcResourceOverbooked(); cs->notScheduled = false; calcFreeFloat(); emit scheduleChanged(cs); emit projectChanged(); } else if (type() == Type_Subproject) { warnPlan << "Subprojects not implemented"; } else { errorPlan << "Illegal project type: " << type(); } } void Project::calculate(ScheduleManager &sm) { emit sigCalculationStarted(this, &sm); sm.setScheduling(true); m_progress = 0; int nodes = 0; foreach (Node *n, nodeIdDict) { if (n->type() == Node::Type_Task || n->type() == Node::Type_Milestone) { nodes++; } } int maxprogress = nodes * 3; if (sm.recalculate()) { emit maxProgress(maxprogress); sm.setMaxProgress(maxprogress); incProgress(); if (sm.parentManager()) { sm.expected()->startTime = sm.parentManager()->expected()->startTime; sm.expected()->earlyStart = sm.parentManager()->expected()->earlyStart; } incProgress(); calculate(sm.expected(), sm.recalculateFrom()); } else { emit maxProgress(maxprogress); sm.setMaxProgress(maxprogress); calculate(sm.expected()); emit scheduleChanged(sm.expected()); setCurrentSchedule(sm.expected()->id()); } emit sigProgress(maxprogress); emit sigCalculationFinished(this, &sm); emit scheduleManagerChanged(&sm); emit projectCalculated(&sm); emit projectChanged(); sm.setScheduling(false); } void Project::calculate(Schedule *schedule) { if (schedule == 0) { errorPlan << "Schedule == 0, cannot calculate"; return ; } m_currentSchedule = schedule; calculate(); } void Project::calculate() { if (m_currentSchedule == 0) { errorPlan << "No current schedule to calculate"; return ; } stopcalculation = false; MainSchedule *cs = static_cast(m_currentSchedule); bool backwards = false; if (cs->manager()) { backwards = cs->manager()->schedulingDirection(); } QLocale locale; Estimate::Use estType = (Estimate::Use) cs->type(); if (type() == Type_Project) { QTime timer; timer.start(); initiateCalculation(*cs); initiateCalculationLists(*cs); // must be after initiateCalculation() !! if (! backwards) { cs->setPhaseName(0, i18n("Init")); cs->logInfo(i18n("Schedule project forward from: %1", locale.toString(m_constraintStartTime, QLocale::ShortFormat)), 0); cs->startTime = m_constraintStartTime; cs->earlyStart = m_constraintStartTime; // Calculate from start time propagateEarliestStart(cs->earlyStart); cs->setPhaseName(1, i18nc("Schedule project forward", "Forward")); cs->logInfo(i18n("Calculate late finish"), 1); cs->lateFinish = calculateForward(estType); // cs->lateFinish = checkEndConstraints(cs->lateFinish); cs->logInfo(i18n("Late finish calculated: %1", locale.toString(cs->lateFinish, QLocale::ShortFormat)), 1); propagateLatestFinish(cs->lateFinish); cs->setPhaseName(2, i18nc("Schedule project backward", "Backward")); cs->logInfo(i18n("Calculate early start"), 2); calculateBackward(estType); cs->setPhaseName(3, i18n("Schedule")); cs->logInfo(i18n("Schedule tasks forward"), 3); cs->endTime = scheduleForward(cs->startTime, estType); cs->duration = cs->endTime - cs->startTime; cs->logInfo(i18n("Scheduled finish: %1", locale.toString(cs->endTime, QLocale::ShortFormat)), 3); if (cs->endTime > m_constraintEndTime) { cs->constraintError = true; cs->logError(i18n("Could not finish project in time: %1", locale.toString(m_constraintEndTime, QLocale::ShortFormat)), 3); } else if (cs->endTime == m_constraintEndTime) { cs->logWarning(i18n("Finished project exactly on time: %1", locale.toString(m_constraintEndTime, QLocale::ShortFormat)), 3); } else { cs->logInfo(i18n("Finished project before time: %1", locale.toString(m_constraintEndTime, QLocale::ShortFormat)), 3); } calcCriticalPath(false); } else { cs->setPhaseName(0, i18n("Init")); cs->logInfo(i18n("Schedule project backward from: %1", locale.toString(m_constraintEndTime, QLocale::ShortFormat)), 0); // Calculate from end time propagateLatestFinish(m_constraintEndTime); cs->setPhaseName(1, i18nc("Schedule project backward", "Backward")); cs->logInfo(i18n("Calculate early start"), 1); cs->earlyStart = calculateBackward(estType); // cs->earlyStart = checkStartConstraints(cs->earlyStart); cs->logInfo(i18n("Early start calculated: %1", locale.toString(cs->earlyStart, QLocale::ShortFormat)), 1); propagateEarliestStart(cs->earlyStart); cs->setPhaseName(2, i18nc("Schedule project forward", "Forward")); cs->logInfo(i18n("Calculate late finish"), 2); cs->lateFinish = qMax(m_constraintEndTime, calculateForward(estType)); cs->logInfo(i18n("Late finish calculated: %1", locale.toString(cs->lateFinish, QLocale::ShortFormat)), 2); cs->setPhaseName(3, i18n("Schedule")); cs->logInfo(i18n("Schedule tasks backward"), 3); cs->startTime = scheduleBackward(cs->lateFinish, estType); cs->endTime = cs->startTime; foreach (Node *n, allNodes()) { if (n->type() == Type_Task || n->type() == Type_Milestone) { DateTime e = n->endTime(cs->id()); if (cs->endTime < e) { cs->endTime = e; } } } if (cs->endTime > m_constraintEndTime) { cs->constraintError = true; cs->logError(i18n("Failed to finish project within target time"), 3); } cs->duration = cs->endTime - cs->startTime; cs->logInfo(i18n("Scheduled start: %1, target time: %2", locale.toString(cs->startTime, QLocale::ShortFormat), locale.toString(m_constraintStartTime, QLocale::ShortFormat)), 3); if (cs->startTime < m_constraintStartTime) { cs->constraintError = true; cs->logError(i18n("Must start project early in order to finish in time: %1", locale.toString(m_constraintStartTime, QLocale::ShortFormat)), 3); } else if (cs->startTime == m_constraintStartTime) { cs->logWarning(i18n("Start project exactly on time: %1", locale.toString(m_constraintStartTime, QLocale::ShortFormat)), 3); } else { cs->logInfo(i18n("Can start project later than time: %1", locale.toString(m_constraintStartTime, QLocale::ShortFormat)), 3); } calcCriticalPath(true); } cs->logInfo(i18n("Calculation took: %1", KFormat().formatDuration(timer.elapsed()))); // TODO: fix this uncertainty, manager should *always* be available if (cs->manager()) { finishCalculation(*(cs->manager())); } } else if (type() == Type_Subproject) { warnPlan << "Subprojects not implemented"; } else { errorPlan << "Illegal project type: " << type(); } } void Project::finishCalculation(ScheduleManager &sm) { MainSchedule *cs = sm.expected(); if (nodeIdDict.count() > 1) { // calculate project duration cs->startTime = m_constraintEndTime; cs->endTime = m_constraintStartTime; for (const Node *n : qAsConst(nodeIdDict)) { cs->startTime = qMin(cs->startTime, n->startTime(cs->id())); cs->endTime = qMax(cs->endTime, n->endTime(cs->id())); } cs->duration = cs->endTime - cs->startTime; } calcCriticalPath(false); calcResourceOverbooked(); cs->notScheduled = false; calcFreeFloat(); emit scheduleChanged(cs); emit projectChanged(); debugPlan<startTime<endTime<<"-------------------------"; } void Project::setProgress(int progress, ScheduleManager *sm) { m_progress = progress; if (sm) { sm->setProgress(progress); } emit sigProgress(progress); } void Project::setMaxProgress(int max, ScheduleManager *sm) { if (sm) { sm->setMaxProgress(max); } emitMaxProgress(max); } void Project::incProgress() { m_progress += 1; emit sigProgress(m_progress); } void Project::emitMaxProgress(int value) { emit maxProgress(value); } bool Project::calcCriticalPath(bool fromEnd) { //debugPlan; MainSchedule *cs = static_cast(m_currentSchedule); if (cs == 0) { return false; } if (fromEnd) { QListIterator startnodes = cs->startNodes(); while (startnodes.hasNext()) { startnodes.next() ->calcCriticalPath(fromEnd); } } else { QListIterator endnodes = cs->endNodes(); while (endnodes.hasNext()) { endnodes.next() ->calcCriticalPath(fromEnd); } } calcCriticalPathList(cs); return false; } void Project::calcCriticalPathList(MainSchedule *cs) { //debugPlan<name(); cs->clearCriticalPathList(); foreach (Node *n, allNodes()) { if (n->numDependParentNodes() == 0 && n->inCriticalPath(cs->id())) { cs->addCriticalPath(); cs->addCriticalPathNode(n); calcCriticalPathList(cs, n); } } cs->criticalPathListCached = true; //debugPlan<<*(criticalPathList(cs->id())); } void Project::calcCriticalPathList(MainSchedule *cs, Node *node) { //debugPlan<name()<<", "<id(); bool newPath = false; QList lst = *(cs->currentCriticalPath()); foreach (Relation *r, node->dependChildNodes()) { if (r->child()->inCriticalPath(cs->id())) { if (newPath) { cs->addCriticalPath(&lst); //debugPlan<name()<<" new path"; } cs->addCriticalPathNode(r->child()); calcCriticalPathList(cs, r->child()); newPath = true; } } } const QList< QList > *Project::criticalPathList(long id) { Schedule *s = schedule(id); if (s == 0) { //debugPlan<<"No schedule with id="<(s); if (! ms->criticalPathListCached) { initiateCalculationLists(*ms); calcCriticalPathList(ms); } return ms->criticalPathList(); } QList Project::criticalPath(long id, int index) { Schedule *s = schedule(id); if (s == 0) { //debugPlan<<"No schedule with id="<(); } MainSchedule *ms = static_cast(s); if (! ms->criticalPathListCached) { initiateCalculationLists(*ms); calcCriticalPathList(ms); } return ms->criticalPath(index); } DateTime Project::startTime(long id) const { Schedule *s = schedule(id); return s ? s->startTime : m_constraintStartTime; } DateTime Project::endTime(long id) const { Schedule *s = schedule(id); return s ? s->endTime : m_constraintEndTime; } Duration Project::duration(long id) const { Schedule *s = schedule(id); return s ? s->duration : Duration::zeroDuration; } Duration *Project::getRandomDuration() { return 0L; } DateTime Project::checkStartConstraints(const DateTime &dt) const { DateTime t = dt; foreach (Node *n, nodeIdDict) { if (n->type() == Node::Type_Task || n->type() == Node::Type_Milestone) { switch (n->constraint()) { case Node::FixedInterval: case Node::StartNotEarlier: case Node::MustStartOn: t = qMin(t, qMax(n->constraintStartTime(), m_constraintStartTime)); break; default: break; } } } return t; } DateTime Project::checkEndConstraints(const DateTime &dt) const { DateTime t = dt; foreach (Node *n, nodeIdDict) { if (n->type() == Node::Type_Task || n->type() == Node::Type_Milestone) { switch (n->constraint()) { case Node::FixedInterval: case Node::FinishNotLater: case Node::MustFinishOn: t = qMax(t, qMin(n->constraintEndTime(), m_constraintEndTime)); break; default: break; } } } return t; } #ifndef PLAN_NLOGDEBUG bool Project::checkParent(Node *n, const QList &list, QList &checked) { if (n->isStartNode()) { debugPlan< lst = list; lst << n; foreach (Relation *r, n->dependParentNodes()) { if (checked.contains(r)) { debugPlan<<"Depend:"<parent()<<": checked"; continue; } checked << r; if (! checkParent(r->parent(), lst, checked)) { return false; } } Task *t = static_cast(n); foreach (Relation *r, t->parentProxyRelations()) { if (checked.contains(r)) { debugPlan<<"Depend:"<parent()<<": checked"; continue; } checked << r; debugPlan<<"Proxy:"<parent()<<":"<parent(), lst, checked)) { return false; } } return true; } bool Project::checkChildren(Node *n, const QList &list, QList &checked) { if (n->isEndNode()) { debugPlan< lst = list; lst << n; foreach (Relation *r, n->dependChildNodes()) { if (checked.contains(r)) { debugPlan<<"Depend:"<parent()<<": checked"; continue; } checked << r; if (! checkChildren(r->child(), lst, checked)) { return false; } } Task *t = static_cast(n); foreach (Relation *r, t->childProxyRelations()) { if (checked.contains(r)) { debugPlan<<"Depend:"<parent()<<": checked"; continue; } debugPlan<<"Proxy:"<parent()<<":"<child(), lst, checked)) { return false; } } return true; } #endif void Project::tasksForward() { m_hardConstraints.clear(); m_softConstraints.clear(); m_terminalNodes.clear(); // Do these in reverse order to get tasks with same prio in wbs order const QList tasks = allTasks(); for (int i = tasks.count() -1; i >= 0; --i) { Task *t = tasks.at(i); switch (t->constraint()) { case Node::MustStartOn: case Node::MustFinishOn: case Node::FixedInterval: m_hardConstraints.prepend(t); break; case Node::StartNotEarlier: case Node::FinishNotLater: m_softConstraints.prepend(t); break; default: if (t->isEndNode()) { m_terminalNodes.insert(-t->priority(), t); } break; } } #ifndef PLAN_NLOGDEBUG debugPlan<<"End nodes:"< lst; QList rel; Q_ASSERT(checkParent(n, lst, rel)); Q_UNUSED(n); } #endif } void Project::tasksBackward() { m_hardConstraints.clear(); m_softConstraints.clear(); m_terminalNodes.clear(); // Do these in reverse order to get tasks with same prio in wbs order const QList tasks = allTasks(); for (int i = tasks.count() -1; i >= 0; --i) { Task *t = tasks.at(i); switch (t->constraint()) { case Node::MustStartOn: case Node::MustFinishOn: case Node::FixedInterval: m_hardConstraints.prepend(t); break; case Node::StartNotEarlier: case Node::FinishNotLater: m_softConstraints.prepend(t); break; default: if (t->isStartNode()) { m_terminalNodes.insert(-t->priority(), t); } break; } } #ifndef PLAN_NLOGDEBUG debugPlan<<"Start nodes:"< lst; QList rel; Q_ASSERT(checkChildren(n, lst, rel)); Q_UNUSED(n); } #endif } DateTime Project::calculateForward(int use) { //debugPlan<(m_currentSchedule); if (cs == 0) { return finish; } if (type() == Node::Type_Project) { QTime timer; timer.start(); cs->logInfo(i18n("Start calculating forward")); m_visitedForward = true; if (! m_visitedBackward) { // setup tasks tasksForward(); // Do all hard constrained first foreach (Node *n, m_hardConstraints) { cs->logDebug("Calculate task with hard constraint:" + n->name() + " : " + n->constraintToString()); DateTime time = n->calculateEarlyFinish(use); // do not do predeccessors if (time > finish) { finish = time; } } // do the predeccessors foreach (Node *n, m_hardConstraints) { cs->logDebug("Calculate predeccessors to hard constrained task:" + n->name() + " : " + n->constraintToString()); DateTime time = n->calculateForward(use); if (time > finish) { finish = time; } } // now try to schedule soft constrained *with* predeccessors foreach (Node *n, m_softConstraints) { cs->logDebug("Calculate task with soft constraint:" + n->name() + " : " + n->constraintToString()); DateTime time = n->calculateForward(use); if (time > finish) { finish = time; } } // and then the rest using the end nodes to calculate everything (remaining) foreach (Task *n, m_terminalNodes) { cs->logDebug("Calculate using end task:" + n->name() + " : " + n->constraintToString()); DateTime time = n->calculateForward(use); if (time > finish) { finish = time; } } } else { // tasks have been calculated backwards in this order foreach (Node *n, cs->backwardNodes()) { DateTime time = n->calculateForward(use); if (time > finish) { finish = time; } } } cs->logInfo(i18n("Finished calculating forward: %1 ms", timer.elapsed())); } else { //TODO: subproject } return finish; } DateTime Project::calculateBackward(int use) { //debugPlan<(m_currentSchedule); if (cs == 0) { return start; } if (type() == Node::Type_Project) { QTime timer; timer.start(); cs->logInfo(i18n("Start calculating backward")); m_visitedBackward = true; if (! m_visitedForward) { // setup tasks tasksBackward(); // Do all hard constrained first foreach (Task *n, m_hardConstraints) { cs->logDebug("Calculate task with hard constraint:" + n->name() + " : " + n->constraintToString()); DateTime time = n->calculateLateStart(use); // do not do predeccessors if (! start.isValid() || time < start) { start = time; } } // then do the predeccessors foreach (Task *n, m_hardConstraints) { cs->logDebug("Calculate predeccessors to hard constrained task:" + n->name() + " : " + n->constraintToString()); DateTime time = n->calculateBackward(use); if (! start.isValid() || time < start) { start = time; } } // now try to schedule soft constrained *with* predeccessors foreach (Task *n, m_softConstraints) { cs->logDebug("Calculate task with soft constraint:" + n->name() + " : " + n->constraintToString()); DateTime time = n->calculateBackward(use); if (! start.isValid() || time < start) { start = time; } } // and then the rest using the start nodes to calculate everything (remaining) foreach (Task *n, m_terminalNodes) { cs->logDebug("Calculate using start task:" + n->name() + " : " + n->constraintToString()); DateTime time = n->calculateBackward(use); if (! start.isValid() || time < start) { start = time; } } } else { // tasks have been calculated forwards in this order foreach (Node *n, cs->forwardNodes()) { DateTime time = n->calculateBackward(use); if (! start.isValid() || time < start) { start = time; } } } cs->logInfo(i18n("Finished calculating backward: %1 ms", timer.elapsed())); } else { //TODO: subproject } return start; } DateTime Project::scheduleForward(const DateTime &earliest, int use) { DateTime end; MainSchedule *cs = static_cast(m_currentSchedule); if (cs == 0 || stopcalculation) { return DateTime(); } QTime timer; timer.start(); cs->logInfo(i18n("Start scheduling forward")); resetVisited(); // Schedule in the same order as calculated forward // Do all hard constrained first foreach (Node *n, m_hardConstraints) { cs->logDebug("Schedule task with hard constraint:" + n->name() + " : " + n->constraintToString()); DateTime time = n->scheduleFromStartTime(use); // do not do predeccessors if (time > end) { end = time; } } foreach (Node *n, cs->forwardNodes()) { cs->logDebug("Schedule task:" + n->name() + " : " + n->constraintToString()); DateTime time = n->scheduleForward(earliest, use); if (time > end) { end = time; } } // Fix summarytasks adjustSummarytask(); cs->logInfo(i18n("Finished scheduling forward: %1 ms", timer.elapsed())); foreach (Node *n, allNodes()) { if (n->type() == Node::Type_Task || n->type() == Node::Type_Milestone) { Q_ASSERT(n->isScheduled()); } } return end; } DateTime Project::scheduleBackward(const DateTime &latest, int use) { DateTime start; MainSchedule *cs = static_cast(m_currentSchedule); if (cs == 0 || stopcalculation) { return start; } QTime timer; timer.start(); cs->logInfo(i18n("Start scheduling backward")); resetVisited(); // Schedule in the same order as calculated backward // Do all hard constrained first foreach (Node *n, m_hardConstraints) { cs->logDebug("Schedule task with hard constraint:" + n->name() + " : " + n->constraintToString()); DateTime time = n->scheduleFromEndTime(use); // do not do predeccessors if (! start.isValid() || time < start) { start = time; } } foreach (Node *n, cs->backwardNodes()) { cs->logDebug("Schedule task:" + n->name() + " : " + n->constraintToString()); DateTime time = n->scheduleBackward(latest, use); if (! start.isValid() || time < start) { start = time; } } // Fix summarytasks adjustSummarytask(); cs->logInfo(i18n("Finished scheduling backward: %1 ms", timer.elapsed())); foreach (Node *n, allNodes()) { if (n->type() == Node::Type_Task || n->type() == Node::Type_Milestone) { Q_ASSERT(n->isScheduled()); } } return start; } void Project::adjustSummarytask() { MainSchedule *cs = static_cast(m_currentSchedule); if (cs == 0 || stopcalculation) { return; } QListIterator it(cs->summaryTasks()); while (it.hasNext()) { it.next() ->adjustSummarytask(); } } void Project::initiateCalculation(MainSchedule &sch) { //debugPlan< git(m_resourceGroups); while (git.hasNext()) { git.next() ->initiateCalculation(sch); } for (Resource *r : m_resources) { r->initiateCalculation(sch); } - + Node::initiateCalculation(sch); } void Project::initiateCalculationLists(MainSchedule &sch) { //debugPlan< it = childNodeIterator(); while (it.hasNext()) { it.next() ->initiateCalculationLists(sch); } } else { //TODO: subproject } } -bool Project::load(KoXmlElement &element, XMLLoaderObject &status) +bool Project::load(KoXmlElement &projectElement, XMLLoaderObject &status) { //debugPlan<<"--->"; - m_useSharedResources = false; // default should off in case old project - // load locale first - KoXmlNode n = element.firstChild(); - for (; ! n.isNull(); n = n.nextSibling()) { - if (! n.isElement()) { - continue; + QString s; + bool ok = false; + if (projectElement.hasAttribute("name")) { + setName(projectElement.attribute("name")); + } + if (projectElement.hasAttribute("id")) { + removeId(m_id); + m_id = projectElement.attribute("id"); + registerNodeId(this); + } + if (projectElement.hasAttribute("priority")) { + m_priority = projectElement.attribute(QStringLiteral("priority"), "0").toInt(); + } + if (projectElement.hasAttribute("leader")) { + m_leader = projectElement.attribute("leader"); + } + if (projectElement.hasAttribute("description")) { + m_description = projectElement.attribute("description"); + } + if (projectElement.hasAttribute("timezone")) { + QTimeZone tz(projectElement.attribute("timezone").toLatin1()); + if (tz.isValid()) { + m_timeZone = tz; + } else warnPlan<<"No timezone specified, using default (local)"; + status.setProjectTimeZone(m_timeZone); + } + if (projectElement.hasAttribute("scheduling")) { + // Allow for both numeric and text + s = projectElement.attribute("scheduling", "0"); + m_constraint = (Node::ConstraintType) s.toInt(&ok); + if (!ok) { + setConstraint(s); } - KoXmlElement e = n.toElement(); - if (e.tagName() == "locale") { - Locale *l = locale(); - l->setCurrencySymbol(e.attribute("currency-symbol", "")); - - if (e.hasAttribute("currency-digits")) { - l->setMonetaryDecimalPlaces(e.attribute("currency-digits").toInt()); - } - QLocale::Language language = QLocale::AnyLanguage; - QLocale::Country country = QLocale::AnyCountry; - if (e.hasAttribute("language")) { - language = static_cast(e.attribute("language").toInt()); - } - if (e.hasAttribute("country")) { - country = static_cast(e.attribute("country").toInt()); - } - l->setCurrencyLocale(language, country); - } else if (e.tagName() == "shared-resources") { - m_useSharedResources = e.attribute("use", "0").toInt(); - m_sharedResourcesFile = e.attribute("file"); - m_sharedProjectsUrl = QUrl(e.attribute("projects-url")); - m_loadProjectsAtStartup = (bool)e.attribute("projects-loadatstartup", "0").toInt(); - } else if (e.tagName() == QLatin1String("documents")) { - m_documents.load(e, status); - } else if (e.tagName() == QLatin1String("workpackageinfo")) { - if (e.hasAttribute("check-for-workpackages")) { - m_workPackageInfo.checkForWorkPackages = e.attribute("check-for-workpackages").toInt(); - } - if (e.hasAttribute("retrieve-url")) { - m_workPackageInfo.retrieveUrl = QUrl(e.attribute("retrieve-url")); - } - if (e.hasAttribute("delete-after-retrieval")) { - m_workPackageInfo.deleteAfterRetrieval = e.attribute("delete-after-retrieval").toInt(); - } - if (e.hasAttribute("archive-after-retrieval")) { - m_workPackageInfo.archiveAfterRetrieval = e.attribute("archive-after-retrieval").toInt(); - } - if (e.hasAttribute("archive-url")) { - m_workPackageInfo.archiveUrl = QUrl(e.attribute("archive-url")); - } - if (e.hasAttribute("publish-url")) { - m_workPackageInfo.publishUrl = QUrl(e.attribute("publish-url")); - } - } else if (e.tagName() == QLatin1String("task-modules")) { - m_useLocalTaskModules = false; - QList urls; - for (KoXmlNode child = e.firstChild(); !child.isNull(); child = child.nextSibling()) { - KoXmlElement path = child.toElement(); - if (path.isNull()) { - continue; - } - QString s = path.attribute("url"); - if (!s.isEmpty()) { - QUrl url = QUrl::fromUserInput(s); - if (!urls.contains(url)) { - urls << url; - } - } - } - m_taskModules = urls; - // If set adds local path to taskModules() - setUseLocalTaskModules((bool)e.attribute("use-local-task-modules").toInt()); + if (m_constraint != Node::MustStartOn && m_constraint != Node::MustFinishOn) { + errorPlanXml << "Illegal constraint: " << constraintToString(); + setConstraint(Node::MustStartOn); } } - QList cals; - QString s; - bool ok = false; - setName(element.attribute("name")); - removeId(m_id); - m_id = element.attribute("id"); - registerNodeId(this); - m_priority = element.attribute(QStringLiteral("priority"), "0").toInt(); - m_leader = element.attribute("leader"); - m_description = element.attribute("description"); - QTimeZone tz(element.attribute("timezone").toLatin1()); - if (tz.isValid()) { - m_timeZone = tz; - } else warnPlan<<"No timezone specified, using default (local)"; - status.setProjectTimeZone(m_timeZone); - - // Allow for both numeric and text - s = element.attribute("scheduling", "0"); - m_constraint = (Node::ConstraintType) s.toInt(&ok); - if (!ok) - setConstraint(s); - if (m_constraint != Node::MustStartOn && - m_constraint != Node::MustFinishOn) { - errorPlan << "Illegal constraint: " << constraintToString(); - setConstraint(Node::MustStartOn); - } - s = element.attribute("start-time"); - if (!s.isEmpty()) - m_constraintStartTime = DateTime::fromString(s, m_timeZone); - s = element.attribute("end-time"); - if (!s.isEmpty()) - m_constraintEndTime = DateTime::fromString(s, m_timeZone); - + if (projectElement.hasAttribute("start-time")) { + s = projectElement.attribute("start-time"); + if (!s.isEmpty()) + m_constraintStartTime = DateTime::fromString(s, m_timeZone); + } + if (projectElement.hasAttribute("end-time")) { + s = projectElement.attribute("end-time"); + if (!s.isEmpty()) + m_constraintEndTime = DateTime::fromString(s, m_timeZone); + } status.setProgress(10); // Load the project children + KoXmlElement e = projectElement; + if (status.version() < "0.7.0") { + e = projectElement; + } else { + e = projectElement.namedItem("project-settings").toElement(); + } + if (!e.isNull()) { + loadSettings(e, status); + } + e = projectElement.namedItem("documents").toElement(); + if (!e.isNull()) { + m_documents.load(e, status); + } // Do calendars first, they only reference other calendars //debugPlan<<"Calendars--->"; - n = element.firstChild(); - for (; ! n.isNull(); n = n.nextSibling()) { - if (! n.isElement()) { - continue; - } - KoXmlElement e = n.toElement(); - if (e.tagName() == "calendar") { + if (status.version() < "0.7.0") { + e = projectElement; + } else { + e = projectElement.namedItem("calendars").toElement(); + } + if (!e.isNull()) { + debugPlanXml< cals; + KoXmlElement ce; + forEachElement(ce, e) { + if (ce.tagName() != "calendar") { + continue; + } // Load the calendar. // Referenced by resources - Calendar * child = new Calendar(); + Calendar *child = new Calendar(); child->setProject(this); - if (child->load(e, status)) { + if (child->load(ce, status)) { cals.append(child); // temporary, reorder later } else { // TODO: Complain about this errorPlan << "Failed to load calendar"; delete child; } - } else if (e.tagName() == "standard-worktime") { - // Load standard worktime - StandardWorktime * child = new StandardWorktime(); - if (child->load(e, status)) { - setStandardWorktime(child); - } else { - errorPlan << "Failed to load standard worktime"; - delete child; - } } - } - // calendars references calendars in arbitrary saved order - bool added = false; - do { - added = false; - QList lst; - while (!cals.isEmpty()) { - Calendar *c = cals.takeFirst(); - c->m_blockversion = true; - if (c->parentId().isEmpty()) { - addCalendar(c, status.baseCalendar()); // handle pre 0.6 version - added = true; - //debugPlan<<"added to project:"<name(); - } else { - Calendar *par = calendar(c->parentId()); - if (par) { - par->m_blockversion = true; - addCalendar(c, par); + // calendars references calendars in arbitrary saved order + bool added = false; + do { + added = false; + QList lst; + while (!cals.isEmpty()) { + Calendar *c = cals.takeFirst(); + c->m_blockversion = true; + if (c->parentId().isEmpty()) { + addCalendar(c, status.baseCalendar()); // handle pre 0.6 version added = true; - //debugPlan<<"added:"<name()<<" to parent:"<name(); - par->m_blockversion = false; + //debugPlan<<"added to project:"<name(); } else { - lst.append(c); // treat later - //debugPlan<<"treat later:"<name(); + Calendar *par = calendar(c->parentId()); + if (par) { + par->m_blockversion = true; + addCalendar(c, par); + added = true; + //debugPlan<<"added:"<name()<<" to parent:"<name(); + par->m_blockversion = false; + } else { + lst.append(c); // treat later + //debugPlan<<"treat later:"<name(); + } } + c->m_blockversion = false; } - c->m_blockversion = false; + cals = lst; + } while (added); + if (! cals.isEmpty()) { + errorPlan<<"All calendars not saved!"; } - cals = lst; - } while (added); - if (! cals.isEmpty()) { - errorPlan<<"All calendars not saved!"; + //debugPlan<<"Calendars<---"; } - //debugPlan<<"Calendars<---"; - status.setProgress(15); + KoXmlNode n; // Resource groups and resources, can reference calendars if (status.version() < "0.7.0") { - n = element.firstChild(); - for (; ! n.isNull(); n = n.nextSibling()) { - if (! n.isElement()) { - continue; - } - KoXmlElement e = n.toElement(); + forEachElement(e, projectElement) { if (e.tagName() == "resource-group") { + debugPlanXml<load(e, status)) { addResourceGroup(child); } else { // TODO: Complain about this errorPlan<resources()) { addResource(r); } } } else { - n = element.firstChild(); - for (; ! n.isNull(); n = n.nextSibling()) { - if (n.nodeName() == "resource-groups") { - break; - } - } - KoXmlElement e = n.toElement(); - if (e.tagName() == "resource-groups") { + e = projectElement.namedItem("resource-groups").toElement(); + if (!e.isNull()) { + debugPlanXml<load(ge, status)) { addResourceGroup(child); } else { // TODO: Complain about this delete child; } } } - // resources are not stored under groups anymore - n = element.firstChild(); - for (; ! n.isNull(); n = n.nextSibling()) { - if (n.nodeName() == "resources") { - KoXmlElement e = n.toElement(); - KoXmlElement re; - forEachElement(re, e) { - if (re.nodeName() != "resource") { - continue; - } - Resource *r = new Resource(); - if (r->load(re, status)) { - addResource(r); - } else { - errorPlan<<"Failed to load resource xml"; - delete r; - } + e = projectElement.namedItem("resources").toElement(); + if (!e.isNull()) { + debugPlanXml<load(re, status)) { + addResource(r); + } else { + errorPlan<<"Failed to load resource xml"; + delete r; } - break; } } // resource-group relations - n = element.firstChild(); - for (; ! n.isNull(); n = n.nextSibling()) { - if (n.nodeName() == "resource-group-relations") { - KoXmlElement e = n.toElement(); - KoXmlElement re; - forEachElement(re, e) { - if (re.nodeName() != "resource-group-relation") { - continue; - } - ResourceGroup *g = group(re.attribute("group-id")); - Resource *r = resource(re.attribute("resource-id")); - if (r && g) { - r->addParentGroup(g); - } else { - errorPlan<<"Failed to load resource-group-relation"; - } + e = projectElement.namedItem("resource-group-relations").toElement(); + if (!e.isNull()) { + debugPlanXml<addParentGroup(g); + } else { + errorPlan<<"Failed to load resource-group-relation"; } - break; } } - n = element.firstChild(); - for (; ! n.isNull(); n = n.nextSibling()) { - if (n.nodeName() == "required-resources") { - KoXmlElement e = n.toElement(); - KoXmlElement re; - forEachElement(re, e) { - if (re.nodeName() != "required-resource") { + e = projectElement.namedItem("resource-teams").toElement(); + if (!e.isNull()) { + debugPlanXml<resource(re.attribute("required-id")); - Resource *resource = this->resource(re.attribute("resource-id")); - if (required && resource) { - resource->addRequiredId(required->id()); - } else { - errorPlan<<"Failed to load required-resource"; + if (r == tm) { + errorPlan<<"resource-teams: a team cannot be a member of itself"; + continue; } + r->addTeamMemberId(tm->id()); + } else { + errorPlan<<"resource-teams: unhandled tag"<resource(re.attribute("required-id")); + Resource *resource = this->resource(re.attribute("resource-id")); + if (required && resource) { + resource->addRequiredId(required->id()); + } else { + errorPlan<<"Failed to load required-resource"; + } + } + } + e = projectElement.namedItem("external-appointments").toElement(); + if (!e.isNull()) { + debugPlanXml<resource(e.attribute("resource-id")); if (!resource) { errorPlan<<"Cannot find resource:"<clearExternalAppointments(projectId); // in case... AppointmentIntervalList lst; lst.loadXML(e, status); Appointment *a = new Appointment(); a->setIntervals(lst); a->setAuxcilliaryInfo(e.attribute("project-name", "Unknown")); resource->addExternalAppointment(projectId, a); } } } status.setProgress(20); // The main stuff - n = element.firstChild(); - for (; ! n.isNull(); n = n.nextSibling()) { - if (! n.isElement()) { - continue; - } - KoXmlElement e = n.toElement(); - if (e.tagName() == "project") { - //debugPlan<<"Sub project--->"; -/* // Load the subproject - Project * child = new Project(this); - if (child->load(e)) { - if (!addTask(child, this)) { - delete child; // TODO: Complain about this - } - } else { - // TODO: Complain about this - delete child; - }*/ - } else if (e.tagName() == "task") { + if (status.version() < "0.7.0") { + e = projectElement; + } else { + e = projectElement.namedItem("tasks").toElement(); + } + if (!e.isNull()) { + debugPlanXml<"; - // Load the task (and resourcerequests). // Depends on resources already loaded - Task * child = new Task(this); - if (child->load(e, status)) { + Task *child = new Task(this); + if (child->load(te, status)) { if (!addTask(child, this)) { delete child; // TODO: Complain about this + } else { + debugPlanXml<"; - // Load accounts - // References tasks - if (!m_accounts.load(e, *this)) { - errorPlan << "Failed to load accounts"; - } - } else if (e.tagName() == "relation") { - //debugPlan<<"Relation--->"; + if (status.version() < "0.7.0" && e.tagName() == "relation") { + debugPlanXml<load(e, *this)) { + Relation *child = new Relation(); + if (!child->load(e, status)) { // TODO: Complain about this errorPlan << "Failed to load relation"; delete child; } //debugPlan<<"Relation<---"; - } else if (e.tagName() == "project-schedules" || (status.version() < "0.7.0" && e.tagName() == "schedules")) { - //debugPlan<<"Project schedules & task appointments--->"; - // References tasks and resources - KoXmlNode sn = e.firstChild(); - for (; ! sn.isNull(); sn = sn.nextSibling()) { - if (! sn.isElement()) { - continue; - } - KoXmlElement el = sn.toElement(); - //debugPlan<loadXML(el, status)) { - if (add) - addScheduleManager(sm); - } else { - errorPlan << "Failed to load schedule manager"; - delete sm; + } else if (status.version() < "0.7.0" && e.tagName() == "resource-requests") { + // NOTE: Not supported: request to project (or sub-project) + Q_ASSERT(false); +#if 0 + // Load the resource request + // Handle multiple requests to same group gracefully (Not really allowed) + KoXmlElement req; + forEachElement(req, e) { + ResourceGroupRequest *r = m_requests.findGroupRequestById(req.attribute(QStringLiteral("group-id"))); + if (r) { + warnPlan<<"Multiple requests to same group, loading into existing group"; + if (! r->load(e, status)) { + errorPlan<<"Failed to load resource request"; } } else { - debugPlan<<"No schedule manager ?!"; - } - } - //debugPlan<<"Node schedules<---"; - } else if (e.tagName() == "resource-teams") { - //debugPlan<<"Resource teams--->"; - // References other resources - KoXmlNode tn = e.firstChild(); - for (; ! tn.isNull(); tn = tn.nextSibling()) { - if (! tn.isElement()) { - continue; - } - KoXmlElement el = tn.toElement(); - if (el.tagName() == "team") { - Resource *r = findResource(el.attribute("team-id")); - Resource *tm = findResource(el.attribute("member-id")); - if (r == 0 || tm == 0) { - errorPlan<<"resource-teams: cannot find resources"; - continue; - } - if (r == tm) { - errorPlan<<"resource-teams: a team cannot be a member of itself"; - continue; + r = new ResourceGroupRequest(); + if (r->load(e, status)) { + addRequest(r); + } else { + errorPlan<<"Failed to load resource request"; + delete r; } - r->addTeamMemberId(tm->id()); - } else { - errorPlan<<"resource-teams: unhandled tag"<loadXML(sn, status)) { + if (add) + addScheduleManager(sm); + } else { + errorPlan << "Failed to load schedule manager"; + delete sm; + } + } else { + debugPlan<<"No schedule manager ?!"; + } + } + //debugPlan<<"Node schedules<---"; + } + e = projectElement.namedItem("resource-teams").toElement(); + if (!e.isNull()) { + debugPlanXml<addTeamMemberId(tm->id()); + } + } + if (status.version() >= "0.7.0") { + e = projectElement.namedItem("relations").toElement(); + debugPlanXml<load(de, status)) { + // TODO: Complain about this + errorPlan << "Failed to load relation"; + delete child; + } + } + } + e = projectElement.namedItem("resourcegroup-requests").toElement(); + if (!e.isNull()) { + debugPlanXml<setId(requestId); + task->requests().addRequest(request); + debugPlanXml<<"Added group request:"<requests().groupRequest(re.attribute("request-id").toInt()); + if (!group) { + warnPlanXml< 0); + request->setId(requestId); + task->requests().addResourceRequest(request, group); + } else { + warnPlanXml<requests().resourceRequest(re.attribute("request-id").toInt()); + Resource *required = findResource(re.attribute("required-id")); + QList lst; + if (required && request->resource() != required) { + lst << required; + } + request->setRequiredResources(lst); + } + } + } //debugPlan<<"<---"; status.setProgress(90); return true; } -void Project::save(QDomElement &element, const XmlSaveContext &context) const +bool Project::loadSettings(KoXmlElement &element, XMLLoaderObject &status) { - QDomElement me = element.ownerDocument().createElement("project"); - element.appendChild(me); + Q_UNUSED(status) - me.setAttribute("name", m_name); - me.setAttribute("leader", m_leader); - me.setAttribute("id", m_id); - me.setAttribute("priority", QString::number(m_priority)); - me.setAttribute("description", m_description); - me.setAttribute("timezone", m_timeZone.isValid() ? QString::fromLatin1(m_timeZone.id()) : QString()); + m_useSharedResources = false; // default should be off in case old project NOTE: review - me.setAttribute("scheduling", constraintToString()); - me.setAttribute("start-time", m_constraintStartTime.toString(Qt::ISODate)); - me.setAttribute("end-time", m_constraintEndTime.toString(Qt::ISODate)); + KoXmlElement e; + forEachElement(e, element) { + debugPlanXml<setCurrencySymbol(e.attribute("currency-symbol", "")); + if (e.hasAttribute("currency-digits")) { + l->setMonetaryDecimalPlaces(e.attribute("currency-digits").toInt()); + } + QLocale::Language language = QLocale::AnyLanguage; + QLocale::Country country = QLocale::AnyCountry; + if (e.hasAttribute("language")) { + language = static_cast(e.attribute("language").toInt()); + } + if (e.hasAttribute("country")) { + country = static_cast(e.attribute("country").toInt()); + } + l->setCurrencyLocale(language, country); + } else if (e.tagName() == "shared-resources") { + m_useSharedResources = e.attribute("use", "0").toInt(); + m_sharedResourcesFile = e.attribute("file"); + m_sharedProjectsUrl = QUrl(e.attribute("projects-url")); + m_loadProjectsAtStartup = (bool)e.attribute("projects-loadatstartup", "0").toInt(); + } else if (e.tagName() == QLatin1String("workpackageinfo")) { + if (e.hasAttribute("check-for-workpackages")) { + m_workPackageInfo.checkForWorkPackages = e.attribute("check-for-workpackages").toInt(); + } + if (e.hasAttribute("retrieve-url")) { + m_workPackageInfo.retrieveUrl = QUrl(e.attribute("retrieve-url")); + } + if (e.hasAttribute("delete-after-retrieval")) { + m_workPackageInfo.deleteAfterRetrieval = e.attribute("delete-after-retrieval").toInt(); + } + if (e.hasAttribute("archive-after-retrieval")) { + m_workPackageInfo.archiveAfterRetrieval = e.attribute("archive-after-retrieval").toInt(); + } + if (e.hasAttribute("archive-url")) { + m_workPackageInfo.archiveUrl = QUrl(e.attribute("archive-url")); + } + if (e.hasAttribute("publish-url")) { + m_workPackageInfo.publishUrl = QUrl(e.attribute("publish-url")); + } + } else if (e.tagName() == QLatin1String("task-modules")) { + m_useLocalTaskModules = false; + QList urls; + for (KoXmlNode child = e.firstChild(); !child.isNull(); child = child.nextSibling()) { + KoXmlElement path = child.toElement(); + if (path.isNull()) { + continue; + } + QString s = path.attribute("url"); + if (!s.isEmpty()) { + QUrl url = QUrl::fromUserInput(s); + if (!urls.contains(url)) { + urls << url; + } + } + } + m_taskModules = urls; + // If set adds local path to taskModules() + setUseLocalTaskModules((bool)e.attribute("use-local-task-modules").toInt()); + } else if (e.tagName() == "standard-worktime") { + // Load standard worktime + StandardWorktime *child = new StandardWorktime(); + if (child->load(e, status)) { + setStandardWorktime(child); + } else { + errorPlanXml << "Failed to load standard worktime"; + delete child; + } + } + } + return true; +} +void Project::saveSettings(QDomElement &element, const XmlSaveContext &context) const +{ + Q_UNUSED(context) + + QDomElement settingsElement = element.ownerDocument().createElement("project-settings"); + element.appendChild(settingsElement); - m_wbsDefinition.saveXML(me); + m_wbsDefinition.saveXML(settingsElement); - QDomElement loc = me.ownerDocument().createElement("locale"); - me.appendChild(loc); + QDomElement loc = settingsElement.ownerDocument().createElement("locale"); + settingsElement.appendChild(loc); const Locale *l = locale(); loc.setAttribute("currency-symbol", l->currencySymbol()); loc.setAttribute("currency-digits", l->monetaryDecimalPlaces()); loc.setAttribute("language", l->currencyLanguage()); loc.setAttribute("country", l->currencyCountry()); - QDomElement share = me.ownerDocument().createElement("shared-resources"); - me.appendChild(share); + QDomElement share = settingsElement.ownerDocument().createElement("shared-resources"); + settingsElement.appendChild(share); share.setAttribute("use", m_useSharedResources); share.setAttribute("file", m_sharedResourcesFile); share.setAttribute("projects-url", QString(m_sharedProjectsUrl.toEncoded())); share.setAttribute("projects-loadatstartup", m_loadProjectsAtStartup); - QDomElement wpi = me.ownerDocument().createElement("workpackageinfo"); - me.appendChild(wpi); + QDomElement wpi = settingsElement.ownerDocument().createElement("workpackageinfo"); + settingsElement.appendChild(wpi); wpi.setAttribute("check-for-workpackages", m_workPackageInfo.checkForWorkPackages); wpi.setAttribute("retrieve-url", m_workPackageInfo.retrieveUrl.toString(QUrl::None)); wpi.setAttribute("delete-after-retrieval", m_workPackageInfo.deleteAfterRetrieval); wpi.setAttribute("archive-after-retrieval", m_workPackageInfo.archiveAfterRetrieval); wpi.setAttribute("archive-url", m_workPackageInfo.archiveUrl.toString(QUrl::None)); wpi.setAttribute("publish-url", m_workPackageInfo.publishUrl.toString(QUrl::None)); - QDomElement tm = me.ownerDocument().createElement("task-modules"); - me.appendChild(tm); + QDomElement tm = settingsElement.ownerDocument().createElement("task-modules"); + settingsElement.appendChild(tm); tm.setAttribute("use-local-task-modules", m_useLocalTaskModules); for (const QUrl &url : taskModules(false/*no local*/)) { QDomElement e = tm.ownerDocument().createElement("task-module"); tm.appendChild(e); e.setAttribute("url", url.toString()); } + // save standard worktime + if (m_standardWorktime) { + m_standardWorktime->save(settingsElement); + } +} - m_documents.save(me); +void Project::save(QDomElement &element, const XmlSaveContext &context) const +{ + debugPlanXml<save(me); + debugPlanXml<<"calendars:"<save(ce); + } } - // save standard worktime - if (m_standardWorktime) - m_standardWorktime->save(me); - - // save project resources, must be after calendars + // save project resources + debugPlanXml<<"resource-groups:"< git(m_resourceGroups); while (git.hasNext()) { git.next()->save(ge); } } + debugPlanXml<<"resources:"< rit(m_resources); while (rit.hasNext()) { rit.next()->save(re); } } + debugPlanXml<<"resource-group-relations"; if (!m_resources.isEmpty() && !m_resourceGroups.isEmpty()) { QDomElement e = me.ownerDocument().createElement("resource-group-relations"); me.appendChild(e); for (ResourceGroup *g : m_resourceGroups) { for (Resource *r : g->resources()) { QDomElement re = e.ownerDocument().createElement("resource-group-relation"); e.appendChild(re); re.setAttribute("group-id", g->id()); re.setAttribute("resource-id", r->id()); } } } + debugPlanXml<<"required-resources"; if (m_resources.count() > 1) { QList > requiredList; for (Resource *resource : m_resources) { for (const QString &required : resource->requiredIds()) { requiredList << std::pair(resource->id(), required); } } if (!requiredList.isEmpty()) { QDomElement e = me.ownerDocument().createElement("required-resources"); me.appendChild(e); for (const std::pair pair : requiredList) { QDomElement re = e.ownerDocument().createElement("required-resource"); e.appendChild(re); re.setAttribute("resource-id", pair.first); re.setAttribute("required-id", pair.second); } } } + // save resource teams + debugPlanXml<<"resource-teams"; + QDomElement el = me.ownerDocument().createElement("resource-teams"); + me.appendChild(el); + foreach (Resource *r, m_resources) { + if (r->type() != Resource::Type_Team) { + continue; + } + foreach (const QString &id, r->teamMemberIds()) { + QDomElement e = el.ownerDocument().createElement("team"); + el.appendChild(e); + e.setAttribute("team-id", r->id()); + e.setAttribute("member-id", id); + } + } + // save resource usage in other projects QList externals; for (Resource *resource : m_resources) { if (!resource->externalAppointmentList().isEmpty()) { externals << resource; } } + debugPlanXml<<"external-appointments"< projects = resource->externalProjects(); QMap::const_iterator it; for (it = projects.constBegin(); it != projects.constEnd(); ++it) { QDomElement re = e.ownerDocument().createElement("external-appointment"); e.appendChild(re); re.setAttribute("resource-id", resource->id()); re.setAttribute("project-id", it.key()); re.setAttribute("project-name", it.value()); resource->externalAppointments(it.key()).saveXML(e); } } } - } - // Only save parent relations - QListIterator it(m_dependParentNodes); - while (it.hasNext()) { - Relation *r = it.next(); - if (context.saveNode(r->parent()) && context.saveNode(r->child())) { - r->save(me, context); + debugPlanXml<<"tasks:"< 0) { + QDomElement e = me.ownerDocument().createElement("tasks"); + me.appendChild(e); + for (int i = 0; i < numChildren(); i++) { + childNode(i)->save(e, context); + } } - } - if (context.saveAll(this)) { - for (int i = 0; i < numChildren(); i++) - // Save all children - childNode(i)->save(me, context); - } - // Now we can save relations assuming no tasks have relations outside the project - QListIterator nodes(m_nodes); - while (nodes.hasNext()) { - nodes.next()->saveRelations(me, context); - } - if (context.saveAll(this)) { + // Now we can save relations assuming no tasks have relations outside the project + QDomElement deps = me.ownerDocument().createElement("relations"); + me.appendChild(deps); + QListIterator nodes(m_nodes); + while (nodes.hasNext()) { + const Node *n = nodes.next(); + n->saveRelations(deps, context); + } + debugPlanXml<<"task relations:"<saveXML(el); } } - // save resource teams - QDomElement el = me.ownerDocument().createElement("resource-teams"); - me.appendChild(el); - foreach (Resource *r, m_resources) { - if (r->type() != Resource::Type_Team) { - continue; + // save resource requests + QHash groups; + QHash resources; + for (Task *task : allTasks()) { + const ResourceRequestCollection &requests = task->requests(); + for (ResourceGroupRequest *gr : requests.requests()) { + groups.insert(task, gr); } - foreach (const QString &id, r->teamMemberIds()) { - QDomElement e = el.ownerDocument().createElement("team"); - el.appendChild(e); - e.setAttribute("team-id", r->id()); - e.setAttribute("member-id", id); + for (ResourceRequest *rr : requests.resourceRequests(false)) { + resources.insert(task, rr); + } + } + debugPlanXml<<"resourcegroup-requests:"<::const_iterator it; + for (it = groups.constBegin(); it != groups.constEnd(); ++it) { + if (!it.value()->group()) { + warnPlanXml<<"resourcegroup-request with no group"; + continue; + } + QDomElement ge = el.ownerDocument().createElement("resourcegroup-request"); + el.appendChild(ge); + ge.setAttribute("request-id", it.value()->id()); + ge.setAttribute("task-id", it.key()->id()); + ge.setAttribute("group-id", it.value()->group()->id()); + ge.setAttribute("units", QString::number(it.value()->units())); + } + } + QHash > required; // QHash> + debugPlanXml<<"resource-requests:"<::const_iterator it; + for (it = resources.constBegin(); it != resources.constEnd(); ++it) { + if (!it.value()->resource()) { + continue; + } + QDomElement re = el.ownerDocument().createElement("resource-request"); + el.appendChild(re); + re.setAttribute("request-id", it.value()->id()); + re.setAttribute("task-id", it.key()->id()); + if (it.value()->parent()) { + re.setAttribute("group-id", it.value()->parent()->group()->id()); + } + re.setAttribute("resource-id", it.value()->resource()->id()); + re.setAttribute("units", QString::number(it.value()->units())); + // collect required resources + for (Resource *r : it.value()->requiredResources()) { + required.insert(it.key(), std::pair(it.value(), r)); + } + } + } + debugPlanXml<<"required-resource-requests:"< >::const_iterator it; + for (it = required.constBegin(); it != required.constEnd(); ++it) { + QDomElement req = reqs.ownerDocument().createElement("required-resource"); + reqs.appendChild(req); + req.setAttribute("task-id", it.key()->id()); + req.setAttribute("request-id", it.value().first->id()); + req.setAttribute("required-id", it.value().second->id()); } } } } void Project::saveWorkPackageXML(QDomElement &element, const Node *node, long id) const { QDomElement me = element.ownerDocument().createElement("project"); element.appendChild(me); me.setAttribute("name", m_name); me.setAttribute("leader", m_leader); me.setAttribute("id", m_id); me.setAttribute("description", m_description); me.setAttribute("timezone", m_timeZone.isValid() ? QString::fromLatin1(m_timeZone.id()) : QString()); me.setAttribute("scheduling", constraintToString()); me.setAttribute("start-time", m_constraintStartTime.toString(Qt::ISODate)); me.setAttribute("end-time", m_constraintEndTime.toString(Qt::ISODate)); QListIterator git(m_resourceGroups); while (git.hasNext()) { git.next() ->saveWorkPackageXML(me, node->assignedResources(id)); } if (node == 0) { return; } node->saveWorkPackageXML(me, id); foreach (ScheduleManager *sm, m_managerIdMap) { if (sm->scheduleId() == id) { QDomElement el = me.ownerDocument().createElement("schedules"); me.appendChild(el); sm->saveWorkPackageXML(el, *node); break; } } } void Project::setParentSchedule(Schedule *sch) { QListIterator it = m_nodes; while (it.hasNext()) { it.next() ->setParentSchedule(sch); } } void Project::addResourceGroup(ResourceGroup *group, int index) { int i = index == -1 ? m_resourceGroups.count() : index; emit resourceGroupToBeAdded(group, i); m_resourceGroups.insert(i, group); setResourceGroupId(group); group->setProject(this); foreach (Resource *r, group->resources()) { setResourceId(r); r->setProject(this); } emit resourceGroupAdded(group); emit projectChanged(); } ResourceGroup *Project::takeResourceGroup(ResourceGroup *group) { int i = m_resourceGroups.indexOf(group); Q_ASSERT(i != -1); if (i == -1) { return 0; } emit resourceGroupToBeRemoved(group); ResourceGroup *g = m_resourceGroups.takeAt(i); Q_ASSERT(group == g); g->setProject(0); removeResourceGroupId(g->id()); foreach (Resource *r, g->resources()) { r->removeParentGroup(g); } emit resourceGroupRemoved(g); emit projectChanged(); return g; } QList &Project::resourceGroups() { return m_resourceGroups; } void Project::addResource(Resource *resource, int index) { int i = index == -1 ? m_resources.count() : index; emit resourceToBeAddedToProject(resource, i); setResourceId(resource); m_resources.insert(i, resource); resource->setProject(this); emit resourceAddedToProject(resource, i); emit projectChanged(); } bool Project::takeResource(Resource *resource) { int index = m_resources.indexOf(resource); emit resourceToBeRemovedFromProject(resource, index); bool result = removeResourceId(resource->id()); Q_ASSERT(result == true); if (!result) { warnPlan << "Could not remove resource with id" << resource->id(); } resource->removeRequests(); // not valid anymore for (ResourceGroup *g : resource->parentGroups()) { g->takeResource(resource); } bool rem = m_resources.removeOne(resource); Q_ASSERT(!m_resources.contains(resource)); emit resourceRemovedFromProject(resource, index); emit projectChanged(); return rem; } void Project::moveResource(ResourceGroup *group, Resource *resource) { if (resource->parentGroups().contains(group)) { return; } takeResource(resource); addResource(resource); group->addResource(resource); return; } QMap< QString, QString > Project::externalProjects() const { QMap< QString, QString > map; foreach (Resource *r, resourceList()) { for(QMapIterator it(r->externalProjects()); it.hasNext();) { it.next(); if (! map.contains(it.key())) { map[ it.key() ] = it.value(); } } } return map; } bool Project::addTask(Node* task, Node* position) { // we want to add a task at the given position. => the new node will // become next sibling right after position. if (0 == position) { return addSubTask(task, this); } //debugPlan<<"Add"<name()<<" after"<name(); // in case we want to add to the main project, we make it child element // of the root element. if (Node::Type_Project == position->type()) { return addSubTask(task, position); } // find the position // we have to tell the parent that we want to delete one of its children Node* parentNode = position->parentNode(); if (!parentNode) { debugPlan <<"parent node not found???"; return false; } int index = parentNode->findChildNode(position); if (-1 == index) { // ok, it does not exist debugPlan <<"Task not found???"; return false; } return addSubTask(task, index + 1, parentNode); } bool Project::addSubTask(Node* task, Node* parent) { // append task to parent return addSubTask(task, -1, parent); } bool Project::addSubTask(Node* task, int index, Node* parent, bool emitSignal) { // we want to add a subtask to the node "parent" at the given index. // If parent is 0, add to this Node *p = parent; if (0 == p) { p = this; } if (!registerNodeId(task)) { errorPlan << "Failed to register node id, can not add subtask: " << task->name(); return false; } int i = index == -1 ? p->numChildren() : index; if (emitSignal) emit nodeToBeAdded(p, i); p->insertChildNode(i, task); connect(this, &Project::standardWorktimeChanged, task, &Node::slotStandardWorktimeChanged); if (emitSignal) { emit nodeAdded(task); emit projectChanged(); if (p != this && p->numChildren() == 1) { emit nodeChanged(p, TypeProperty); } } return true; } void Project::takeTask(Node *node, bool emitSignal) { //debugPlan<name(); Node * parent = node->parentNode(); if (parent == 0) { debugPlan <<"Node must have a parent!"; return; } removeId(node->id()); if (emitSignal) emit nodeToBeRemoved(node); disconnect(this, &Project::standardWorktimeChanged, node, &Node::slotStandardWorktimeChanged); parent->takeChildNode(node); if (emitSignal) { emit nodeRemoved(node); emit projectChanged(); if (parent != this && parent->type() != Node::Type_Summarytask) { emit nodeChanged(parent, TypeProperty); } } } bool Project::canMoveTask(Node* node, Node *newParent, bool checkBaselined) { //debugPlan<name()<<" to"<name(); if (node == this) { return false; } if (checkBaselined) { if (node->isBaselined() || (newParent->type() != Node::Type_Summarytask && newParent->isBaselined())) { return false; } } Node *p = newParent; while (p && p != this) { if (! node->canMoveTo(p)) { return false; } p = p->parentNode(); } return true; } bool Project::moveTask(Node* node, Node *newParent, int newPos) { //debugPlan<name()<<" to"<name()<<","<parentNode(); int oldPos = oldParent->indexOf(node); int i = newPos < 0 ? newParent->numChildren() : newPos; if (oldParent == newParent && i == oldPos) { // no need to move to where it already is return false; } int newRow = i; if (oldParent == newParent && newPos > oldPos) { ++newRow; // itemmodels wants new row *before* node is removed from old position } debugPlan<name()<<"at"<indexOf(node)<<"to"<name()<numChildren() == 0) { emit nodeChanged(oldParent, TypeProperty); } if (newParent != this && newParent->numChildren() == 1) { emit nodeChanged(newParent, TypeProperty); } return true; } bool Project::canIndentTask(Node* node) { if (0 == node) { // should always be != 0. At least we would get the Project, // but you never know who might change that, so better be careful return false; } if (node->type() == Node::Type_Project) { //debugPlan<<"The root node cannot be indented"; return false; } // we have to find the parent of task to manipulate its list of children Node* parentNode = node->parentNode(); if (!parentNode) { return false; } if (parentNode->findChildNode(node) == -1) { errorPlan << "Tasknot found???"; return false; } Node *sib = node->siblingBefore(); if (!sib) { //debugPlan<<"new parent node not found"; return false; } if (node->findParentRelation(sib) || node->findChildRelation(sib)) { //debugPlan<<"Cannot have relations to parent"; return false; } return true; } bool Project::indentTask(Node* node, int index) { if (canIndentTask(node)) { Node * newParent = node->siblingBefore(); int i = index == -1 ? newParent->numChildren() : index; moveTask(node, newParent, i); //debugPlan; return true; } return false; } bool Project::canUnindentTask(Node* node) { if (0 == node) { // is always != 0. At least we would get the Project, but you // never know who might change that, so better be careful return false; } if (Node::Type_Project == node->type()) { //debugPlan<<"The root node cannot be unindented"; return false; } // we have to find the parent of task to manipulate its list of children // and we need the parent's parent too Node* parentNode = node->parentNode(); if (!parentNode) { return false; } Node* grandParentNode = parentNode->parentNode(); if (!grandParentNode) { //debugPlan<<"This node already is at the top level"; return false; } int index = parentNode->findChildNode(node); if (-1 == index) { errorPlan << "Tasknot found???"; return false; } return true; } bool Project::unindentTask(Node* node) { if (canUnindentTask(node)) { Node * parentNode = node->parentNode(); Node *grandParentNode = parentNode->parentNode(); int i = grandParentNode->indexOf(parentNode) + 1; if (i == 0) { i = grandParentNode->numChildren(); } moveTask(node, grandParentNode, i); //debugPlan; return true; } return false; } bool Project::canMoveTaskUp(Node* node) { if (node == 0) return false; // safety // we have to find the parent of task to manipulate its list of children Node* parentNode = node->parentNode(); if (!parentNode) { //debugPlan<<"No parent found"; return false; } if (parentNode->findChildNode(node) == -1) { errorPlan << "Tasknot found???"; return false; } if (node->siblingBefore()) { return true; } return false; } bool Project::moveTaskUp(Node* node) { if (canMoveTaskUp(node)) { moveTask(node, node->parentNode(), node->parentNode()->indexOf(node) - 1); return true; } return false; } bool Project::canMoveTaskDown(Node* node) { if (node == 0) return false; // safety // we have to find the parent of task to manipulate its list of children Node* parentNode = node->parentNode(); if (!parentNode) { return false; } if (parentNode->findChildNode(node) == -1) { errorPlan << "Tasknot found???"; return false; } if (node->siblingAfter()) { return true; } return false; } bool Project::moveTaskDown(Node* node) { if (canMoveTaskDown(node)) { moveTask(node, node->parentNode(), node->parentNode()->indexOf(node) + 1); return true; } return false; } Task *Project::createTask() { Task * node = new Task(); node->setId(uniqueNodeId()); reserveId(node->id(), node); return node; } Task *Project::createTask(const Task &def) { Task * node = new Task(def); node->setId(uniqueNodeId()); reserveId(node->id(), node); return node; } Node *Project::findNode(const QString &id) const { if (m_parent == 0) { if (nodeIdDict.contains(id)) { return nodeIdDict[ id ]; } return 0; } return m_parent->findNode(id); } bool Project::nodeIdentExists(const QString &id) const { return nodeIdDict.contains(id) || nodeIdReserved.contains(id); } QString Project::uniqueNodeId(int seed) const { Q_UNUSED(seed); QString ident = generateId(); while (nodeIdentExists(ident)) { ident = generateId(); } return ident; } QString Project::uniqueNodeId(const QList &existingIds, int seed) { QString id = uniqueNodeId(seed); while (existingIds.contains(id)) { id = uniqueNodeId(seed); } return id; } bool Project::removeId(const QString &id) { //debugPlan <<"id=" << id; if (m_parent) { return m_parent->removeId(id); } //debugPlan << "id=" << id<< nodeIdDict.contains(id); return nodeIdDict.remove(id); } void Project::reserveId(const QString &id, Node *node) { //debugPlan <<"id=" << id << node->name(); nodeIdReserved.insert(id, node); } bool Project::registerNodeId(Node *node) { nodeIdReserved.remove(node->id()); if (node->id().isEmpty()) { warnPlan << "Node id is empty, cannot register it"; return false; } Node *rn = findNode(node->id()); if (rn == 0) { //debugPlan <<"id=" << node->id() << node->name(); nodeIdDict.insert(node->id(), node); return true; } if (rn != node) { errorPlan << "Id already exists for different task: " << node->id(); return false; } //debugPlan<<"Already exists" <<"id=" << node->id() << node->name(); return true; } QList Project::allNodes(bool ordered, Node* parent) const { QList lst; if (ordered) { const Node *p = parent ? parent : this; foreach (Node *n, p->childNodeIterator()) { if (n->type() == Node::Type_Task || n->type() == Type_Milestone || n->type() == Node::Type_Summarytask) { lst << static_cast(n); lst += allNodes(ordered, n); } } } else { lst = nodeIdDict.values(); int me = lst.indexOf(const_cast(this)); if (me != -1) { lst.removeAt(me); } } return lst; } QList Project::allTasks(const Node *parent) const { QList lst; const Node *p = parent ? parent : this; foreach (Node *n, p->childNodeIterator()) { if (n->type() == Node::Type_Task || n->type() == Type_Milestone) { lst << static_cast(n); } lst += allTasks(n); } return lst; } bool Project::isStarted() const { const QList tasks = allTasks(); for (const Task *t : tasks) { if (t->isStarted()) { return true; } } return false; } bool Project::setResourceGroupId(ResourceGroup *group) { if (group == 0) { return false; } if (! group->id().isEmpty()) { ResourceGroup *g = findResourceGroup(group->id()); if (group == g) { return true; } else if (g == 0) { insertResourceGroupId(group->id(), group); return true; } } QString id = uniqueResourceGroupId(); group->setId(id); if (id.isEmpty()) { return false; } insertResourceGroupId(id, group); return true; } QString Project::uniqueResourceGroupId() const { QString id = generateId(); while (resourceGroupIdDict.contains(id)) { id = generateId(); } return id; } ResourceGroup *Project::group(const QString& id) { return findResourceGroup(id); } ResourceGroup *Project::groupByName(const QString& name) const { foreach (ResourceGroup *g, resourceGroupIdDict) { if (g->name() == name) { return g; } } return 0; } QList Project::autoAllocateResources() const { QList lst; foreach (Resource *r, m_resources) { if (r->autoAllocate()) { lst << r; } } return lst; } void Project::insertResourceId(const QString &id, Resource *resource) { resourceIdDict.insert(id, resource); } bool Project::removeResourceId(const QString &id) { return resourceIdDict.remove(id); } bool Project::setResourceId(Resource *resource) { if (resource == 0) { return false; } if (! resource->id().isEmpty()) { Resource *r = findResource(resource->id()); if (resource == r) { return true; } else if (r == 0) { insertResourceId(resource->id(), resource); return true; } } QString id = uniqueResourceId(); resource->setId(id); if (id.isEmpty()) { return false; } insertResourceId(id, resource); return true; } QString Project::uniqueResourceId() const { QString id = generateId(); while (resourceIdDict.contains(id)) { id = generateId(); } return id; } Resource *Project::resource(const QString& id) { return findResource(id); } Resource *Project::resourceByName(const QString& name) const { QHash::const_iterator it; for (it = resourceIdDict.constBegin(); it != resourceIdDict.constEnd(); ++it) { Resource *r = it.value(); if (r->name() == name) { Q_ASSERT(it.key() == r->id()); return r; } } return 0; } QStringList Project::resourceNameList() const { QStringList lst; for (Resource *r : m_resources) { lst << r->name(); } return lst; } EffortCostMap Project::plannedEffortCostPrDay(QDate start, QDate end, long id, EffortCostCalculationType typ) const { //debugPlan< it(childNodeIterator()); while (it.hasNext()) { ec += it.next() ->plannedEffortCostPrDay(start, end, id, typ); } return ec; } EffortCostMap Project::plannedEffortCostPrDay(const Resource *resource, QDate start, QDate end, long id, EffortCostCalculationType typ) const { //debugPlan< it(childNodeIterator()); while (it.hasNext()) { ec += it.next() ->plannedEffortCostPrDay(resource, start, end, id, typ); } return ec; } EffortCostMap Project::actualEffortCostPrDay(QDate start, QDate end, long id, EffortCostCalculationType typ) const { //debugPlan< it(childNodeIterator()); while (it.hasNext()) { ec += it.next() ->actualEffortCostPrDay(start, end, id, typ); } return ec; } EffortCostMap Project::actualEffortCostPrDay(const Resource *resource, QDate start, QDate end, long id, EffortCostCalculationType typ) const { //debugPlan< it(childNodeIterator()); while (it.hasNext()) { ec += it.next() ->actualEffortCostPrDay(resource, start, end, id, typ); } return ec; } // Returns the total planned effort for this project (or subproject) Duration Project::plannedEffort(long id, EffortCostCalculationType typ) const { //debugPlan; Duration eff; QListIterator it(childNodeIterator()); while (it.hasNext()) { eff += it.next() ->plannedEffort(id, typ); } return eff; } // Returns the total planned effort for this project (or subproject) on date Duration Project::plannedEffort(QDate date, long id, EffortCostCalculationType typ) const { //debugPlan; Duration eff; QListIterator it(childNodeIterator()); while (it.hasNext()) { eff += it.next() ->plannedEffort(date, id, typ); } return eff; } // Returns the total planned effort for this project (or subproject) upto and including date Duration Project::plannedEffortTo(QDate date, long id, EffortCostCalculationType typ) const { //debugPlan; Duration eff; QListIterator it(childNodeIterator()); while (it.hasNext()) { eff += it.next() ->plannedEffortTo(date, id, typ); } return eff; } // Returns the total actual effort for this project (or subproject) upto and including date Duration Project::actualEffortTo(QDate date) const { //debugPlan; Duration eff; QListIterator it(childNodeIterator()); while (it.hasNext()) { eff += it.next() ->actualEffortTo(date); } return eff; } // Returns the total planned effort for this project (or subproject) upto and including date double Project::plannedCostTo(QDate date, long id, EffortCostCalculationType typ) const { //debugPlan; double c = 0; QListIterator it(childNodeIterator()); while (it.hasNext()) { c += it.next() ->plannedCostTo(date, id, typ); } return c; } // Returns the total actual cost for this project (or subproject) upto and including date EffortCost Project::actualCostTo(long int id, QDate date) const { //debugPlan; EffortCost c; QListIterator it(childNodeIterator()); while (it.hasNext()) { c += it.next() ->actualCostTo(id, date); } return c; } Duration Project::budgetedWorkPerformed(QDate date, long id) const { //debugPlan; Duration e; foreach (Node *n, childNodeIterator()) { e += n->budgetedWorkPerformed(date, id); } return e; } double Project::budgetedCostPerformed(QDate date, long id) const { //debugPlan; double c = 0.0; foreach (Node *n, childNodeIterator()) { c += n->budgetedCostPerformed(date, id); } return c; } double Project::effortPerformanceIndex(QDate date, long id) const { //debugPlan; debugPlan< 0.0) { r = p / s; } debugPlan< date ? end : date), id); double budgetAtCompletion; double plannedCompleted; double budgetedCompleted; bool useEffort = false; //FIXME if (useEffort) { budgetAtCompletion = plan.totalEffort().toDouble(Duration::Unit_h); plannedCompleted = plan.effortTo(date).toDouble(Duration::Unit_h); //actualCompleted = actual.effortTo(date).toDouble(Duration::Unit_h); budgetedCompleted = budgetedWorkPerformed(date, id).toDouble(Duration::Unit_h); } else { budgetAtCompletion = plan.totalCost(); plannedCompleted = plan.costTo(date); budgetedCompleted = budgetedCostPerformed(date, id); } double c = 0.0; if (budgetAtCompletion > 0.0) { double percentageCompletion = budgetedCompleted / budgetAtCompletion; c = budgetAtCompletion * percentageCompletion; //?? debugPlan<name()<<","<<(parent?parent->name():"No parent"); int row = parent == 0 ? m_calendars.count() : parent->calendars().count(); if (index >= 0 && index < row) { row = index; } emit calendarToBeAdded(parent, row); calendar->setProject(this); if (parent == 0) { calendar->setParentCal(0); // in case m_calendars.insert(row, calendar); } else { calendar->setParentCal(parent, row); } if (calendar->isDefault()) { setDefaultCalendar(calendar); } setCalendarId(calendar); emit calendarAdded(calendar); emit projectChanged(); } void Project::takeCalendar(Calendar *calendar) { emit calendarToBeRemoved(calendar); removeCalendarId(calendar->id()); if (calendar == m_defaultCalendar) { m_defaultCalendar = 0; } if (calendar->parentCal() == 0) { int i = indexOf(calendar); if (i != -1) { m_calendars.removeAt(i); } } else { calendar->setParentCal(0); } emit calendarRemoved(calendar); calendar->setProject(0); emit projectChanged(); } int Project::indexOf(const Calendar *calendar) const { return m_calendars.indexOf(const_cast(calendar)); } Calendar *Project::calendar(const QString& id) const { return findCalendar(id); } Calendar *Project::calendarByName(const QString& name) const { foreach(Calendar *c, calendarIdDict) { if (c->name() == name) { return c; } } return 0; } const QList &Project::calendars() const { return m_calendars; } QList Project::allCalendars() const { return calendarIdDict.values(); } QStringList Project::calendarNames() const { QStringList lst; foreach(Calendar *c, calendarIdDict) { lst << c->name(); } return lst; } bool Project::setCalendarId(Calendar *calendar) { if (calendar == 0) { return false; } if (! calendar->id().isEmpty()) { Calendar *c = findCalendar(calendar->id()); if (calendar == c) { return true; } else if (c == 0) { insertCalendarId(calendar->id(), calendar); return true; } } QString id = uniqueCalendarId(); calendar->setId(id); if (id.isEmpty()) { return false; } insertCalendarId(id, calendar); return true; } QString Project::uniqueCalendarId() const { QString id = generateId(); while (calendarIdDict.contains(id)) { id = generateId(); } return id; } void Project::setDefaultCalendar(Calendar *cal) { if (m_defaultCalendar) { m_defaultCalendar->setDefault(false); } m_defaultCalendar = cal; if (cal) { cal->setDefault(true); } emit defaultCalendarChanged(cal); emit projectChanged(); } void Project::setStandardWorktime(StandardWorktime * worktime) { if (m_standardWorktime != worktime) { delete m_standardWorktime; m_standardWorktime = worktime; m_standardWorktime->setProject(this); emit standardWorktimeChanged(worktime); } } void Project::emitDocumentAdded(Node *node , Document *doc , int index) { emit documentAdded(node, doc, index); } void Project::emitDocumentRemoved(Node *node , Document *doc , int index) { emit documentRemoved(node, doc, index); } void Project::emitDocumentChanged(Node *node , Document *doc , int index) { emit documentChanged(node, doc, index); } bool Project::linkExists(const Node *par, const Node *child) const { if (par == 0 || child == 0 || par == child || par->isDependChildOf(child)) { return false; } foreach (Relation *r, par->dependChildNodes()) { if (r->child() == child) { return true; } } return false; } bool Project::legalToLink(const Node *par, const Node *child) const { //debugPlan<isDependChildOf(child)) { return false; } if (linkExists(par, child)) { return false; } bool legal = true; // see if par/child is related if (legal && (par->isParentOf(child) || child->isParentOf(par))) { legal = false; } if (legal) legal = legalChildren(par, child); if (legal) legal = legalParents(par, child); if (legal) { foreach (Node *p, par->childNodeIterator()) { if (! legalToLink(p, child)) { return false; } } } return legal; } bool Project::legalParents(const Node *par, const Node *child) const { bool legal = true; //debugPlan<name()<<" ("<numDependParentNodes()<<" parents)"<name()<<" ("<numDependChildNodes()<<" children)"; for (int i = 0; i < par->numDependParentNodes() && legal; ++i) { Node *pNode = par->getDependParentNode(i) ->parent(); if (child->isParentOf(pNode) || pNode->isParentOf(child)) { //debugPlan<<"Found:"<name()<<" is related to"<name(); legal = false; } else { legal = legalChildren(pNode, child); } if (legal) legal = legalParents(pNode, child); } return legal; } bool Project::legalChildren(const Node *par, const Node *child) const { bool legal = true; //debugPlan<name()<<" ("<numDependParentNodes()<<" parents)"<name()<<" ("<numDependChildNodes()<<" children)"; for (int j = 0; j < child->numDependChildNodes() && legal; ++j) { Node *cNode = child->getDependChildNode(j) ->child(); if (par->isParentOf(cNode) || cNode->isParentOf(par)) { //debugPlan<<"Found:"<name()<<" is related to"<name(); legal = false; } else { legal = legalChildren(par, cNode); } } return legal; } WBSDefinition &Project::wbsDefinition() { return m_wbsDefinition; } void Project::setWbsDefinition(const WBSDefinition &def) { //debugPlan; m_wbsDefinition = def; emit wbsDefinitionChanged(); emit projectChanged(); } QString Project::generateWBSCode(QList &indexes, bool sortable) const { QString code = m_wbsDefinition.projectCode(); if (sortable) { int fw = (nodeIdDict.count() / 10) + 1; QLatin1Char fc('0'); foreach (int index, indexes) { code += ".%1"; code = code.arg(QString::number(index), fw, fc); } //debugPlan<setCurrentSchedule(id); } emit currentScheduleChanged(); emit projectChanged(); } ScheduleManager *Project::scheduleManager(long id) const { foreach (ScheduleManager *sm, m_managers) { if (sm->scheduleId() == id) { return sm; } } return 0; } ScheduleManager *Project::scheduleManager(const QString &id) const { return m_managerIdMap.value(id); } ScheduleManager *Project::findScheduleManagerByName(const QString &name) const { //debugPlan; ScheduleManager *m = 0; foreach(ScheduleManager *sm, m_managers) { m = sm->findManager(name); if (m) { break; } } return m; } QList Project::allScheduleManagers() const { QList lst; foreach (ScheduleManager *sm, m_managers) { lst << sm; lst << sm->allChildren(); } return lst; } QString Project::uniqueScheduleName() const { //debugPlan; QString n = i18n("Plan"); bool unique = findScheduleManagerByName(n) == 0; if (unique) { return n; } n += " %1"; int i = 1; for (; true; ++i) { unique = findScheduleManagerByName(n.arg(i)) == 0; if (unique) { break; } } return n.arg(i); } void Project::addScheduleManager(ScheduleManager *sm, ScheduleManager *parent, int index) { int row = parent == 0 ? m_managers.count() : parent->childCount(); if (index >= 0 && index < row) { row = index; } if (parent == 0) { emit scheduleManagerToBeAdded(parent, row); m_managers.insert(row, sm); } else { emit scheduleManagerToBeAdded(parent, row); sm->setParentManager(parent, row); } if (sm->managerId().isEmpty()) { sm->setManagerId(uniqueScheduleManagerId()); } Q_ASSERT(! m_managerIdMap.contains(sm->managerId())); m_managerIdMap.insert(sm->managerId(), sm); emit scheduleManagerAdded(sm); emit projectChanged(); //debugPlan<<"Added:"<name()<<", now"<children()) { takeScheduleManager(s); } if (sm->scheduling()) { sm->stopCalculation(); } int index = -1; if (sm->parentManager()) { int index = sm->parentManager()->indexOf(sm); if (index >= 0) { emit scheduleManagerToBeRemoved(sm); sm->setParentManager(0); m_managerIdMap.remove(sm->managerId()); emit scheduleManagerRemoved(sm); emit projectChanged(); } } else { index = indexOf(sm); if (index >= 0) { emit scheduleManagerToBeRemoved(sm); m_managers.removeAt(indexOf(sm)); m_managerIdMap.remove(sm->managerId()); emit scheduleManagerRemoved(sm); emit projectChanged(); } } return index; } void Project::swapScheduleManagers(ScheduleManager *from, ScheduleManager *to) { emit scheduleManagersSwapped(from, to); } void Project::moveScheduleManager(ScheduleManager *sm, ScheduleManager *newparent, int newindex) { //debugPlan<name()<parentManager()) { m_managers.removeAt(indexOf(sm)); } sm->setParentManager(newparent, newindex); if (! newparent) { m_managers.insert(newindex, sm); } emit scheduleManagerMoved(sm, newindex); } bool Project::isScheduleManager(void *ptr) const { const ScheduleManager *sm = static_cast(ptr); if (indexOf(sm) >= 0) { return true; } foreach (ScheduleManager *p, m_managers) { if (p->isParentOf(sm)) { return true; } } return false; } ScheduleManager *Project::createScheduleManager(const QString &name) { //debugPlan<isBaselined()) { return true; } } return false; } Schedule *s = schedule(id); return s == 0 ? false : s->isBaselined(); } MainSchedule *Project::createSchedule(const QString& name, Schedule::Type type) { //debugPlan<<"No of schedules:"<setName(name); sch->setType(type); addMainSchedule(sch); return sch; } void Project::addMainSchedule(MainSchedule *sch) { if (sch == 0) { return; } //debugPlan<<"No of schedules:"<setId(i); sch->setNode(this); addSchedule(sch); } bool Project::removeCalendarId(const QString &id) { //debugPlan <<"id=" << id; return calendarIdDict.remove(id); } void Project::insertCalendarId(const QString &id, Calendar *calendar) { //debugPlan <<"id=" << id <<":" << calendar->name(); calendarIdDict.insert(id, calendar); } void Project::changed(Node *node, int property) { if (m_parent == 0) { Node::changed(node, property); // reset cache if (property != Node::TypeProperty) { // add/remove node is handled elsewhere emit nodeChanged(node, property); emit projectChanged(); } return; } Node::changed(node, property); } void Project::changed(ResourceGroup *group) { //debugPlan; emit resourceGroupChanged(group); emit projectChanged(); } void Project::changed(ScheduleManager *sm, int property) { emit scheduleManagerChanged(sm, property); emit projectChanged(); } void Project::changed(MainSchedule *sch) { //debugPlan<id(); emit scheduleChanged(sch); emit projectChanged(); } void Project::sendScheduleToBeAdded(const ScheduleManager *sm, int row) { emit scheduleToBeAdded(sm, row); } void Project::sendScheduleAdded(const MainSchedule *sch) { //debugPlan<id(); emit scheduleAdded(sch); emit projectChanged(); } void Project::sendScheduleToBeRemoved(const MainSchedule *sch) { //debugPlan<id(); emit scheduleToBeRemoved(sch); } void Project::sendScheduleRemoved(const MainSchedule *sch) { //debugPlan<id(); emit scheduleRemoved(sch); emit projectChanged(); } void Project::changed(Resource *resource) { emit resourceChanged(resource); emit projectChanged(); } void Project::changed(Calendar *cal) { emit calendarChanged(cal); emit projectChanged(); } void Project::changed(StandardWorktime *w) { emit standardWorktimeChanged(w); emit projectChanged(); } bool Project::addRelation(Relation *rel, bool check) { if (rel->parent() == 0 || rel->child() == 0) { return false; } if (check && !legalToLink(rel->parent(), rel->child())) { return false; } emit relationToBeAdded(rel, rel->parent()->numDependChildNodes(), rel->child()->numDependParentNodes()); rel->parent()->addDependChildNode(rel); rel->child()->addDependParentNode(rel); emit relationAdded(rel); emit projectChanged(); return true; } void Project::takeRelation(Relation *rel) { emit relationToBeRemoved(rel); rel->parent() ->takeDependChildNode(rel); rel->child() ->takeDependParentNode(rel); emit relationRemoved(rel); emit projectChanged(); } void Project::setRelationType(Relation *rel, Relation::Type type) { emit relationToBeModified(rel); rel->setType(type); emit relationModified(rel); emit projectChanged(); } void Project::setRelationLag(Relation *rel, const Duration &lag) { emit relationToBeModified(rel); rel->setLag(lag); emit relationModified(rel); emit projectChanged(); } QList Project::flatNodeList(Node *parent) { QList lst; Node *p = parent == 0 ? this : parent; //debugPlan<name()<childNodeIterator()) { lst.append(n); if (n->numChildren() > 0) { lst += flatNodeList(n); } } return lst; } void Project::setSchedulerPlugins(const QMap &plugins) { m_schedulerPlugins = plugins; debugPlan< Project::taskModules(bool includeLocal) const { if (!includeLocal && m_useLocalTaskModules) { QList lst = m_taskModules; lst.removeAll(m_localTaskModulesPath); return lst; } return m_taskModules; } void Project::setTaskModules(const QList modules, bool useLocalTaskModules) { m_taskModules = modules; m_useLocalTaskModules = useLocalTaskModules; if (m_useLocalTaskModules && m_localTaskModulesPath.isValid()) { m_taskModules.prepend(m_localTaskModulesPath); } emit taskModulesChanged(m_taskModules); } bool Project::useLocalTaskModules() const { return m_useLocalTaskModules; } void Project::setUseLocalTaskModules(bool value, bool emitChanged) { if (m_useLocalTaskModules) { m_taskModules.removeAll(m_localTaskModulesPath); } m_useLocalTaskModules = value; if (m_useLocalTaskModules && m_localTaskModulesPath.isValid()) { m_taskModules.prepend(m_localTaskModulesPath); } if (emitChanged) { emit taskModulesChanged(m_taskModules); } } void Project::setLocalTaskModulesPath(const QUrl &url) { m_taskModules.removeAll(m_localTaskModulesPath); m_localTaskModulesPath = url; if (m_useLocalTaskModules && url.isValid()) { m_taskModules.prepend(url); } emit taskModulesChanged(m_taskModules); } } //KPlato namespace diff --git a/src/libs/kernel/kptproject.h b/src/libs/kernel/kptproject.h index a0c25cd7..ec28d9b7 100644 --- a/src/libs/kernel/kptproject.h +++ b/src/libs/kernel/kptproject.h @@ -1,771 +1,774 @@ /* This file is part of the KDE project Copyright (C) 2001 Thomas Zander zander@kde.org Copyright (C) 2004 - 2010 Dag Andersen Copyright (C) 2007 Florian Piquemal Copyright (C) 2007 Alexis Ménard Copyright (C) 2019 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 KPTPROJECT_H #define KPTPROJECT_H #include "plankernel_export.h" #include "kptnode.h" #include "kptglobal.h" #include "kptaccount.h" #include "kptcalendar.h" #include "kptdatetime.h" #include "kptduration.h" #include "kptresource.h" #include "kptwbsdefinition.h" #include "kptconfigbase.h" #include #include #include #include #include /// The main namespace. namespace KPlato { class Locale; class Schedule; class StandardWorktime; class ScheduleManager; class XMLLoaderObject; class Task; class SchedulerPlugin; class KPlatoXmlLoaderBase; class XmlSaveContext; /** * Project is the main node in a project, it contains child nodes and * possibly sub-projects. A sub-project is just another instantiation of this * node however. * * A note on timezones: * To be able to handle resources working in different timezones and * to facilitate data exchange with other applications like PIMs or * and groupware servers, the project has a timezone that is used for * all datetimes in nodes and schedules. * By default the local timezone is used. * * A resources timezone is defined by the associated calendar. * * Note that a projects datetimes are always displayed/modified in the timezone * it was originally created, not necessarily in your current local timezone. */ class PLANKERNEL_EXPORT Project : public Node { Q_OBJECT public: explicit Project(Node *parent = 0); explicit Project(ConfigBase &config, Node *parent = 0); explicit Project(ConfigBase &config, bool useDefaultValues, Node *parent = 0); ~Project() override; /// Reference this project. void ref() { ++m_refCount; } /// De-reference this project. Deletes project of ref count <= 0 void deref(); /// Returns the node type. Can be Type_Project or Type_Subproject. int type() const override; /** * Calculate the schedules managed by the schedule manager * * @param sm Schedule manager */ void calculate(ScheduleManager &sm); /** * Re-calculate the schedules managed by the schedule manager * * @param sm Schedule manager * @param dt The datetime from when the schedule shall be re-calculated */ void calculate(ScheduleManager &sm, const DateTime &dt); DateTime startTime(long id = -1) const override; DateTime endTime(long id = -1) const override; /// Returns the calculated duration for schedule @p id Duration duration(long id = -1) const; using Node::duration; /** * Instead of using the expected duration, generate a random value using * the Distribution of each Task. This can be used for Monte-Carlo * estimation of Project duration. */ Duration *getRandomDuration() override; bool load(KoXmlElement &element, XMLLoaderObject &status) override; void save(QDomElement &element, const XmlSaveContext &context) const override; using Node::saveWorkPackageXML; /// Save a workpackage document containing @p node with schedule identity @p id void saveWorkPackageXML(QDomElement &element, const Node *node, long id) const; /** * Add the node @p task to the project, after node @p position * If @p position is zero or the project node, it will be added to this project. */ bool addTask(Node* task, Node* position); /** * Add the node @p task to the @p parent */ bool addSubTask(Node* task, Node* parent); /** * Add the node @p task to @p parent, in position @p index * If @p parent is zero, it will be added to this project. */ bool addSubTask(Node* task, int index, Node* parent, bool emitSignal = true); /** * Remove the @p node. * The node is not deleted. */ void takeTask(Node *node, bool emitSignal = true); bool canMoveTask(Node* node, Node *newParent, bool checkBaselined = false); bool moveTask(Node* node, Node *newParent, int newPos); bool canIndentTask(Node* node); bool indentTask(Node* node, int index = -1); bool canUnindentTask(Node* node); bool unindentTask(Node* node); bool canMoveTaskUp(Node* node); bool moveTaskUp(Node* node); bool canMoveTaskDown(Node* node); bool moveTaskDown(Node* node); /** * Create a task with a unique id. * The task is not added to the project. Do this with addSubTask(). */ Task *createTask(); /** * Create a copy of @p def with a unique id. * The task is not added to the project. Do this with addSubTask(). */ Task *createTask(const Task &def); /// @return true if any of the tasks has been started bool isStarted() const; int resourceGroupCount() const { return m_resourceGroups.count(); } QList &resourceGroups(); /// Adds the resource group to the project. virtual void addResourceGroup(ResourceGroup *resource, int index = -1); /** * Removes the resource group @p resource from the project. * The resource group is not deleted. */ ResourceGroup *takeResourceGroup(ResourceGroup *resource); int indexOf(ResourceGroup *resource) const { return m_resourceGroups.indexOf(resource); } ResourceGroup *resourceGroupAt(int pos) const { return m_resourceGroups.value(pos); } int numResourceGroups() const { return m_resourceGroups.count(); } /// Returns the resourcegroup with identity id. ResourceGroup *group(const QString& id); /// Returns the resource group with the matching name, 0 if no match is found. ResourceGroup *groupByName(const QString& name) const; /** * Adds the resource to the project * Always use this to add resources. */ void addResource(Resource *resource, int index = -1); /** * Removes the resource from the project and all resource groups. * The resource is not deleted. * Always use this to remove resources. */ bool takeResource(Resource *resource); /// Move @p resource to the new @p group. Requests are removed. void moveResource(ResourceGroup *group, Resource *resource); /// Returns the resource with identity id. Resource *resource(const QString& id); /// Returns the resource with matching name, 0 if no match is found. Resource *resourceByName(const QString& name) const; QStringList resourceNameList() const; /// Returns a list of all resources QList resourceList() const { return m_resources; } EffortCostMap plannedEffortCostPrDay(QDate start, QDate end, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All) const override; EffortCostMap plannedEffortCostPrDay(const Resource *resource, QDate start, QDate end, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All) const override; using Node::plannedEffort; /// Returns the total planned effort for this project (or subproject) Duration plannedEffort(long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All) const override; /// Returns the total planned effort for this project (or subproject) on date Duration plannedEffort(QDate date, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All ) const override; using Node::plannedEffortTo; /// Returns the planned effort up to and including date Duration plannedEffortTo(QDate date, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All ) const override; /// Returns the actual effort up to and including @p date Duration actualEffortTo(QDate date) const override; /** * Planned cost up to and including date * @param date The cost is calculated from the start of the project upto including date. * @param id Identity of the schedule to be used. * @param typ the type of calculation. * @sa EffortCostCalculationType */ double plannedCostTo(QDate date, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All ) const override; /** * Actual cost up to and including @p date * @param id Identity of the schedule to be used. * @param date The cost is calculated from the start of the project upto including date. */ EffortCost actualCostTo(long int id, QDate date) const override; EffortCostMap actualEffortCostPrDay(QDate start, QDate end, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All) const override; EffortCostMap actualEffortCostPrDay(const Resource *resource, QDate start, QDate end, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All) const override; double effortPerformanceIndex(QDate date, long id) const override; double schedulePerformanceIndex(QDate date, long id) const override; /// Returns the effort planned to be used to reach the actual percent finished Duration budgetedWorkPerformed(QDate date, long id = CURRENTSCHEDULE) const override; /// Returns the cost planned to be used to reach the actual percent finished double budgetedCostPerformed(QDate date, long id = CURRENTSCHEDULE) const override; /// Budgeted Cost of Work Scheduled (up to @p date) double bcws(QDate date, long id = BASELINESCHEDULE) const override; /// Budgeted Cost of Work Performed double bcwp(long id = BASELINESCHEDULE) const override; /// Budgeted Cost of Work Performed (up to @p date) double bcwp(QDate date, long id = BASELINESCHEDULE) const override; Calendar *defaultCalendar() const { return m_defaultCalendar; } void setDefaultCalendar(Calendar *cal); const QList &calendars() const; void addCalendar(Calendar *calendar, Calendar *parent = 0, int index = -1); void takeCalendar(Calendar *calendar); int indexOf(const Calendar *calendar) const; /// Returns the calendar with identity id. Calendar *calendar(const QString& id) const; /// Returns a list of all calendars QStringList calendarNames() const; /// Find calendar by name Calendar *calendarByName(const QString &name) const; void changed(Calendar *cal); QList allCalendars() const; /// Return number of calendars int calendarCount() const { return m_calendars.count(); } /// Return the calendar at @p index, 0 if index out of bounds Calendar *calendarAt(int index) const { return m_calendars.value(index); } /** * Defines the length of days, weeks, months and years * and the standard working week. * Used for estimation and calculation of effort, * and presentation in gantt chart. */ StandardWorktime *standardWorktime() { return m_standardWorktime; } void setStandardWorktime(StandardWorktime * worktime); void changed(StandardWorktime*); /// Check if a link exists between node @p par and @p child. bool linkExists(const Node *par, const Node *child) const; /// Check if node @p par can be linked to node @p child. bool legalToLink(const Node *par, const Node *child) const override; using Node::legalToLink; virtual const QHash &nodeDict() { return nodeIdDict; } /// Return a list of all nodes in the project (excluding myself) QList allNodes(bool ordered = false, Node *parent = nullptr) const; /// Return the number of all nodes in the project (excluding myself) int nodeCount() const { return nodeIdDict.count() - 1; } /// Return a list of all tasks and milestones int the wbs order QList allTasks(const Node *parent = 0) const; using Node::findNode; /// Find the node with identity id Node *findNode(const QString &id) const override; using Node::removeId; /// Remove the node with identity id from the registers bool removeId(const QString &id) override; /// Reserve @p id for the @p node virtual void reserveId(const QString &id, Node *node); /// Register @p node. The nodes id must be unique and non-empty. bool registerNodeId(Node *node); /// Create a unique id. QString uniqueNodeId(int seed = 1) const; /// Check if node @p id is used bool nodeIdentExists(const QString &id) const; /// Create a unique id. QString uniqueNodeId(const QList &existingIds, int seed = 1); ResourceGroup *findResourceGroup(const QString &id) const { if (resourceGroupIdDict.contains(id) ) return resourceGroupIdDict[ id ]; return 0; } /// Remove the resourcegroup with identity id from the register bool removeResourceGroupId(const QString &id) { if (resourceGroupIdDict.contains(id) ) return resourceGroupIdDict.remove(id); return false; } /// Insert the resourcegroup with identity id void insertResourceGroupId(const QString &id, ResourceGroup* group) { resourceGroupIdDict.insert(id, group); } /// Generate, set and insert unique id bool setResourceGroupId(ResourceGroup *group); /// returns a unique resourcegroup id QString uniqueResourceGroupId() const; /// Return a list of resources that will be allocated to new tasks QList autoAllocateResources() const; Resource *findResource(const QString &id) const { if (resourceIdDict.contains(id) ) return resourceIdDict[ id ]; return 0; } /// Remove the resource with identity id from the register bool removeResourceId(const QString &id); /// Insert the resource with identity id void insertResourceId(const QString &id, Resource *resource); /// Generate, set and insert unique id bool setResourceId(Resource *resource); /// returns a unique resource id QString uniqueResourceId() const; /// Find the calendar with identity id virtual Calendar *findCalendar(const QString &id) const { if (id.isEmpty() || !calendarIdDict.contains(id) ) return 0; return calendarIdDict[ id ]; } /// Remove the calendar with identity id from the register virtual bool removeCalendarId(const QString &id); /// Insert the calendar with identity id virtual void insertCalendarId(const QString &id, Calendar *calendar); /// Set and insert a unique id for calendar bool setCalendarId(Calendar *calendar); /// returns a unique calendar id QString uniqueCalendarId() const; /// Return reference to WBS Definition WBSDefinition &wbsDefinition(); /// Set WBS Definition to @p def void setWbsDefinition(const WBSDefinition &def); /// Generate WBS Code QString generateWBSCode(QList &indexes, bool sortable = false) const override; Accounts &accounts() { return m_accounts; } const Accounts &accounts() const { return m_accounts; } /** * Set current schedule to the schedule with identity @p id, for me and my children * Note that this is used (and may be changed) when calculating schedules */ void setCurrentSchedule(long id) override; /// Create new schedule with unique name and id of type Expected. MainSchedule *createSchedule(); /// Create new schedule with unique id. MainSchedule *createSchedule(const QString& name, Schedule::Type type); /// Add the schedule to the project. A fresh id will be generated for the schedule. void addMainSchedule(MainSchedule *schedule); /// Set parent schedule for my children void setParentSchedule(Schedule *sch) override; /// Find the schedule manager that manages the Schedule with @p id ScheduleManager *scheduleManager(long id) const; /// Find the schedule manager with @p id ScheduleManager *scheduleManager(const QString &id) const; /// Create a unique schedule name (This may later be changed by the user) QString uniqueScheduleName() const; /// Create a unique schedule manager identity QString uniqueScheduleManagerId() const; ScheduleManager *createScheduleManager(); ScheduleManager *createScheduleManager(const QString &name); /// Returns a list of all top level schedule managers QList scheduleManagers() const { return m_managers; } int numScheduleManagers() const { return m_managers.count(); } int indexOf(const ScheduleManager *sm) const { return m_managers.indexOf(const_cast(sm)); } bool isScheduleManager(void* ptr) const; void addScheduleManager(ScheduleManager *sm, ScheduleManager *parent = 0, int index = -1); int takeScheduleManager(ScheduleManager *sm); void moveScheduleManager(ScheduleManager *sm, ScheduleManager *newparent = 0, int newindex = -1); ScheduleManager *findScheduleManagerByName(const QString &name) const; /// Returns a list of all schedule managers QList allScheduleManagers() const; /// Return true if schedule with identity @p id is baselined bool isBaselined(long id = ANYSCHEDULED) const; void changed(ResourceGroup *group); void changed(Resource *resource); void changed(ScheduleManager *sm, int property = -1); void changed(MainSchedule *sch); void sendScheduleAdded(const MainSchedule *sch); void sendScheduleToBeAdded(const ScheduleManager *manager, int row); void sendScheduleRemoved(const MainSchedule *sch); void sendScheduleToBeRemoved(const MainSchedule *sch); /// Return the time zone used in this project QTimeZone timeZone() const { return m_timeZone; } /// Set the time zone to be used in this project void setTimeZone(const QTimeZone &tz) { m_timeZone = tz; } /** * Add a relation between the nodes specified in the relation rel. * Emits signals relationToBeAdded() before the relation is added, * and relationAdded() after it has been added. * @param rel The relation to be added. * @param check If true, the relation is checked for validity * @return true if successful. */ bool addRelation(Relation *rel, bool check=true); /** * Removes the relation @p rel without deleting it. * Emits signals relationToBeRemoved() before the relation is removed, * and relationRemoved() after it has been removed. */ void takeRelation(Relation *rel); /** * Modify the @p type of the @p relation. */ void setRelationType(Relation *relation, Relation::Type type); /** * Modify the @p lag of the @p relation. */ void setRelationLag(Relation *relation, const Duration &lag); void calcCriticalPathList(MainSchedule *cs); void calcCriticalPathList(MainSchedule *cs, Node *node); /** * Returns the list of critical paths for schedule @p id */ const QList< QList > *criticalPathList(long id = CURRENTSCHEDULE); QList criticalPath(long id = CURRENTSCHEDULE, int index = 0); /// Returns a flat list af all nodes QList flatNodeList(Node *parent = 0); void generateUniqueNodeIds(); void generateUniqueIds(); const ConfigBase &config() const { return m_config ? *m_config : emptyConfig; } /// Set configuration data void setConfig(ConfigBase *config) { m_config = config; } const Task &taskDefaults() const { return config().taskDefaults(); } /// Return locale. (Used for currency, everything else is from KGlobal::locale) Locale *locale() { return const_cast(config()).locale(); } /// Return locale. (Used for currency, everything else is from KGlobal::locale) const Locale *locale() const { return config().locale(); } /// Signal that locale data has changed void emitLocaleChanged(); void setSchedulerPlugins(const QMap &plugins); const QMap &schedulerPlugins() const { return m_schedulerPlugins; } void initiateCalculation(MainSchedule &sch) override; void initiateCalculationLists(MainSchedule &sch) override; void finishCalculation(ScheduleManager &sm); void adjustSummarytask() override; /// Increments progress and emits signal sigProgress() void incProgress(); /// Emits signal maxProgress() void emitMaxProgress(int value); bool stopcalculation; /// return a map of all external projects QMap externalProjects() const; void emitDocumentAdded(Node*, Document*, int index) override; void emitDocumentRemoved(Node*, Document*, int index) override; void emitDocumentChanged(Node*, Document*, int index) override; bool useSharedResources() const; void setUseSharedResources(bool on); bool isSharedResourcesLoaded() const; void setSharedResourcesLoaded(bool on); void setSharedResourcesFile(const QString &file); QString sharedResourcesFile() const; void setSharedProjectsUrl(const QUrl &url); QUrl sharedProjectsUrl() const; void setLoadProjectsAtStartup(bool value); bool loadProjectsAtStartup() const; QList taskModules(bool includeLocal = true) const; void setTaskModules(const QList modules, bool useLocalTaskModules); bool useLocalTaskModules() const; void setUseLocalTaskModules(bool value, bool emitChanged = true); void setLocalTaskModulesPath(const QUrl &url); public Q_SLOTS: /// Sets m_progress to @p progress and emits signal sigProgress() /// If @p sm is not 0, progress is also set for the schedule manager void setProgress(int progress, KPlato::ScheduleManager *sm = 0); /// Sets m_maxprogress to @p max and emits signal maxProgress() /// If @p sm is not 0, max progress is also set for the schedule manager void setMaxProgress(int max, KPlato::ScheduleManager *sm = 0); void swapScheduleManagers(KPlato::ScheduleManager *from, KPlato::ScheduleManager *to); Q_SIGNALS: void scheduleManagersSwapped(KPlato::ScheduleManager *from, KPlato::ScheduleManager *to); /// Emitted when the project is about to be deleted (The destroyed signal is disabled) void aboutToBeDeleted(); /// Emitted when anything in the project is changed (use with care) void projectChanged(); /// Emitted when the WBS code definition has changed. This may change all nodes. void wbsDefinitionChanged(); /// Emitted when a schedule has been calculated void projectCalculated(KPlato::ScheduleManager *sm); /// Emitted when the pointer to the current schedule has been changed void currentScheduleChanged(); /// Use to show progress during calculation void sigProgress(int); /// Use to set the maximum progress (minimum always 0) void maxProgress(int); /// Emitted when calculation starts void sigCalculationStarted(KPlato::Project *project, KPlato::ScheduleManager *sm); /// Emitted when calculation is finished void sigCalculationFinished(KPlato::Project *project, KPlato::ScheduleManager *sm); /// This signal is emitted when one of the nodes members is changed. void nodeChanged(KPlato::Node*, int); /// This signal is emitted when the node is to be added to the project. void nodeToBeAdded(KPlato::Node*, int); /// This signal is emitted when the node has been added to the project. void nodeAdded(KPlato::Node*); /// This signal is emitted when the node is to be removed from the project. void nodeToBeRemoved(KPlato::Node*); /// This signal is emitted when the node has been removed from the project. void nodeRemoved(KPlato::Node*); /// This signal is emitted when the node is to be moved up, moved down, indented or unindented. void nodeToBeMoved(KPlato::Node* node, int pos, KPlato::Node* newParent, int newPos); /// This signal is emitted when the node has been moved up, moved down, indented or unindented. void nodeMoved(KPlato::Node*); /// This signal is emitted when a document is added void documentAdded(KPlato::Node*, KPlato::Document*, int index); /// This signal is emitted when a document is removed void documentRemoved(KPlato::Node*, KPlato::Document*, int index); /// This signal is emitted when a document is changed void documentChanged(KPlato::Node*, KPlato::Document*, int index); void resourceGroupChanged(KPlato::ResourceGroup *group); void resourceGroupAdded(const KPlato::ResourceGroup *group); void resourceGroupToBeAdded(const KPlato::ResourceGroup *group, int row); void resourceGroupRemoved(const KPlato::ResourceGroup *group); void resourceGroupToBeRemoved(const KPlato::ResourceGroup *group); void resourceToBeAddedToGroup(const KPlato::ResourceGroup *group, int row); void resourceAddedToGroup(const KPlato::ResourceGroup *group, int row); void resourceToBeRemovedFromGroup(const KPlato::ResourceGroup *group, int row); void resourceRemovedFromGroup(const KPlato::ResourceGroup *group, int row); void resourceChanged(KPlato::Resource *resource); void resourceAddedToProject(const KPlato::Resource *resource, int row); void resourceToBeAddedToProject(const KPlato::Resource* resource, int row); void resourceRemovedFromProject(const KPlato::Resource *resource, int row); void resourceToBeRemovedFromProject(const KPlato::Resource *resource, int row); void scheduleManagerChanged(KPlato::ScheduleManager *sch, int property = -1); void scheduleManagerAdded(const KPlato::ScheduleManager *sch); void scheduleManagerToBeAdded(const KPlato::ScheduleManager *sch, int row); void scheduleManagerRemoved(const KPlato::ScheduleManager *sch); void scheduleManagerToBeRemoved(const KPlato::ScheduleManager *sch); void scheduleManagerMoved(const KPlato::ScheduleManager *sch, int row); void scheduleManagerToBeMoved(const KPlato::ScheduleManager *sch); void scheduleChanged(KPlato::MainSchedule *sch); void scheduleToBeAdded(const KPlato::ScheduleManager *manager, int row); void scheduleAdded(const KPlato::MainSchedule *sch); void scheduleToBeRemoved(const KPlato::MainSchedule *sch); void scheduleRemoved(const KPlato::MainSchedule *sch); // void currentViewScheduleIdChanged(long id); void calendarChanged(KPlato::Calendar *cal); void calendarToBeAdded(const KPlato::Calendar *cal, int row); void calendarAdded(const KPlato::Calendar *cal); void calendarToBeRemoved(const KPlato::Calendar *cal); void calendarRemoved(const KPlato::Calendar *cal); /** * Emitted when the default calendar pointer has changed * @param cal The new default calendar. May be 0. */ void defaultCalendarChanged(KPlato::Calendar *cal); /** * Emitted when the standard worktime has been changed. */ void standardWorktimeChanged(KPlato::StandardWorktime*); /// Emitted when the relation @p rel is about to be added. void relationToBeAdded(KPlato::Relation *rel, int parentIndex, int childIndex); /// Emitted when the relation @p rel has been added. void relationAdded(KPlato::Relation *rel); /// Emitted when the relation @p rel is about to be removed. void relationToBeRemoved(KPlato::Relation *rel); /// Emitted when the relation @p rel has been removed. void relationRemoved(KPlato::Relation *rel); /// Emitted when the relation @p rel shall be modified. void relationToBeModified(KPlato::Relation *rel); /// Emitted when the relation @p rel has been modified. void relationModified(KPlato::Relation *rel); /// Emitted when locale data has changed void localeChanged(); void taskModulesChanged(const QList &modules); protected: /// Calculate the schedule. void calculate(Schedule *scedule); /// Calculate current schedule void calculate(); /// Re-calculate the schedule from @p dt void calculate(Schedule *scedule, const DateTime &dt); /// Calculate current schedule from @p dt (Always calculates forward) void calculate(const DateTime &dt); /// Calculate critical path bool calcCriticalPath(bool fromEnd) override; /// Prepare task lists for scheduling void tasksForward(); /// Prepare task lists for scheduling void tasksBackward(); + void saveSettings(QDomElement &element, const XmlSaveContext &context) const; + bool loadSettings(KoXmlElement &element, XMLLoaderObject &status); + protected: friend class KPlatoXmlLoaderBase; using Node::changed; void changed(Node *node, int property = -1) override; Accounts m_accounts; QList m_resourceGroups; QList m_resources; QList m_calendars; Calendar * m_defaultCalendar; StandardWorktime *m_standardWorktime; DateTime calculateForward(int use) override; DateTime calculateBackward(int use) override; DateTime scheduleForward(const DateTime &earliest, int use) override; DateTime scheduleBackward(const DateTime &latest, int use) override; DateTime checkStartConstraints(const DateTime &dt) const; DateTime checkEndConstraints(const DateTime &dt) const; bool legalParents(const Node *par, const Node *child) const; bool legalChildren(const Node *par, const Node *child) const; #ifndef PLAN_NLOGDEBUG private: static bool checkParent(Node *n, const QList &list, QList &checked); static bool checkChildren(Node *n, const QList &list, QList &checked); #endif private: void init(); QHash resourceGroupIdDict; QHash resourceIdDict; QHash nodeIdDict; QMap nodeIdReserved; QMap calendarIdDict; QMap m_managerIdMap; QList m_managers; QTimeZone m_timeZone; WBSDefinition m_wbsDefinition; ConfigBase emptyConfig; QPointer m_config; // this one is not owned by me, don't delete int m_progress; QMap m_schedulerPlugins; int m_refCount; // make it possible to use the project by different threads QList m_hardConstraints; QList m_softConstraints; QMultiMap m_terminalNodes; bool m_useSharedResources; bool m_sharedResourcesLoaded; QString m_sharedResourcesFile; QUrl m_sharedProjectsUrl; bool m_loadProjectsAtStartup; public: class WorkPackageInfo { public: WorkPackageInfo() : checkForWorkPackages(false), deleteAfterRetrieval(true), archiveAfterRetrieval(false) {} bool checkForWorkPackages; QUrl retrieveUrl; bool deleteAfterRetrieval; bool archiveAfterRetrieval; QUrl archiveUrl; QUrl publishUrl; bool operator!=(const WorkPackageInfo &o) { return !operator==(o); } bool operator==(const WorkPackageInfo &o) { return checkForWorkPackages == o.checkForWorkPackages && deleteAfterRetrieval == o.deleteAfterRetrieval && archiveAfterRetrieval == o.archiveAfterRetrieval && retrieveUrl == o.retrieveUrl && archiveUrl == o.archiveUrl && publishUrl == o.publishUrl; } }; void setWorkPackageInfo(const WorkPackageInfo &wpInfo) { m_workPackageInfo = wpInfo; } WorkPackageInfo workPackageInfo() const { return m_workPackageInfo; } private: WorkPackageInfo m_workPackageInfo; QList m_taskModules; bool m_useLocalTaskModules; QUrl m_localTaskModulesPath; }; } //KPlato namespace #endif diff --git a/src/libs/kernel/kptrelation.cpp b/src/libs/kernel/kptrelation.cpp index e7255a4c..426d30de 100644 --- a/src/libs/kernel/kptrelation.cpp +++ b/src/libs/kernel/kptrelation.cpp @@ -1,212 +1,214 @@ /* This file is part of the KDE project Copyright (C) 2001 Thomas zander Copyright (C) 2004, 2012 Dag Andersen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ // clazy:excludeall=qstring-arg #include "kptrelation.h" #include "kptnode.h" #include "kptproject.h" #include "XmlSaveContext.h" +#include "kptxmlloaderobject.h" #include "kptdebug.h" #include #include #include namespace KPlato { Relation::Relation(Node *parent, Node *child, Type type, Duration lag) { m_parent=parent; m_child=child; m_type=type; m_lag=lag; //debugPlan<parent(); m_child=rel->child(); m_type=rel->type(); m_lag=rel->lag(); //debugPlan<name():"none")<<" child:"<<(m_child ? m_child->name():"None"); if (m_parent) m_parent->takeDependChildNode(this); if (m_child) m_child->takeDependParentNode(this); } void Relation::setType(Type type) { m_type=type; } void Relation::setType(const QString &type) { int t = typeList().indexOf(type); if (t == -1) { t = FinishStart; } m_type = static_cast(t); } QString Relation::typeToString(bool trans) const { return typeList(trans).at(m_type); } QStringList Relation::typeList(bool trans) { //NOTE: must match enum QStringList lst; lst << (trans ? i18n("Finish-Start") : "Finish-Start"); lst << (trans ? i18n("Finish-Finish") : "Finish-Finish"); lst << (trans ? i18n("Start-Start") : "Start-Start"); return lst; } void Relation::setParent(Node* node) { m_parent = node; } void Relation::setChild(Node* node) { m_child = node; } -bool Relation::load(KoXmlElement &element, Project &project) { +bool Relation::load(KoXmlElement &element, XMLLoaderObject &status) { + const Project &project = status.project(); m_parent = project.findNode(element.attribute("parent-id")); if (m_parent == 0) { return false; } m_child = project.findNode(element.attribute("child-id")); if (m_child == 0) { return false; } if (m_child == m_parent) { debugPlan<<"child == parent"; return false; } if (m_child == m_parent) { debugPlan<<"child == parent"; return false; } if (!m_parent->legalToLink(m_child)) return false; setType(element.attribute("type")); m_lag = Duration::fromString(element.attribute("lag")); if (!m_parent->addDependChildNode(this)) { errorPlan<<"Failed to add relation: Child="<name()<<" parent="<name()<addDependParentNode(this)) { m_parent->takeDependChildNode(this); errorPlan<<"Failed to add relation: Child="<name()<<" parent="<name()<id()); me.setAttribute("child-id", m_child->id()); QString type = "Finish-Start"; switch (m_type) { case FinishStart: type = "Finish-Start"; break; case FinishFinish: type = "Finish-Finish"; break; case StartStart: type = "Start-Start"; break; default: break; } me.setAttribute("type", type); me.setAttribute("lag", m_lag.toString()); } #ifndef NDEBUG void Relation::printDebug(const QByteArray& _indent) { QString indent = _indent; indent += " "; debugPlan<name(); debugPlan<name(); debugPlan<name()<<"->"<name()< 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 KPTRELATION_H #define KPTRELATION_H #include "plankernel_export.h" #include "kptduration.h" #include #include class QDomElement; /// The main namespace namespace KPlato { class Node; class Project; class XmlSaveContext; +class XMLLoaderObject; /** * The relation class couples 2 nodes together which are dependent on each other. * If for example you have a project to build a house, the node that represents the * adding of the roof is dependent on the node that represents the building of the walls; * the roof can't be put up if the walls are not there yet. * We actually have a number of relationtypes so this relation can be used in different manners. */ class PLANKERNEL_EXPORT Relation { public: enum Type { FinishStart, FinishFinish, StartStart }; Relation(Node *parent, Node *child, Type type, Duration lag); explicit Relation(Node *parent=0, Node *child=0, Type type=FinishStart); explicit Relation(Relation *rel); /** * When deleted the relation will remove itself from * the parent- and child nodes lists */ virtual ~Relation(); /// Set relation type void setType(Type); /// Set relation type void setType(const QString &type); /// Return relation type Type type() const { return m_type; } /// Return relation type as a string. Translated if @p trans = true. QString typeToString(bool trans = false) const; /// Convert @p type to a valid relation type static Type typeFromString(const QString &type); /// Return a stringlist of relation types. Translated if @p trans = true static QStringList typeList(bool trans = false); /** * Returns the lag. * The lag of a relation is the time it takes between the parent starting/stopping * and the start of the child. */ const Duration &lag() const { return m_lag; } /// Set relaion time lag void setLag(Duration lag) { m_lag = lag; } /** * @return The parent dependent node. */ Node *parent() const { return m_parent; } void setParent(Node *node); /** * @return The child dependent node. */ Node *child() const { return m_child; } void setChild(Node *node); - bool load(KoXmlElement &element, Project &project); + bool load(KoXmlElement &element, XMLLoaderObject &status); void save(QDomElement &element, const XmlSaveContext &context) const; protected: // variables Node *m_parent; Node *m_child; Type m_type; Duration m_lag; private: QString m_parentId; #ifndef NDEBUG public: void printDebug(const QByteArray& indent); #endif }; class PLANKERNEL_EXPORT ProxyRelation : public Relation { public: ProxyRelation(Node *parent, Node *child, Relation::Type type, Duration lag) : Relation(parent, child, type, lag) {} ~ProxyRelation() override { m_parent = 0; m_child = 0;} }; } //KPlato namespace PLANKERNEL_EXPORT QDebug operator<<(QDebug dbg, const KPlato::Relation* r); PLANKERNEL_EXPORT QDebug operator<<(QDebug dbg, const KPlato::Relation& r); #endif diff --git a/src/libs/kernel/kptresourcerequest.cpp b/src/libs/kernel/kptresourcerequest.cpp index 951a56a2..4c00f1e5 100644 --- a/src/libs/kernel/kptresourcerequest.cpp +++ b/src/libs/kernel/kptresourcerequest.cpp @@ -1,1099 +1,1244 @@ /* 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 * Copyright (C) 2019 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 "kptresourcerequest.h" #include "ResourceGroup.h" #include "Resource.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 #include using namespace KPlato; ResourceRequest::ResourceRequest(Resource *resource, int units) - : m_resource(resource), + : m_id(0), + m_resource(resource), m_units(units), - m_parent(0), + m_collection(nullptr), + m_parent(nullptr), m_dynamic(false) { if (resource) { m_required = resource->requiredResources(); } //debugPlan<<"("<name() : QString("None")); } ResourceRequest::ResourceRequest(const ResourceRequest &r) - : m_resource(r.m_resource), + : m_id(r.m_id), + m_resource(r.m_resource), m_units(r.m_units), - m_parent(0), + m_collection(nullptr), + m_parent(nullptr), m_dynamic(r.m_dynamic), m_required(r.m_required) { } ResourceRequest::~ResourceRequest() { - //debugPlan<<"("<name() : QString("None")); - if (m_resource) + if (m_resource) { m_resource->unregisterRequest(this); + } m_resource = 0; - qDeleteAll(m_teamMembers); + if (m_parent) { + m_parent->removeResourceRequest(this); + } + if (m_collection) { + m_collection->removeResourceRequest(this); + } +} + +ResourceRequestCollection *ResourceRequest::collection() const +{ + return m_collection; +} + +void ResourceRequest::setCollection(ResourceRequestCollection *collection) +{ + m_collection = collection; +} + +int ResourceRequest::id() const +{ + return m_id; +} + +void ResourceRequest::setId(int id) +{ + m_id = id; } void ResourceRequest::registerRequest() { if (m_resource) m_resource->registerRequest(this); } void ResourceRequest::unregisterRequest() { if (m_resource) m_resource->unregisterRequest(this); } bool ResourceRequest::load(KoXmlElement &element, Project &project) { - //debugPlan; + debugPlanXml<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(Node::ResourceRequestProperty); } } 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; } ///////// ResourceGroupRequest::ResourceGroupRequest(ResourceGroup *group, int units) - : m_group(group), m_units(units), m_parent(0) { - + : m_id(0) + , m_group(group) + , m_units(units) + , m_parent(nullptr) +{ //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) + : m_id(g.m_id) + , 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(); + for (ResourceRequest *r : m_resourceRequests) { + r->setParent(nullptr); + } + if (m_parent) { + m_parent->takeRequest(this); } } +int ResourceGroupRequest::id() const +{ + return m_id; +} + +void ResourceGroupRequest::setId(int id) +{ + m_id = id; +} + 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) { - qInfo()<resource(); 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; + debugPlanXml<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::removeResourceRequest(ResourceRequest *request) +{ + m_resourceRequests.removeAll(request); + 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<setCollection(nullptr); + } + for (ResourceGroupRequest *r : m_groupRequests) { + r->setParent(nullptr); + } + qDeleteAll(m_resourceRequests); // removes themselves from possible group + qDeleteAll(m_groupRequests); +} + +void ResourceRequestCollection::removeRequests() +{ + const QList resourceRequests = m_resourceRequests.values(); + for (ResourceRequest *r : resourceRequests) { + removeResourceRequest(r); + } + const QList groupRequests = m_groupRequests.values(); + for (ResourceGroupRequest *r : groupRequests) { + takeRequest(r); } } +Task *ResourceRequestCollection::task() const +{ + return m_task; +} + +void ResourceRequestCollection::setTask(Task *t) +{ + m_task = t; +} + void ResourceRequestCollection::addRequest(ResourceGroupRequest *request) { Q_ASSERT(request->group()); - foreach (ResourceGroupRequest *r, m_requests) { + foreach (ResourceGroupRequest *r, m_groupRequests) { if (r->group() == request->group()) { errorPlan<<"Request to this group already exists"; errorPlan<<"Task:"<name()<<"Group:"<group()->name(); Q_ASSERT(false); } } - m_requests.append(request); + int id = request->id(); + if (id == 0) { + int i = 1; + while (m_groupRequests.contains(i)) { + ++i; + } + request->setId(i); + } + Q_ASSERT(!m_groupRequests.contains(request->id())); + m_groupRequests.insert(request->id(), request); if (!request->group()->requests().contains(request)) { request->group()->registerRequest(request); } request->setParent(this); changed(); } +int ResourceRequestCollection::takeRequest(ResourceGroupRequest *request) +{ + Q_ASSERT(m_groupRequests.contains(request->id())); + Q_ASSERT(m_groupRequests.value(request->id()) == request); + int i = m_groupRequests.values().indexOf(request); + m_groupRequests.remove(request->id()); + changed(); + return i; +} + +ResourceGroupRequest *ResourceRequestCollection::groupRequest(int id) const +{ + return m_groupRequests.value(id); +} + ResourceGroupRequest *ResourceRequestCollection::find(const ResourceGroup *group) const { - foreach (ResourceGroupRequest *r, m_requests) { + foreach (ResourceGroupRequest *r, m_groupRequests) { 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); +void ResourceRequestCollection::addResourceRequest(ResourceRequest *request, ResourceGroupRequest *group) +{ + if (group) { + Q_ASSERT(m_groupRequests.contains(group->id())); + } + int id = request->id(); + if (id == 0) { + int i = 1; + while (m_resourceRequests.contains(i)) { + ++i; + } + request->setId(i); + } + Q_ASSERT(!m_resourceRequests.contains(request->id())); + request->setCollection(this); + request->registerRequest(); + m_resourceRequests.insert(request->id(), request); + + if (group && !group->resourceRequests().contains(request)) { + group->addResourceRequest(request); } - 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); +void ResourceRequestCollection::removeResourceRequest(ResourceRequest *request) +{ + Q_ASSERT(m_resourceRequests.contains(request->id())); + Q_ASSERT(m_resourceRequests.values().contains(request)); + m_resourceRequests.remove(request->id()); + Q_ASSERT(!m_resourceRequests.values().contains(request)); + if (request->parent()) { + request->parent()->takeResourceRequest(request); + } +} + +void ResourceRequestCollection::deleteResourceRequest(ResourceRequest *request) +{ + delete request; +} + +ResourceRequest *ResourceRequestCollection::find(const Resource *resource) const +{ + for (ResourceRequest *r : m_resourceRequests) { + if (r->resource() == resource) { + return r; + } } - return req; + return nullptr; +} + +ResourceRequest *ResourceRequestCollection::resourceRequest(int id) const +{ + return m_resourceRequests.value(id); +} + +ResourceRequest *ResourceRequestCollection::resourceRequest(const QString &name) const +{ + for (ResourceRequest *r : m_resourceRequests) { + if (r->resource()->name() == name) { + return r; + } + } + return nullptr; } QStringList ResourceRequestCollection::requestNameList(bool includeGroup) const { QStringList lst; - foreach (ResourceGroupRequest *r, m_requests) { + foreach (ResourceGroupRequest *r, m_groupRequests) { lst << r->requestNameList(includeGroup); } return lst; } QList ResourceRequestCollection::requestedResources() const { QList lst; - foreach (ResourceGroupRequest *g, m_requests) { + foreach (ResourceGroupRequest *g, m_groupRequests) { lst += g->requestedResources(); } return lst; } -QList ResourceRequestCollection::resourceRequests(bool resolveTeam) const { +QList ResourceRequestCollection::resourceRequests(bool resolveTeam) const +{ + // FIXME: skip going through groups, probably separate method for resolveTeam case? + if (!resolveTeam) { + return m_resourceRequests.values(); + } QList lst; - foreach (ResourceGroupRequest *g, m_requests) { + foreach (ResourceGroupRequest *g, m_groupRequests) { 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) { + foreach (ResourceGroupRequest *r, m_groupRequests) { 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) { + foreach (ResourceGroupRequest *r, m_groupRequests) { 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) { + foreach (ResourceGroupRequest *r, m_groupRequests) { 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) { + foreach (ResourceGroupRequest *r, m_groupRequests) { 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) { + foreach (ResourceGroupRequest *r, m_groupRequests) { r->makeAppointments(schedule); } } void ResourceRequestCollection::reserve(const DateTime &start, const Duration &duration) { //debugPlan; - foreach (ResourceGroupRequest *r, m_requests) { + foreach (ResourceGroupRequest *r, m_groupRequests) { r->reserve(start, duration); } } bool ResourceRequestCollection::isEmpty() const { - foreach (ResourceGroupRequest *r, m_requests) { + foreach (ResourceGroupRequest *r, m_groupRequests) { if (!r->isEmpty()) return false; } return true; } void ResourceRequestCollection::changed() { //debugPlan<changed(Node::ResourceRequestProperty); } } void ResourceRequestCollection::resetDynamicAllocations() { - foreach (ResourceGroupRequest *g, m_requests) { + foreach (ResourceGroupRequest *g, m_groupRequests) { 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); } 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()<<']'; + dbg<<"ResourceRequest["<name()<<']'; } else { - dbg<<"ResourceRequest[No resource]"; + dbg<<"ResourceRequest["< * Copyright (C) 2011 Dag Andersen * Copyright (C) 2019 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 KPTRESOURCEREQUEST_H #define KPTRESOURCEREQUEST_H #include "plankernel_export.h" #include "kptglobal.h" #include "kptduration.h" #include "kptdatetime.h" #include #include #include #include class QDomElement; /// The main namespace. namespace KPlato { class Account; class Risk; class Effort; class Appointment; class Task; class Node; class Project; class Resource; class ResourceGroup; class ResourceRequest; class ResourceGroupRequest; class ResourceRequestCollection; class Schedule; class ResourceSchedule; class Schedule; class XMLLoaderObject; class DateTimeInterval; - +class ResourceRequestCollection; class PLANKERNEL_EXPORT ResourceRequest { public: - explicit ResourceRequest(Resource *resource = 0, int units = 1); + explicit ResourceRequest(Resource *resource = nullptr, int units = 1); explicit ResourceRequest(const ResourceRequest &r); ~ResourceRequest(); + int id() const; + void setId(int id); + + ResourceRequestCollection *collection() const; + void setCollection(ResourceRequestCollection *collection); + 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(); void unregisterRequest(); 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: + int m_id; Resource *m_resource; int m_units; + ResourceRequestCollection *m_collection; ResourceGroupRequest *m_parent; bool m_dynamic; QList m_required; mutable QList m_teamMembers; #ifndef NDEBUG public: void printDebug(const QString& ident); #endif }; class PLANKERNEL_EXPORT ResourceGroupRequest { public: explicit ResourceGroupRequest(ResourceGroup *group = 0, int units = 0); explicit ResourceGroupRequest(const ResourceGroupRequest &group); ~ResourceGroupRequest(); + int id() const; + void setId(int id); + 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); + void removeResourceRequest(ResourceRequest *request); + private: + friend ResourceRequestCollection; + void addResourceRequest(ResourceRequest *request); + void deleteResourceRequest(ResourceRequest *request); + +private: + int m_id; 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; } + /// Remove all group- and resource requests + /// Note: Does not delete + void removeRequests(); + + QList requests() const { return m_groupRequests.values(); } void addRequest(ResourceGroupRequest *request); void deleteRequest(ResourceGroupRequest *request) { - int i = m_requests.indexOf(request); - if (i != -1) - m_requests.removeAt(i); + Q_ASSERT(m_groupRequests.contains(request->id())); + Q_ASSERT(m_groupRequests.value(request->id()) == request); + m_groupRequests.remove(request->id()); delete request; changed(); } - int takeRequest(ResourceGroupRequest *request) - { - int i = m_requests.indexOf(request); - if (i != -1) { - m_requests.removeAt(i); - changed(); - } - return i; - } - + int takeRequest(ResourceGroupRequest *request); + ResourceGroupRequest *groupRequest(int id) const; ResourceGroupRequest *find(const ResourceGroup *resource) const; + ResourceRequest *resourceRequest(int id) 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(); } +// void clear() { m_groupRequests.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; + /// Add the resource request @p request + /// If @p group is not nullptr, the request is also added to the @p group + void addResourceRequest(ResourceRequest *request, ResourceGroupRequest *group = nullptr); + /// Remove resource request @p request + void removeResourceRequest(ResourceRequest *request); + /// Delete resource request @p request + void deleteResourceRequest(ResourceRequest *request); + //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; } + Task *task() const; + void setTask(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; + QMap m_groupRequests; + QMap m_resourceRequests; }; } //KPlato namespace PLANKERNEL_EXPORT QDebug operator<<(QDebug dbg, const KPlato::ResourceRequest *r); PLANKERNEL_EXPORT QDebug operator<<(QDebug dbg, const KPlato::ResourceRequest &r); #endif diff --git a/src/libs/kernel/kpttask.cpp b/src/libs/kernel/kpttask.cpp index 5f054ad7..f1080356 100644 --- a/src/libs/kernel/kpttask.cpp +++ b/src/libs/kernel/kpttask.cpp @@ -1,3917 +1,3917 @@ /* This file is part of the KDE project - Copyright (C) 2001 Thomas zander - Copyright (C) 2004 - 2007 Dag Andersen - Copyright (C) 2007 Florian Piquemal - Copyright (C) 2007 Alexis Ménard - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Library General Public - License as published by the Free Software Foundation; either - version 2 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Library General Public License for more details. - - You should have received a copy of the GNU Library General Public License - along with this library; see the file COPYING.LIB. If not, write to - the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Copyright (C) 2001 Thomas zander + * Copyright (C) 2004 - 2007 Dag Andersen + * Copyright (C) 2007 Florian Piquemal + * Copyright (C) 2007 Alexis Ménard + * Copyright (C) 2019 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 "kpttask.h" #include "kptappointment.h" #include "kptproject.h" #include "kptduration.h" #include "kptrelation.h" #include "kptdatetime.h" #include "kptcalendar.h" #include "kpteffortcostmap.h" #include "kptschedule.h" #include "kptxmlloaderobject.h" #include "XmlSaveContext.h" #include #include #include namespace KPlato { Task::Task(Node *parent) : Node(parent), m_resource(), m_workPackage(this) { //debugPlan<<"("<setOptimisticRatio(-10); m_estimate->setPessimisticRatio(20); m_estimate->setParentNode(this); if (m_parent) m_leader = m_parent->leader(); } Task::Task(const Task &task, Node *parent) : Node(task, parent), m_resource(), m_workPackage(this) { //debugPlan<<"("<setParentNode(this); } Task::~Task() { while (!m_resource.isEmpty()) { delete m_resource.takeFirst(); } while (!m_parentProxyRelations.isEmpty()) { delete m_parentProxyRelations.takeFirst(); } while (!m_childProxyRelations.isEmpty()) { delete m_childProxyRelations.takeFirst(); } } int Task::type() const { if (numChildren() > 0) { return Node::Type_Summarytask; } else if (m_constraint == Node::FixedInterval) { if (m_constraintEndTime == m_constraintStartTime) { return Node::Type_Milestone; } } else if (m_estimate->expectedEstimate() == 0.0) { return Node::Type_Milestone; } return Node::Type_Task; } Duration *Task::getRandomDuration() { return 0L; } ResourceGroupRequest *Task::resourceGroupRequest(const ResourceGroup *group) const { return m_requests.find(group); } -void Task::clearResourceRequests() { - m_requests.clear(); - changed(this, ResourceRequestProperty); -} +// void Task::clearResourceRequests() { +// m_requests.clear(); +// changed(this, ResourceRequestProperty); +// } void Task::addRequest(ResourceGroup *group, int numResources) { addRequest(new ResourceGroupRequest(group, numResources)); } void Task::addRequest(ResourceGroupRequest *request) { //debugPlan<group()<group()->id()<group()->name(); m_requests.addRequest(request); changed(this, Node::ResourceRequestProperty); } void Task::takeRequest(ResourceGroupRequest *request) { //debugPlan< Task::requestedResources() const { return m_requests.requestedResources(); } bool Task::containsRequest(const QString &identity) const { return m_requests.contains(identity); } ResourceRequest *Task::resourceRequest(const QString &name) const { return m_requests.resourceRequest(name); } QStringList Task::assignedNameList(long id) const { Schedule *s = schedule(id); if (s == 0) { return QStringList(); } return s->resourceNameList(); } void Task::makeAppointments() { if (m_currentSchedule == 0) return; if (type() == Node::Type_Task) { //debugPlan<startTime<<","<endTime<<";"<duration.toString(); m_requests.makeAppointments(m_currentSchedule); //debugPlan<startTime<<","<endTime<<";"<duration.toString(); } else if (type() == Node::Type_Summarytask) { foreach (Node *n, m_nodes) { n->makeAppointments(); } } else if (type() == Node::Type_Milestone) { //debugPlan<<"Milestone not implemented"; // Well, shouldn't have resources anyway... } } void Task::copySchedule() { if (m_currentSchedule == 0 || type() != Node::Type_Task) { return; } int id = m_currentSchedule->parentScheduleId(); NodeSchedule *ns = static_cast(findSchedule(id)); if (ns == 0) { return; } if (type() == Node::Type_Task) { copyAppointments(ns->startTime, ns->endTime); } m_currentSchedule->startTime = ns->startTime; m_currentSchedule->earlyStart = ns->earlyStart; m_currentSchedule->endTime = ns->endTime; m_currentSchedule->lateFinish = ns->lateFinish; m_currentSchedule->duration = ns->duration; // TODO: status flags, etc //debugPlan; } void Task::copyAppointments() { copyAppointments(DateTime(), m_currentSchedule->startTime); } void Task::copyAppointments(const DateTime &start, const DateTime &end) { if (m_currentSchedule == 0 || type() != Node::Type_Task) { return; } int id = m_currentSchedule->parentScheduleId(); NodeSchedule *ns = static_cast(findSchedule(id)); if (ns == 0) { return; } DateTime st = start.isValid() ? start : ns->startTime; DateTime et = end.isValid() ? end : ns->endTime; //debugPlan<calculationMode(); foreach (const Appointment *a, ns->appointments()) { Resource *r = a->resource() == 0 ? 0 : a->resource()->resource(); if (r == 0) { errorPlan<<"No resource"; continue; } AppointmentIntervalList lst = a->intervals(st, et); if (lst.isEmpty()) { //debugPlan<<"No intervals to copy from"<appointments()) { if (c->resource()->resource() == r) { //debugPlan<<"Found current appointment to"<resource()->resource()->name()<add(curr); curr->setNode(m_currentSchedule); //debugPlan<<"Created new appointment"<(r->findSchedule(m_currentSchedule->id())); if (rs == 0) { rs = r->createSchedule(m_currentSchedule->parent()); rs->setId(m_currentSchedule->id()); rs->setName(m_currentSchedule->name()); rs->setType(m_currentSchedule->type()); //debugPlan<<"Resource schedule not found, id="<id(); } rs->setCalculationMode(m_currentSchedule->calculationMode()); if (! rs->appointments().contains(curr)) { //debugPlan<<"add to resource"<add(curr); curr->setResource(rs); } Appointment app; app.setIntervals(lst); //foreach (AppointmentInterval *i, curr->intervals()) { debugPlan<startTime().toString()<endTime().toString(); } curr->merge(app); //debugPlan<<"Appointments added"; } m_currentSchedule->startTime = ns->startTime; m_currentSchedule->earlyStart = ns->earlyStart; } void Task::calcResourceOverbooked() { if (m_currentSchedule) m_currentSchedule->calcResourceOverbooked(); } bool Task::load(KoXmlElement &element, XMLLoaderObject &status) { QString s; bool ok = false; m_id = element.attribute(QStringLiteral("id")); m_priority = element.attribute(QStringLiteral("priority"), "0").toInt(); setName(element.attribute(QStringLiteral("name"))); m_leader = element.attribute(QStringLiteral("leader")); m_description = element.attribute(QStringLiteral("description")); //debugPlan<load(e)) { if (!project.addSubTask(child, this)) { delete child; // TODO: Complain about this } } else { // TODO: Complain about this delete child; }*/ } else if (e.tagName() == QLatin1String("task")) { if (status.loadTaskChildren()) { // Load the task Task *child = new Task(this); if (child->load(e, status)) { if (!status.project().addSubTask(child, this)) { delete child; // TODO: Complain about this } } else { // TODO: Complain about this delete child; } } } else if (e.tagName() == QLatin1String("resource")) { // TODO: Load the resource (projects don't have resources yet) } else if (e.tagName() == QLatin1String("estimate") || (/*status.version() < "0.6" &&*/ e.tagName() == QLatin1String("effort"))) { // Load the estimate m_estimate->load(e, status); } else if (e.tagName() == QLatin1String("resourcegroup-request")) { // Load the resource request // Handle multiple requests to same group gracefully (Not really allowed) ResourceGroupRequest *r = m_requests.findGroupRequestById(e.attribute(QStringLiteral("group-id"))); if (r) { warnPlan<<"Multiple requests to same group, loading into existing group"; if (! r->load(e, status)) { errorPlan<<"Failed to load resource request"; } } else { r = new ResourceGroupRequest(); if (r->load(e, status)) { addRequest(r); + for (ResourceRequest *rr : r->resourceRequests()) { + m_requests.addResourceRequest(rr); + } } else { errorPlan<<"Failed to load resource request"; delete r; } } } else if (e.tagName() == QLatin1String("workpackage")) { m_workPackage.loadXML(e, status); } else if (e.tagName() == QLatin1String("progress")) { completion().loadXML(e, status); } else if (e.tagName() == QLatin1String("task-schedules") || (status.version() < "0.7.0" && e.tagName() == QLatin1String("schedules"))) { KoXmlNode n = e.firstChild(); for (; ! n.isNull(); n = n.nextSibling()) { if (! n.isElement()) { continue; } KoXmlElement el = n.toElement(); if (el.tagName() == QLatin1String("schedule")) { NodeSchedule *sch = new NodeSchedule(); if (sch->loadXML(el, status)) { sch->setNode(this); addSchedule(sch); } else { errorPlan<<"Failed to load schedule"; delete sch; } } } } else if (e.tagName() == QLatin1String("documents")) { m_documents.load(e, status); } else if (e.tagName() == QLatin1String("workpackage-log")) { KoXmlNode n = e.firstChild(); for (; ! n.isNull(); n = n.nextSibling()) { if (! n.isElement()) { continue; } KoXmlElement el = n.toElement(); if (el.tagName() == QLatin1String("workpackage")) { WorkPackage *wp = new WorkPackage(this); if (wp->loadLoggedXML(el, status)) { m_packageLog << wp; } else { errorPlan<<"Failed to load logged workpackage"; delete wp; } } } } } //debugPlan<save(me); m_documents.save(me); - if (! m_requests.isEmpty()) { - m_requests.save(me); - } - if (context.saveAll(this)) { if (!m_schedules.isEmpty()) { QDomElement schs = me.ownerDocument().createElement(QStringLiteral("task-schedules")); me.appendChild(schs); foreach (const Schedule *s, m_schedules) { if (!s->isDeleted()) { s->saveXML(schs); } } } completion().saveXML(me); m_workPackage.saveXML(me); // The workpackage log if (!m_packageLog.isEmpty()) { QDomElement log = me.ownerDocument().createElement(QStringLiteral("workpackage-log")); me.appendChild(log); foreach (const WorkPackage *wp, m_packageLog) { wp->saveLoggedXML(log); } } } if (context.saveChildren(this)) { for (int i=0; isave(me, context); } } } void Task::saveAppointments(QDomElement &element, long id) const { //debugPlan<save(me); completion().saveXML(me); if (m_schedules.contains(id) && ! m_schedules[ id ]->isDeleted()) { QDomElement schs = me.ownerDocument().createElement(QStringLiteral("task-schedules")); me.appendChild(schs); m_schedules[ id ]->saveXML(schs); } m_documents.save(me); // TODO: copying documents } bool Task::isStarted() const { return completion().isStarted(); } EffortCostMap Task::plannedEffortCostPrDay(QDate start, QDate end, long id, EffortCostCalculationType typ) const { //debugPlan< it(childNodeIterator()); while (it.hasNext()) { ec += it.next() ->plannedEffortCostPrDay(start, end, id, typ); } return ec; } Schedule *s = schedule(id); if (s) { return s->plannedEffortCostPrDay(start, end, typ); } return EffortCostMap(); } EffortCostMap Task::plannedEffortCostPrDay(const Resource *resource, QDate start, QDate end, long id, EffortCostCalculationType typ) const { //debugPlan< it(childNodeIterator()); while (it.hasNext()) { ec += it.next() ->plannedEffortCostPrDay(resource, start, end, id, typ); } return ec; } Schedule *s = schedule(id); if (s) { return s->plannedEffortCostPrDay(resource, start, end, typ); } return EffortCostMap(); } EffortCostMap Task::actualEffortCostPrDay(QDate start, QDate end, long id, EffortCostCalculationType typ) const { //debugPlan< it(childNodeIterator()); while (it.hasNext()) { ec += it.next() ->actualEffortCostPrDay(start, end, id, typ); } return ec; } switch (completion().entrymode()) { case Completion::FollowPlan: return plannedEffortCostPrDay(start, end, id, typ); default: return completion().effortCostPrDay(start, end, id); } return EffortCostMap(); } EffortCostMap Task::actualEffortCostPrDay(const Resource *resource, QDate start, QDate end, long id, EffortCostCalculationType typ) const { //debugPlan< it(childNodeIterator()); while (it.hasNext()) { ec += it.next() ->actualEffortCostPrDay(resource, start, end, id, typ); } return ec; } switch (completion().entrymode()) { case Completion::FollowPlan: return plannedEffortCostPrDay(resource, start, end, id, typ); default: return completion().effortCostPrDay(resource, start, end); } return EffortCostMap(); } // Returns the total planned effort for this task (or subtasks) Duration Task::plannedEffort(const Resource *resource, long id, EffortCostCalculationType typ) const { //debugPlan; Duration eff; if (type() == Node::Type_Summarytask) { foreach (const Node *n, childNodeIterator()) { eff += n->plannedEffort(resource, id, typ); } return eff; } Schedule *s = schedule(id); if (s) { eff = s->plannedEffort(resource, typ); } return eff; } // Returns the total planned effort for this task (or subtasks) Duration Task::plannedEffort(long id, EffortCostCalculationType typ) const { //debugPlan; Duration eff; if (type() == Node::Type_Summarytask) { foreach (const Node *n, childNodeIterator()) { eff += n->plannedEffort(id, typ); } return eff; } Schedule *s = schedule(id); if (s) { eff = s->plannedEffort(typ); } return eff; } // Returns the total planned effort for this task (or subtasks) on date Duration Task::plannedEffort(const Resource *resource, QDate date, long id, EffortCostCalculationType typ) const { //debugPlan; Duration eff; if (type() == Node::Type_Summarytask) { foreach (const Node *n, childNodeIterator()) { eff += n->plannedEffort(resource, date, id, typ); } return eff; } Schedule *s = schedule(id); if (s) { eff = s->plannedEffort(resource, date, typ); } return eff; } // Returns the total planned effort for this task (or subtasks) on date Duration Task::plannedEffort(QDate date, long id, EffortCostCalculationType typ) const { //debugPlan; Duration eff; if (type() == Node::Type_Summarytask) { foreach (const Node *n, childNodeIterator()) { eff += n->plannedEffort(date, id, typ); } return eff; } Schedule *s = schedule(id); if (s) { eff = s->plannedEffort(date, typ); } return eff; } // Returns the total planned effort for this task (or subtasks) upto and including date Duration Task::plannedEffortTo(QDate date, long id, EffortCostCalculationType typ) const { //debugPlan; Duration eff; if (type() == Node::Type_Summarytask) { foreach (const Node *n, childNodeIterator()) { eff += n->plannedEffortTo(date, id, typ); } return eff; } Schedule *s = schedule(id); if (s) { eff = s->plannedEffortTo(date, typ); } return eff; } // Returns the total planned effort for this task (or subtasks) upto and including date Duration Task::plannedEffortTo(const Resource *resource, QDate date, long id, EffortCostCalculationType typ) const { //debugPlan; Duration eff; if (type() == Node::Type_Summarytask) { foreach (const Node *n, childNodeIterator()) { eff += n->plannedEffortTo(resource, date, id, typ); } return eff; } Schedule *s = schedule(id); if (s) { eff = s->plannedEffortTo(resource, date, typ); } return eff; } // Returns the total actual effort for this task (or subtasks) Duration Task::actualEffort() const { //debugPlan; Duration eff; if (type() == Node::Type_Summarytask) { foreach (const Node *n, childNodeIterator()) { eff += n->actualEffort(); } } return completion().actualEffort(); } // Returns the total actual effort for this task (or subtasks) on date Duration Task::actualEffort(QDate date) const { //debugPlan; Duration eff; if (type() == Node::Type_Summarytask) { foreach (const Node *n, childNodeIterator()) { eff += n->actualEffort(date); } return eff; } return completion().actualEffort(date); } // Returns the total actual effort for this task (or subtasks) to date Duration Task::actualEffortTo(QDate date) const { //debugPlan; Duration eff; if (type() == Node::Type_Summarytask) { foreach (const Node *n, childNodeIterator()) { eff += n->actualEffortTo(date); } return eff; } return completion().actualEffortTo(date); } EffortCost Task::plannedCost(long id, EffortCostCalculationType typ) const { //debugPlan; if (type() == Node::Type_Summarytask) { return Node::plannedCost(id, typ); } EffortCost c; Schedule *s = schedule(id); if (s) { c = s->plannedCost(typ); } return c; } double Task::plannedCostTo(QDate date, long id, EffortCostCalculationType typ) const { //debugPlan; double c = 0; if (type() == Node::Type_Summarytask) { foreach (const Node *n, childNodeIterator()) { c += n->plannedCostTo(date, id, typ); } return c; } Schedule *s = schedule(id); if (s == 0) { return c; } c = s->plannedCostTo(date, typ); if (date >= s->startTime.date()) { c += m_startupCost; } if (date >= s->endTime.date()) { c += m_shutdownCost; } return c; } EffortCost Task::actualCostTo(long int id, QDate date) const { //debugPlan; EffortCostMap ecm = acwp(id); return EffortCost(ecm.effortTo(date), ecm.costTo(date)); } double Task::bcws(QDate date, long id) const { //debugPlan; double c = plannedCostTo(date, id); //debugPlan<bcwsPrDayCache(typ); if (! cache.cached) { EffortCostMap ec = s->bcwsPrDay(typ); if (typ != ECCT_Work) { if (m_startupCost > 0.0) { ec.add(s->startTime.date(), Duration::zeroDuration, m_startupCost); } if (m_shutdownCost > 0.0) { ec.add(s->endTime.date(), Duration::zeroDuration, m_shutdownCost); } cache.effortcostmap = ec; cache.cached = true; } } return cache.effortcostmap; } EffortCostMap Task::bcwpPrDay(long int id, EffortCostCalculationType typ) { //debugPlan; if (type() == Node::Type_Summarytask) { return Node::bcwpPrDay(id, typ); } Schedule *s = schedule(id); if (s == 0) { return EffortCostMap(); } EffortCostCache cache = s->bcwpPrDayCache(typ); if (! cache.cached) { // do not use bcws cache, it includes startup/shutdown cost EffortCostMap e = s->plannedEffortCostPrDay(s->appointmentStartTime().date(), s->appointmentEndTime().date(), typ); if (completion().isStarted() && ! e.isEmpty()) { // calculate bcwp on bases of bcws *without* startup/shutdown cost double totEff = e.totalEffort().toDouble(Duration::Unit_h); double totCost = e.totalCost(); QDate sd = completion().entries().keys().value(0); if (! sd.isValid() || e.startDate() < sd) { sd = e.startDate(); } QDate ed = qMax(e.endDate(), completion().entryDate()); for (QDate d = sd; d <= ed; d = d.addDays(1)) { double p = (double)(completion().percentFinished(d)) / 100.0; EffortCost ec = e.days()[ d ]; ec.setBcwpEffort(totEff * p); ec.setBcwpCost(totCost * p); e.insert(d, ec); } } if (typ != ECCT_Work) { // add bcws startup/shutdown cost if (m_startupCost > 0.0) { e.add(s->startTime.date(), Duration::zeroDuration, m_startupCost); } if (m_shutdownCost > 0.0) { e.add(s->endTime.date(), Duration::zeroDuration, m_shutdownCost); } // add bcwp startup/shutdown cost if (m_shutdownCost > 0.0 && completion().finishIsValid()) { QDate finish = completion().finishTime().date(); e.addBcwpCost(finish, m_shutdownCost); debugPlan<<"addBcwpCost:"< finish) { e.addBcwpCost(date, m_shutdownCost); debugPlan<<"addBcwpCost:"< 0.0 && completion().startIsValid()) { QDate start = completion().startTime().date(); e.addBcwpCost(start, m_startupCost); // bcwp is cumulative so add to all entries after start for (EffortCostDayMap::const_iterator it = e.days().constBegin(); it != e.days().constEnd(); ++it) { const QDate date = it.key(); if (date > start) { e.addBcwpCost(date, m_startupCost); } } } } cache.effortcostmap = e; cache.cached = true; } return cache.effortcostmap; } Duration Task::budgetedWorkPerformed(QDate date, long id) const { //debugPlan; Duration e; if (type() == Node::Type_Summarytask) { foreach (const Node *n, childNodeIterator()) { e += n->budgetedWorkPerformed(date, id); } return e; } e = plannedEffort(id) * (double)completion().percentFinished(date) / 100.0; //debugPlan<budgetedCostPerformed(date, id); } return c; } c = plannedCost(id).cost() * (double)completion().percentFinished(date) / 100.0; if (completion().isStarted() && date >= completion().startTime().date()) { c += m_startupCost; } if (completion().isFinished() && date >= completion().finishTime().date()) { c += m_shutdownCost; } //debugPlan<acwpCache(typ); if (! ec.cached) { //debugPlan<= completion().startTime().date()) { c.add(Duration::zeroDuration, m_startupCost); } if (completion().isFinished() && date >= completion().finishTime().date()) { c.add(Duration::zeroDuration, m_shutdownCost); } return c; } double Task::schedulePerformanceIndex(QDate date, long id) const { //debugPlan; double r = 1.0; double s = bcws(date, id); double p = bcwp(date, id); if (s > 0.0) { r = p / s; } return r; } double Task::effortPerformanceIndex(QDate date, long id) const { //debugPlan; double r = 1.0; Duration a, b; if (m_estimate->type() == Estimate::Type_Effort) { Duration b = budgetedWorkPerformed(date, id); if (b == Duration::zeroDuration) { return r; } Duration a = actualEffortTo(date); if (b == Duration::zeroDuration) { return 1.0; } r = b.toDouble() / a.toDouble(); } else if (m_estimate->type() == Estimate::Type_Duration) { //TODO } return r; } //FIXME Handle summarytasks double Task::costPerformanceIndex(long int id, QDate date, bool *error) const { double res = 0.0; double ac = actualCostTo(id, date).cost(); bool e = (ac == 0.0 || completion().percentFinished() == 0); if (error) { *error = e; } if (!e) { res = (plannedCostTo(date, id) * completion().percentFinished()) / (100 * ac); } return res; } void Task::initiateCalculation(MainSchedule &sch) { //debugPlan< &list_, int use) { DateTime time; // do them forward QMultiMap lst; for (Relation* r : list_) { lst.insert(-r->parent()->priority(), r); } const QList list = lst.values(); foreach (Relation *r, list) { if (r->parent()->type() == Type_Summarytask) { //debugPlan<<"Skip summarytask:"<parent()->name(); continue; // skip summarytasks } DateTime t = r->parent()->calculateForward(use); // early finish switch (r->type()) { case Relation::StartStart: // I can't start earlier than my predesseccor t = r->parent()->earlyStart() + r->lag(); break; case Relation::FinishFinish: { // I can't finish earlier than my predeccessor, so // I can't start earlier than it's (earlyfinish+lag)- my duration t += r->lag(); Schedule::OBState obs = m_currentSchedule->allowOverbookingState(); m_currentSchedule->setAllowOverbookingState(Schedule::OBS_Allow); #ifndef PLAN_NLOGDEBUG m_currentSchedule->logDebug(QStringLiteral("FinishFinish: get duration to calculate early finish")); #endif t -= duration(t, use, true); m_currentSchedule->setAllowOverbookingState(obs); break; } default: t += r->lag(); break; } if (!time.isValid() || t > time) time = t; } //debugPlan<earlyFinish; } if (m_currentSchedule == 0) { return DateTime(); } Schedule *cs = m_currentSchedule; cs->setCalculationMode(Schedule::CalculateForward); //cs->logDebug("calculateForward: earlyStart=" + cs->earlyStart.toString()); // calculate all predecessors if (!dependParentNodes().isEmpty()) { DateTime time = calculatePredeccessors(dependParentNodes(), use); if (time.isValid() && time > cs->earlyStart) { cs->earlyStart = time; //cs->logDebug(QString("calculate forward: early start moved to: %1").arg(cs->earlyStart.toString())); } } if (!m_parentProxyRelations.isEmpty()) { DateTime time = calculatePredeccessors(m_parentProxyRelations, use); if (time.isValid() && time > cs->earlyStart) { cs->earlyStart = time; //cs->logDebug(QString("calculate forward: early start moved to: %1").arg(cs->earlyStart.toString())); } } m_calculateForwardRun = true; //cs->logDebug("calculateForward: earlyStart=" + cs->earlyStart.toString()); return calculateEarlyFinish(use); } DateTime Task::calculateEarlyFinish(int use) { //debugPlan<usePert(); cs->setCalculationMode(Schedule::CalculateForward); #ifndef PLAN_NLOGDEBUG QTime timer; timer.start(); cs->logDebug(QStringLiteral("Start calculate forward: %1 ").arg(constraintToString(true))); #endif QLocale locale; cs->logInfo(i18n("Calculate early finish ")); //debugPlan<<"------>"<earlyStart; if (type() == Node::Type_Task) { m_durationForward = m_estimate->value(use, pert); switch (constraint()) { case Node::ASAP: case Node::ALAP: { //debugPlan<earlyStart; cs->earlyStart = workTimeAfter(cs->earlyStart); m_durationForward = duration(cs->earlyStart, use, false); m_earlyFinish = cs->earlyStart + m_durationForward; #ifndef PLAN_NLOGDEBUG cs->logDebug("ASAP/ALAP: " + cs->earlyStart.toString() + '+' + m_durationForward.toString() + '=' + m_earlyFinish.toString()); #endif if (!cs->allowOverbooking()) { cs->startTime = cs->earlyStart; cs->endTime = m_earlyFinish; makeAppointments(); // calculate duration wo checking booking = the earliest finish possible Schedule::OBState obs = cs->allowOverbookingState(); cs->setAllowOverbookingState(Schedule::OBS_Allow); m_durationForward = duration(cs->earlyStart, use, false); cs->setAllowOverbookingState(obs); #ifndef PLAN_NLOGDEBUG cs->logDebug("ASAP/ALAP earliest possible: " + cs->earlyStart.toString() + '+' + m_durationForward.toString() + '=' + (cs->earlyStart+m_durationForward).toString()); #endif } break; } case Node::MustFinishOn: { cs->earlyStart = workTimeAfter(cs->earlyStart); m_durationForward = duration(cs->earlyStart, use, false); cs->earlyFinish = cs->earlyStart + m_durationForward; //debugPlan<<"MustFinishOn:"<earlyStart<earlyFinish; if (cs->earlyFinish > m_constraintEndTime) { cs->logWarning(i18nc("1=type of constraint", "%1: Failed to meet constraint", constraintToString(true))); } cs->earlyFinish = qMax(cs->earlyFinish, m_constraintEndTime); if (!cs->allowOverbooking()) { cs->endTime = cs->earlyFinish; cs->startTime = cs->earlyFinish - duration(cs->earlyFinish, use, true); makeAppointments(); } m_earlyFinish = cs->earlyFinish; m_durationForward = m_earlyFinish - cs->earlyStart; break; } case Node::FinishNotLater: { m_durationForward = duration(cs->earlyStart, use, false); cs->earlyFinish = cs->earlyStart + m_durationForward; //debugPlan<<"FinishNotLater:"<earlyStart<earlyFinish; if (cs->earlyFinish > m_constraintEndTime) { cs->logWarning(i18nc("1=type of constraint", "%1: Failed to meet constraint", constraintToString(true))); } if (!cs->allowOverbooking()) { cs->startTime = cs->earlyStart; cs->endTime = cs->earlyFinish; makeAppointments(); } m_earlyFinish = cs->earlyStart + m_durationForward; break; } case Node::MustStartOn: case Node::StartNotEarlier: { //debugPlan<<"MSO/SNE:"<earlyStart; cs->logDebug(constraintToString() + ": " + m_constraintStartTime.toString() + ' ' + cs->earlyStart.toString()); cs->earlyStart = workTimeAfter(qMax(cs->earlyStart, m_constraintStartTime)); if (cs->earlyStart < m_constraintStartTime) { cs->logWarning(i18nc("1=type of constraint", "%1: Failed to meet constraint", constraintToString(true))); } m_durationForward = duration(cs->earlyStart, use, false); m_earlyFinish = cs->earlyStart + m_durationForward; if (!cs->allowOverbooking()) { cs->startTime = cs->earlyStart; cs->endTime = m_earlyFinish; makeAppointments(); // calculate duration wo checking booking = the earliest finish possible Schedule::OBState obs = cs->allowOverbookingState(); cs->setAllowOverbookingState(Schedule::OBS_Allow); m_durationForward = duration(cs->startTime, use, false); cs->setAllowOverbookingState(obs); m_earlyFinish = cs->earlyStart + m_durationForward; #ifndef PLAN_NLOGDEBUG cs->logDebug("MSO/SNE earliest possible: " + cs->earlyStart.toString() + '+' + m_durationForward.toString() + '=' + (cs->earlyStart+m_durationForward).toString()); #endif } break; } case Node::FixedInterval: { if (cs->earlyStart > m_constraintStartTime) { cs->logWarning(i18nc("1=type of constraint", "%1: Failed to meet constraint", constraintToString(true))); } //cs->earlyStart = m_constraintStartTime; m_durationForward = m_constraintEndTime - m_constraintStartTime; if (cs->earlyStart < m_constraintStartTime) { m_durationForward = m_constraintEndTime - cs->earlyStart; } if (!cs->allowOverbooking()) { cs->startTime = m_constraintStartTime; cs->endTime = m_constraintEndTime; makeAppointments(); } m_earlyFinish = cs->earlyStart + m_durationForward; break; } } } else if (type() == Node::Type_Milestone) { m_durationForward = Duration::zeroDuration; switch (constraint()) { case Node::MustFinishOn: //debugPlan<<"MustFinishOn:"<earlyStart; //cs->logDebug(QString("%1: %2, early start: %3").arg(constraintToString()).arg(m_constraintEndTime.toString()).arg(cs->earlyStart.toString())); if (cs->earlyStart < m_constraintEndTime) { m_durationForward = m_constraintEndTime - cs->earlyStart; } if (cs->earlyStart > m_constraintEndTime) { cs->logWarning(i18nc("1=type of constraint", "%1: Failed to meet constraint", constraintToString(true))); } m_earlyFinish = cs->earlyStart + m_durationForward; break; case Node::FinishNotLater: //debugPlan<<"FinishNotLater:"<earlyStart; if (cs->earlyStart > m_constraintEndTime) { cs->logWarning(i18nc("1=type of constraint", "%1: Failed to meet constraint", constraintToString(true))); } m_earlyFinish = cs->earlyStart; break; case Node::MustStartOn: //debugPlan<<"MustStartOn:"<earlyStart; if (cs->earlyStart < m_constraintStartTime) { m_durationForward = m_constraintStartTime - cs->earlyStart; } if (cs->earlyStart > m_constraintStartTime) { cs->logWarning(i18nc("1=type of constraint", "%1: Failed to meet constraint", constraintToString(true))); } m_earlyFinish = cs->earlyStart + m_durationForward; break; case Node::StartNotEarlier: //debugPlan<<"StartNotEarlier:"<earlyStart; if (cs->earlyStart < m_constraintStartTime) { m_durationForward = m_constraintStartTime - cs->earlyStart; } m_earlyFinish = cs->earlyStart + m_durationForward; break; case Node::FixedInterval: m_earlyFinish = cs->earlyStart + m_durationForward; break; default: m_earlyFinish = cs->earlyStart + m_durationForward; break; } //debugPlan<insertForwardNode(this); cs->earlyFinish = cs->earlyStart + m_durationForward; foreach (const Appointment *a, cs->appointments(Schedule::CalculateForward)) { cs->logInfo(i18n("Resource %1 booked from %2 to %3", a->resource()->resource()->name(), locale.toString(a->startTime(), QLocale::ShortFormat), locale.toString(a->endTime(), QLocale::ShortFormat))); } // clean up temporary usage cs->startTime = DateTime(); cs->endTime = DateTime(); cs->duration = Duration::zeroDuration; cs->logInfo(i18n("Early finish calculated: %1", locale.toString(cs->earlyFinish, QLocale::ShortFormat))); cs->incProgress(); #ifndef PLAN_NLOGDEBUG cs->logDebug(QStringLiteral("Finished calculate forward: %1 ms").arg(timer.elapsed())); #endif return m_earlyFinish; } DateTime Task::calculateSuccessors(const QList &list_, int use) { DateTime time; QMultiMap lst; for (Relation* r : list_) { lst.insert(-r->child()->priority(), r); } const QList list = lst.values(); foreach (Relation *r, list) { if (r->child()->type() == Type_Summarytask) { //debugPlan<<"Skip summarytask:"<parent()->name(); continue; // skip summarytasks } DateTime t = r->child()->calculateBackward(use); switch (r->type()) { case Relation::StartStart: { // I must start before my successor, so // I can't finish later than it's (starttime-lag) + my duration t -= r->lag(); Schedule::OBState obs = m_currentSchedule->allowOverbookingState(); m_currentSchedule->setAllowOverbookingState(Schedule::OBS_Allow); #ifndef PLAN_NLOGDEBUG m_currentSchedule->logDebug(QStringLiteral("StartStart: get duration to calculate late start")); #endif t += duration(t, use, false); m_currentSchedule->setAllowOverbookingState(obs); break; } case Relation::FinishFinish: // My successor cannot finish before me, so // I can't finish later than it's latest finish - lag t = r->child()->lateFinish() - r->lag(); break; default: t -= r->lag(); break; } if (!time.isValid() || t < time) time = t; } //debugPlan<lateStart; } if (m_currentSchedule == 0) { return DateTime(); } Schedule *cs = m_currentSchedule; cs->setCalculationMode(Schedule::CalculateBackward); //cs->lateFinish = projectNode()->constraintEndTime(); // calculate all successors if (!dependChildNodes().isEmpty()) { DateTime time = calculateSuccessors(dependChildNodes(), use); if (time.isValid() && time < cs->lateFinish) { cs->lateFinish = time; } } if (!m_childProxyRelations.isEmpty()) { DateTime time = calculateSuccessors(m_childProxyRelations, use); if (time.isValid() && time < cs->lateFinish) { cs->lateFinish = time; } } m_calculateBackwardRun = true; return calculateLateStart(use); } DateTime Task::calculateLateStart(int use) { //debugPlan<lateStart; } bool pert = cs->usePert(); cs->setCalculationMode(Schedule::CalculateBackward); #ifndef PLAN_NLOGDEBUG QTime timer; timer.start(); cs->logDebug(QStringLiteral("Start calculate backward: %1 ").arg(constraintToString(true))); #endif QLocale locale; cs->logInfo(i18n("Calculate late start")); cs->logDebug(QStringLiteral("%1: late finish= %2").arg(constraintToString()).arg(cs->lateFinish.toString())); //debugPlan<lateFinish; cs->lateFinish = workTimeBefore(cs->lateFinish); m_durationBackward = duration(cs->lateFinish, use, true); cs->lateStart = cs->lateFinish - m_durationBackward; #ifndef PLAN_NLOGDEBUG cs->logDebug("ASAP/ALAP: " + cs->lateFinish.toString() + '-' + m_durationBackward.toString() + '=' + cs->lateStart.toString()); #endif if (!cs->allowOverbooking()) { cs->startTime = cs->lateStart; cs->endTime = cs->lateFinish; makeAppointments(); // calculate wo checking bookings = latest start possible Schedule::OBState obs = cs->allowOverbookingState(); cs->setAllowOverbookingState(Schedule::OBS_Allow); m_durationBackward = duration(cs->lateFinish, use, true); cs->setAllowOverbookingState(obs); #ifndef PLAN_NLOGDEBUG cs->logDebug("ASAP/ALAP latest start possible: " + cs->lateFinish.toString() + '-' + m_durationBackward.toString() + '=' + (cs->lateFinish-m_durationBackward).toString()); #endif } break; case Node::MustStartOn: case Node::StartNotEarlier: { //debugPlan<<"MustStartOn:"<lateFinish; cs->lateFinish = workTimeBefore(cs->lateFinish); m_durationBackward = duration(cs->lateFinish, use, true); cs->lateStart = cs->lateFinish - m_durationBackward; if (cs->lateStart < m_constraintStartTime) { cs->logWarning(i18nc("1=type of constraint", "%1: Failed to meet constraint", constraintToString(true))); } else { cs->lateStart = qMax(cs->earlyStart, m_constraintStartTime); } if (!cs->allowOverbooking()) { if (constraint() == MustStartOn) { cs->startTime = m_constraintStartTime; cs->endTime = m_constraintStartTime + duration(m_constraintStartTime, use, false); } else { cs->startTime = qMax(cs->lateStart, m_constraintStartTime); cs->endTime = qMax(cs->lateFinish, cs->startTime); // safety } makeAppointments(); } cs->lateStart = cs->lateFinish - m_durationBackward; break; } case Node::MustFinishOn: case Node::FinishNotLater: //debugPlan<<"MustFinishOn:"<lateFinish; cs->lateFinish = workTimeBefore(cs->lateFinish); cs->endTime = cs->lateFinish; if (cs->lateFinish < m_constraintEndTime) { cs->logWarning(i18nc("1=type of constraint", "%1: Failed to meet constraint", constraintToString(true))); } else { cs->endTime = qMax(cs->earlyFinish, m_constraintEndTime); } m_durationBackward = duration(cs->endTime, use, true); cs->startTime = cs->endTime - m_durationBackward; if (!cs->allowOverbooking()) { makeAppointments(); } m_durationBackward = cs->lateFinish - cs->startTime; cs->lateStart = cs->lateFinish - m_durationBackward; break; case Node::FixedInterval: { //cs->lateFinish = m_constraintEndTime; if (cs->lateFinish < m_constraintEndTime) { cs->logWarning(i18nc("1=type of constraint", "%1: Failed to meet constraint", constraintToString(true))); } m_durationBackward = m_constraintEndTime - m_constraintStartTime; if (cs->lateFinish > m_constraintEndTime) { m_durationBackward = cs->lateFinish - m_constraintStartTime; } if (!cs->allowOverbooking()) { cs->startTime = m_constraintStartTime; cs->endTime = m_constraintEndTime; makeAppointments(); } cs->lateStart = cs->lateFinish - m_durationBackward; break; } } } else if (type() == Node::Type_Milestone) { m_durationBackward = Duration::zeroDuration; switch (constraint()) { case Node::MustFinishOn: //debugPlan<<"MustFinishOn:"<lateFinish; if (m_constraintEndTime < cs->lateFinish) { m_durationBackward = cs->lateFinish - m_constraintEndTime; } else if (m_constraintEndTime > cs->lateFinish) { cs->logWarning(i18nc("1=type of constraint", "%1: Failed to meet constraint", constraintToString(true))); } cs->lateStart = cs->lateFinish - m_durationBackward; break; case Node::FinishNotLater: //debugPlan<<"FinishNotLater:"<lateFinish; if (m_constraintEndTime < cs->lateFinish) { m_durationBackward = cs->lateFinish - m_constraintEndTime; } else if (m_constraintEndTime > cs->lateFinish) { cs->logWarning(i18nc("1=type of constraint", "%1: Failed to meet constraint", constraintToString(true))); } cs->lateStart = cs->lateFinish - m_durationBackward; break; case Node::MustStartOn: //debugPlan<<"MustStartOn:"<lateFinish; if (m_constraintStartTime < cs->lateFinish) { m_durationBackward = cs->lateFinish - m_constraintStartTime; } else if (m_constraintStartTime > cs->lateFinish) { cs->logWarning(i18nc("1=type of constraint", "%1: Failed to meet constraint", constraintToString(true))); } cs->lateStart = cs->lateFinish - m_durationBackward; //cs->logDebug(QString("%1: constraint:%2, start=%3, finish=%4").arg(constraintToString()).arg(m_constraintStartTime.toString()).arg(cs->lateStart.toString()).arg(cs->lateFinish.toString())); break; case Node::StartNotEarlier: //debugPlan<<"MustStartOn:"<lateFinish; if (m_constraintStartTime > cs->lateFinish) { cs->logWarning(i18nc("1=type of constraint", "%1: Failed to meet constraint", constraintToString(true))); } cs->lateStart = cs->lateFinish; break; case Node::FixedInterval: cs->lateStart = cs->lateFinish - m_durationBackward; break; default: cs->lateStart = cs->lateFinish - m_durationBackward; break; } //debugPlan<lateFinish; } else if (type() == Node::Type_Summarytask) { warnPlan<<"Summarytasks should not be calculated here: "<insertBackwardNode(this); cs->lateStart = cs->lateFinish - m_durationBackward; foreach (const Appointment *a, cs->appointments(Schedule::CalculateBackward)) { cs->logInfo(i18n("Resource %1 booked from %2 to %3", a->resource()->resource()->name(), locale.toString(a->startTime(), QLocale::ShortFormat), locale.toString(a->endTime(), QLocale::ShortFormat))); } // clean up temporary usage cs->startTime = DateTime(); cs->endTime = DateTime(); cs->duration = Duration::zeroDuration; cs->logInfo(i18n("Late start calculated: %1", locale.toString(cs->lateStart, QLocale::ShortFormat))); cs->incProgress(); #ifndef PLAN_NLOGDEBUG cs->logDebug(QStringLiteral("Finished calculate backward: %1 ms").arg(timer.elapsed())); #endif return cs->lateStart; } DateTime Task::schedulePredeccessors(const QList &list_, int use) { DateTime time; QMultiMap lst; for (Relation* r : list_) { lst.insert(-r->parent()->priority(), r); } const QList list = lst.values(); foreach (Relation *r, list) { if (r->parent()->type() == Type_Summarytask) { //debugPlan<<"Skip summarytask:"<parent()->name(); continue; // skip summarytasks } // schedule the predecessors DateTime earliest = r->parent()->earlyStart(); DateTime t = r->parent()->scheduleForward(earliest, use); switch (r->type()) { case Relation::StartStart: // I can't start before my predesseccor t = r->parent()->startTime() + r->lag(); break; case Relation::FinishFinish: // I can't end before my predecessor, so // I can't start before it's endtime - my duration #ifndef PLAN_NLOGDEBUG m_currentSchedule->logDebug(QStringLiteral("FinishFinish: get duration to calculate earliest start")); #endif t -= duration(t + r->lag(), use, true); break; default: t += r->lag(); break; } if (!time.isValid() || t > time) time = t; } //debugPlan<endTime; } if (m_currentSchedule == 0) { return DateTime(); } Schedule *cs = m_currentSchedule; //cs->logDebug(QString("Schedule forward (early start: %1)").arg(cs->earlyStart.toString())); cs->setCalculationMode(Schedule::Scheduling); DateTime startTime = earliest > cs->earlyStart ? earliest : cs->earlyStart; // First, calculate all my own predecessors DateTime time = schedulePredeccessors(dependParentNodes(), use); if (time > startTime) { startTime = time; //debugPlan<earlyStart.toString())); cs->startTime = cs->earlyStart; } QTime timer; timer.start(); cs->logInfo(i18n("Start schedule forward: %1 ", constraintToString(true))); QLocale locale; cs->logInfo(i18n("Schedule from start %1", locale.toString(cs->startTime, QLocale::ShortFormat))); //debugPlan<startTime<<"earliest:"<earlyStart; if (false/*useCalculateForwardAppointments*/ && m_estimate->type() == Estimate::Type_Effort && ! cs->allowOverbooking() && cs->hasAppointments(Schedule::CalculateForward) ) { #ifndef PLAN_NLOGDEBUG cs->logDebug("ASAP: " + cs->startTime.toString() + " earliest: " + cs->earlyStart.toString()); #endif cs->copyAppointments(Schedule::CalculateForward, Schedule::Scheduling); if (cs->recalculate() && completion().isStarted()) { // copy start times + appointments from parent schedule copyAppointments(); } cs->startTime = cs->appointmentStartTime(); cs->endTime = cs->appointmentEndTime(); Q_ASSERT(cs->startTime.isValid()); Q_ASSERT(cs->endTime.isValid()); cs->duration = cs->endTime - cs->startTime; if (cs->lateFinish > cs->endTime) { cs->positiveFloat = workTimeBefore(cs->lateFinish) - cs->endTime; } else { cs->positiveFloat = Duration::zeroDuration; } cs->logInfo(i18n("Scheduled: %1 to %2", locale.toString(cs->startTime, QLocale::ShortFormat), locale.toString(cs->endTime, QLocale::ShortFormat))); return cs->endTime; } cs->startTime = workTimeAfter(cs->startTime, cs); #ifndef PLAN_NLOGDEBUG cs->logDebug("ASAP: " + cs->startTime.toString() + " earliest: " + cs->earlyStart.toString()); #endif cs->duration = duration(cs->startTime, use, false); cs->endTime = cs->startTime + cs->duration; makeAppointments(); if (cs->recalculate() && completion().isStarted()) { // copy start times + appointments from parent schedule copyAppointments(); cs->duration = cs->endTime - cs->startTime; } if (cs->lateFinish > cs->endTime) { cs->positiveFloat = workTimeBefore(cs->lateFinish) - cs->endTime; } else { cs->positiveFloat = Duration::zeroDuration; } break; case Node::ALAP: // cs->startTime calculated above //debugPlan<startTime<endTime<<" latest="<lateFinish; cs->endTime = workTimeBefore(cs->lateFinish, cs); cs->duration = duration(cs->endTime, use, true); cs->startTime = cs->endTime - cs->duration; //debugPlan<endTime = workTimeBefore(cs->earlyFinish, cs); cs->duration = duration(cs->endTime, use, true); cs->startTime = cs->endTime - cs->duration; makeAppointments(); } if (cs->lateFinish > cs->endTime) { cs->positiveFloat = workTimeBefore(cs->lateFinish) - cs->endTime; } else { cs->positiveFloat = Duration::zeroDuration; } if (cs->recalculate() && completion().isStarted()) { cs->earlyStart = cs->startTime = completion().startTime(); } break; case Node::StartNotEarlier: // cs->startTime calculated above //debugPlan<<"StartNotEarlier:"<startTime<lateStart; cs->startTime = workTimeAfter(qMax(cs->startTime, m_constraintStartTime), cs); cs->duration = duration(cs->startTime, use, false); cs->endTime = cs->startTime + cs->duration; makeAppointments(); if (cs->recalculate() && completion().isStarted()) { // copy start times + appointments from parent schedule copyAppointments(); cs->duration = cs->endTime - cs->startTime; } if (cs->lateFinish > cs->endTime) { cs->positiveFloat = workTimeBefore(cs->lateFinish) - cs->endTime; } else { cs->positiveFloat = Duration::zeroDuration; } if (cs->startTime < m_constraintStartTime) { cs->constraintError = true; cs->negativeFloat = cs->startTime - m_constraintStartTime; cs->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString(true), cs->negativeFloat.toString(Duration::Format_i18nHour))); } break; case Node::FinishNotLater: // cs->startTime calculated above //debugPlan<<"FinishNotLater:"<startTime; cs->startTime = workTimeAfter(cs->startTime, cs); cs->duration = duration(cs->startTime, use, false); cs->endTime = cs->startTime + cs->duration; makeAppointments(); if (cs->recalculate() && completion().isStarted()) { // copy start times + appointments from parent schedule copyAppointments(); cs->duration = cs->endTime - cs->startTime; } if (cs->lateFinish > cs->endTime) { cs->positiveFloat = workTimeBefore(cs->lateFinish) - cs->endTime; } else { cs->positiveFloat = Duration::zeroDuration; } if (cs->endTime > m_constraintEndTime) { //warnPlan<<"cs->endTime > m_constraintEndTime"; cs->constraintError = true; cs->negativeFloat = cs->endTime - m_constraintEndTime; cs->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString(true), cs->negativeFloat.toString(Duration::Format_i18nHour))); } break; case Node::MustStartOn: // Always try to put it on time cs->startTime = workTimeAfter(m_constraintStartTime, cs); //debugPlan<<"MustStartOn="<startTime; cs->duration = duration(cs->startTime, use, false); cs->endTime = cs->startTime + cs->duration; #ifndef PLAN_NLOGDEBUG cs->logDebug(QStringLiteral("%1: Schedule from %2 to %3").arg(constraintToString()).arg(cs->startTime.toString()).arg(cs->endTime.toString())); #endif makeAppointments(); if (cs->recalculate() && completion().isStarted()) { // copy start times + appointments from parent schedule copyAppointments(); cs->duration = cs->endTime - cs->startTime; } if (cs->lateFinish > cs->endTime) { cs->positiveFloat = workTimeBefore(cs->lateFinish) - cs->endTime; } else { cs->positiveFloat = Duration::zeroDuration; } if (m_constraintStartTime < cs->startTime) { cs->constraintError = true; cs->negativeFloat = cs->startTime - m_constraintStartTime; cs->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString(true), cs->negativeFloat.toString(Duration::Format_i18nHour))); } break; case Node::MustFinishOn: // Just try to schedule on time cs->endTime = workTimeBefore(m_constraintEndTime, cs); cs->duration = duration(cs->endTime, use, true); cs->startTime = cs->endTime - cs->duration; //debugPlan<<"MustFinishOn:"<lateFinish<<":"<startTime<endTime; makeAppointments(); if (cs->recalculate() && completion().isStarted()) { // copy start times + appointments from parent schedule copyAppointments(); cs->duration = cs->endTime - cs->startTime; } if (cs->lateFinish > cs->endTime) { cs->positiveFloat = workTimeBefore(cs->lateFinish) - cs->endTime; } else { cs->positiveFloat = Duration::zeroDuration; } if (cs->endTime != m_constraintEndTime) { cs->constraintError = true; cs->negativeFloat = cs->endTime - m_constraintEndTime; cs->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString(true), cs->negativeFloat.toString(Duration::Format_i18nHour))); } break; case Node::FixedInterval: { // cs->startTime calculated above //debugPlan<<"FixedInterval="<startTime; cs->duration = m_constraintEndTime - m_constraintStartTime; if (m_constraintStartTime >= cs->earlyStart) { cs->startTime = m_constraintStartTime; cs->endTime = m_constraintEndTime; } else { cs->startTime = cs->earlyStart; cs->endTime = cs->startTime + cs->duration; cs->constraintError = true; } if (m_constraintStartTime < cs->startTime) { cs->constraintError = true; cs->negativeFloat = cs->startTime - m_constraintStartTime; cs->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString(true), cs->negativeFloat.toString(Duration::Format_i18nHour))); } if (cs->lateFinish > cs->endTime) { cs->positiveFloat = workTimeBefore(cs->lateFinish) - cs->endTime; } else { cs->positiveFloat = Duration::zeroDuration; } cs->workStartTime = m_constraintStartTime; cs->workEndTime = m_constraintEndTime; //debugPlan<<"FixedInterval="<startTime<<","<endTime; makeAppointments(); break; } default: break; } if (m_estimate->type() == Estimate::Type_Effort) { // HACK scheduling may accept deviation less than 5 mins to improve performance cs->effortNotMet = (m_estimate->value(use, cs->usePert()) - cs->plannedEffort()) > (5 * 60000); if (cs->effortNotMet) { cs->logError(i18n("Effort not met. Estimate: %1, planned: %2", estimate()->value(use, cs->usePert()).toHours(), cs->plannedEffort().toHours())); } } } else if (type() == Node::Type_Milestone) { if (cs->recalculate() && completion().isFinished()) { cs->startTime = completion().startTime(); cs->endTime = completion().finishTime(); m_visitedForward = true; return cs->endTime; } switch (m_constraint) { case Node::ASAP: { cs->endTime = cs->startTime; // TODO check, do we need to check successors earliestStart? cs->positiveFloat = cs->lateFinish - cs->endTime; break; } case Node::ALAP: { cs->startTime = qMax(cs->lateFinish, cs->earlyFinish); cs->endTime = cs->startTime; cs->positiveFloat = Duration::zeroDuration; break; } case Node::MustStartOn: case Node::MustFinishOn: case Node::FixedInterval: { //debugPlan<<"MustStartOn:"<startTime; DateTime contime = m_constraint == Node::MustFinishOn ? m_constraintEndTime : m_constraintStartTime; #ifndef PLAN_NLOGDEBUG cs->logDebug(QStringLiteral("%1: constraint time=%2, start time=%3").arg(constraintToString()).arg(contime.toString()).arg(cs->startTime.toString())); #endif if (cs->startTime < contime) { if (contime <= cs->lateFinish || contime <= cs->earlyFinish) { cs->startTime = contime; } } cs->negativeFloat = cs->startTime > contime ? cs->startTime - contime : contime - cs->startTime; if (cs->negativeFloat != 0) { cs->constraintError = true; cs->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString(true), cs->negativeFloat.toString(Duration::Format_i18nHour))); } cs->endTime = cs->startTime; if (cs->negativeFloat == Duration::zeroDuration) { cs->positiveFloat = cs->lateFinish - cs->endTime; } break; } case Node::StartNotEarlier: if (cs->startTime < m_constraintStartTime) { if (m_constraintStartTime <= cs->lateFinish || m_constraintStartTime <= cs->earlyFinish) { cs->startTime = m_constraintStartTime; } } if (cs->startTime < m_constraintStartTime) { cs->constraintError = true; cs->negativeFloat = m_constraintStartTime - cs->startTime; cs->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString(true), cs->negativeFloat.toString(Duration::Format_i18nHour))); } cs->endTime = cs->startTime; if (cs->negativeFloat == Duration::zeroDuration) { cs->positiveFloat = cs->lateFinish - cs->endTime; } break; case Node::FinishNotLater: //debugPlan<startTime; if (cs->startTime > m_constraintEndTime) { cs->constraintError = true; cs->negativeFloat = cs->startTime - m_constraintEndTime; cs->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString(true), cs->negativeFloat.toString(Duration::Format_i18nHour))); } cs->endTime = cs->startTime; if (cs->negativeFloat == Duration::zeroDuration) { cs->positiveFloat = cs->lateFinish - cs->endTime; } break; default: break; } cs->duration = Duration::zeroDuration; //debugPlan<startTime<<","<endTime; } else if (type() == Node::Type_Summarytask) { //shouldn't come here cs->endTime = cs->startTime; cs->duration = cs->endTime - cs->startTime; warnPlan<<"Summarytasks should not be calculated here: "<startTime<<" :"<endTime<<""<startTime < projectNode()->constraintStartTime() || cs->endTime > projectNode()->constraintEndTime()) { cs->logError(i18n("Failed to schedule within project target time")); } foreach (const Appointment *a, cs->appointments()) { cs->logInfo(i18n("Resource %1 booked from %2 to %3", a->resource()->resource()->name(), locale.toString(a->startTime(), QLocale::ShortFormat), locale.toString(a->endTime(), QLocale::ShortFormat))); } if (cs->startTime < cs->earlyStart) { cs->logWarning(i18n("Starting earlier than early start")); } if (cs->endTime > cs->lateFinish) { cs->logWarning(i18n("Finishing later than late finish")); } cs->logInfo(i18n("Scheduled: %1 to %2", locale.toString(cs->startTime, QLocale::ShortFormat), locale.toString(cs->endTime, QLocale::ShortFormat))); m_visitedForward = true; cs->incProgress(); m_requests.resetDynamicAllocations(); cs->logInfo(i18n("Finished schedule forward: %1 ms", timer.elapsed())); return cs->endTime; } DateTime Task::scheduleSuccessors(const QList &list_, int use) { DateTime time; QMultiMap lst; for (Relation* r : list_) { lst.insert(-r->child()->priority(), r); } const QList list = lst.values(); foreach (Relation *r, list) { if (r->child()->type() == Type_Summarytask) { //debugPlan<<"Skip summarytask:"<child()->name(); continue; } // get the successors starttime DateTime latest = r->child()->lateFinish(); DateTime t = r->child()->scheduleBackward(latest, use); switch (r->type()) { case Relation::StartStart: // I can't start before my successor, so // I can't finish later than it's starttime + my duration #ifndef PLAN_NLOGDEBUG m_currentSchedule->logDebug(QStringLiteral("StartStart: get duration to calculate late finish")); #endif t += duration(t - r->lag(), use, false); break; case Relation::FinishFinish: t = r->child()->endTime() - r->lag(); break; default: t -= r->lag(); break; } if (!time.isValid() || t < time) time = t; } return time; } DateTime Task::scheduleBackward(const DateTime &latest, int use) { if (m_scheduleBackwardRun) { return m_currentSchedule->startTime; } if (m_currentSchedule == 0) { return DateTime(); } Schedule *cs = m_currentSchedule; cs->setCalculationMode(Schedule::Scheduling); DateTime endTime = latest < cs->lateFinish ? latest : cs->lateFinish; // First, calculate all my own successors DateTime time = scheduleSuccessors(dependChildNodes(), use); if (time.isValid() && time < endTime) { endTime = time; } // Then my parents time = scheduleSuccessors(m_childProxyRelations, use); if (time.isValid() && time < endTime) { endTime = time; } if (! m_visitedBackward) { cs->endTime = endTime; } m_scheduleBackwardRun = true; return scheduleFromEndTime(use); } DateTime Task::scheduleFromEndTime(int use) { //debugPlan<setCalculationMode(Schedule::Scheduling); bool pert = cs->usePert(); if (m_visitedBackward) { return cs->startTime; } cs->notScheduled = false; if (!cs->endTime.isValid()) { cs->endTime = cs->lateFinish; } #ifndef PLAN_NLOGDEBUG QTime timer; timer.start(); cs->logDebug(QStringLiteral("Start schedule backward: %1 ").arg(constraintToString(true))); #endif QLocale locale; cs->logInfo(i18n("Schedule from end time: %1", cs->endTime.toString())); if (type() == Node::Type_Task) { cs->duration = m_estimate->value(use, pert); switch (m_constraint) { case Node::ASAP: { // cs->endTime calculated above //debugPlan<duration = duration(cs->endTime, use, true); e = cs->endTime; cs->startTime = e - cs->duration; } if (e > cs->lateFinish) { cs->schedulingError = true; cs->logError(i18nc("1=type of constraint", "%1: Failed to schedule within late finish.", constraintToString())); #ifndef PLAN_NLOGDEBUG cs->logDebug("ASAP: late finish=" + cs->lateFinish.toString() + " end time=" + e.toString()); #endif } else if (e > cs->endTime) { cs->schedulingError = true; cs->logWarning(i18nc("1=type of constraint", "%1: Failed to schedule within successors start time", constraintToString())); #ifndef PLAN_NLOGDEBUG cs->logDebug("ASAP: succ. start=" + cs->endTime.toString() + " end time=" + e.toString()); #endif } if (cs->lateFinish > e) { DateTime w = workTimeBefore(cs->lateFinish); if (w > e) { cs->positiveFloat = w - e; } #ifndef PLAN_NLOGDEBUG cs->logDebug("ASAP: positiveFloat=" + cs->positiveFloat.toString()); #endif } cs->endTime = e; makeAppointments(); break; } case Node::ALAP: { // cs->endTime calculated above //debugPlan<logDebug("ALAP: earlyStart=" + cs->earlyStart.toString() + " cs->startTime=" + cs->startTime.toString()); #endif } else if (cs->lateFinish > cs->endTime) { cs->positiveFloat = workTimeBefore(cs->lateFinish) - cs->endTime; #ifndef PLAN_NLOGDEBUG cs->logDebug("ALAP: positiveFloat=" + cs->positiveFloat.toString()); #endif } //debugPlan<endTime; cs->endTime = workTimeBefore(cs->endTime, cs); cs->duration = duration(cs->endTime, use, true); cs->startTime = cs->endTime - cs->duration; if (cs->startTime < m_constraintStartTime) { //warnPlan<<"m_constraintStartTime > cs->lateStart"; cs->constraintError = true; cs->negativeFloat = m_constraintStartTime - cs->startTime; cs->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString(true), cs->negativeFloat.toString(Duration::Format_i18nHour))); } makeAppointments(); if (cs->lateFinish > cs->endTime) { cs->positiveFloat = workTimeBefore(cs->lateFinish) - cs->endTime; } break; case Node::FinishNotLater: // cs->endTime calculated above //debugPlan<<"FinishNotLater:"<endTime; if (cs->endTime > m_constraintEndTime) { cs->endTime = qMax(qMin(m_constraintEndTime, cs->lateFinish), cs->earlyFinish); } cs->endTime = workTimeBefore(cs->endTime, cs); cs->duration = duration(cs->endTime, use, true); cs->startTime = cs->endTime - cs->duration; if (cs->endTime > m_constraintEndTime) { cs->negativeFloat = cs->endTime - m_constraintEndTime; cs->constraintError = true; cs->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString(true), cs->negativeFloat.toString(Duration::Format_i18nHour))); } makeAppointments(); if (cs->lateFinish > cs->endTime) { cs->positiveFloat = workTimeBefore(cs->lateFinish) - cs->endTime; } break; case Node::MustStartOn: // Just try to schedule on time //debugPlan<<"MustStartOn="<startTime.toString(); cs->startTime = workTimeAfter(m_constraintStartTime, cs); cs->duration = duration(cs->startTime, use, false); if (cs->endTime >= cs->startTime + cs->duration) { cs->endTime = cs->startTime + cs->duration; } else { cs->endTime = workTimeBefore(cs->endTime); cs->duration = duration(cs->endTime, use, true); cs->startTime = cs->endTime - cs->duration; } if (m_constraintStartTime != cs->startTime) { cs->constraintError = true; cs->negativeFloat = m_constraintStartTime - cs->startTime; cs->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString(true), cs->negativeFloat.toString(Duration::Format_i18nHour))); } makeAppointments(); if (cs->lateFinish > cs->endTime) { cs->positiveFloat = workTimeBefore(cs->lateFinish) - cs->endTime; } break; case Node::MustFinishOn: // Just try to schedule on time //debugPlan<endTime<earlyFinish; cs->endTime = workTimeBefore(m_constraintEndTime, cs); cs->duration = duration(cs->endTime, use, true); cs->startTime = cs->endTime - cs->duration; if (m_constraintEndTime != cs->endTime) { cs->negativeFloat = m_constraintEndTime - cs->endTime; cs->constraintError = true; cs->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString(true), cs->negativeFloat.toString(Duration::Format_i18nHour))); //warnPlan<<"m_constraintEndTime > cs->endTime"; } makeAppointments(); if (cs->lateFinish > cs->endTime) { cs->positiveFloat = workTimeBefore(cs->lateFinish) - cs->endTime; } break; case Node::FixedInterval: { // cs->endTime calculated above //debugPlan<endTime; cs->duration = m_constraintEndTime - m_constraintStartTime; if (cs->endTime > m_constraintEndTime) { cs->endTime = qMax(m_constraintEndTime, cs->earlyFinish); } cs->startTime = cs->endTime - cs->duration; if (m_constraintEndTime != cs->endTime) { cs->negativeFloat = m_constraintEndTime - cs->endTime; cs->constraintError = true; cs->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString(true), cs->negativeFloat.toString(Duration::Format_i18nHour))); } cs->workStartTime = workTimeAfter(cs->startTime); cs->workEndTime = workTimeBefore(cs->endTime); makeAppointments(); if (cs->negativeFloat == Duration::zeroDuration) { cs->positiveFloat = workTimeBefore(cs->lateFinish) - cs->endTime; } break; } default: break; } m_requests.reserve(cs->startTime, cs->duration); if (m_estimate->type() == Estimate::Type_Effort) { // HACK scheduling may accept deviation less than 5 mins to improve performance cs->effortNotMet = (m_estimate->value(use, cs->usePert()) - cs->plannedEffort()) > (5 * 60000); if (cs->effortNotMet) { cs->logError(i18n("Effort not met. Estimate: %1, planned: %2", estimate()->value(use, cs->usePert()).toHours(), cs->plannedEffort().toHours())); } } } else if (type() == Node::Type_Milestone) { switch (m_constraint) { case Node::ASAP: if (cs->endTime < cs->earlyStart) { cs->schedulingError = true; cs->logError(i18nc("1=type of constraint", "%1: Failed to schedule after early start.", constraintToString())); cs->endTime = cs->earlyStart; } else { cs->positiveFloat = cs->lateFinish - cs->endTime; } //cs->endTime = cs->earlyStart; FIXME need to follow predeccessors. Defer scheduling? cs->startTime = cs->endTime; break; case Node::ALAP: cs->startTime = cs->endTime; cs->positiveFloat = cs->lateFinish - cs->endTime; break; case Node::MustStartOn: case Node::MustFinishOn: case Node::FixedInterval: { DateTime contime = m_constraint == Node::MustFinishOn ? m_constraintEndTime : m_constraintStartTime; if (contime < cs->earlyStart) { if (cs->earlyStart < cs->endTime) { cs->endTime = cs->earlyStart; } } else if (contime < cs->endTime) { cs->endTime = contime; } cs->negativeFloat = cs->endTime > contime ? cs->endTime - contime : contime - cs->endTime; if (cs->negativeFloat != 0) { cs->constraintError = true; cs->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString(true), cs->negativeFloat.toString(Duration::Format_i18nHour))); } cs->startTime = cs->endTime; if (cs->negativeFloat == Duration::zeroDuration) { cs->positiveFloat = cs->lateFinish - cs->endTime; } break; } case Node::StartNotEarlier: cs->startTime = cs->endTime; if (m_constraintStartTime > cs->startTime) { cs->constraintError = true; cs->negativeFloat = m_constraintStartTime - cs->startTime; cs->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString(true), cs->negativeFloat.toString(Duration::Format_i18nHour))); } if (cs->negativeFloat == Duration::zeroDuration) { cs->positiveFloat = cs->lateFinish - cs->endTime; } break; case Node::FinishNotLater: if (m_constraintEndTime < cs->earlyStart) { if (cs->earlyStart < cs->endTime) { cs->endTime = cs->earlyStart; } } else if (m_constraintEndTime < cs->endTime) { cs->endTime = m_constraintEndTime; } if (m_constraintEndTime > cs->endTime) { cs->constraintError = true; cs->negativeFloat = cs->endTime - m_constraintEndTime; cs->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString(true), cs->negativeFloat.toString(Duration::Format_i18nHour))); } cs->startTime = cs->endTime; if (cs->negativeFloat == Duration::zeroDuration) { cs->positiveFloat = cs->lateFinish - cs->endTime; } break; default: break; } cs->duration = Duration::zeroDuration; } else if (type() == Node::Type_Summarytask) { //shouldn't come here cs->startTime = cs->endTime; cs->duration = cs->endTime - cs->startTime; warnPlan<<"Summarytasks should not be calculated here: "<startTime < projectNode()->constraintStartTime() || cs->endTime > projectNode()->constraintEndTime()) { cs->logError(i18n("Failed to schedule within project target time")); } foreach (const Appointment *a, cs->appointments()) { cs->logInfo(i18n("Resource %1 booked from %2 to %3", a->resource()->resource()->name(), locale.toString(a->startTime(), QLocale::ShortFormat), locale.toString(a->endTime(), QLocale::ShortFormat))); } if (cs->startTime < cs->earlyStart) { cs->logWarning(i18n("Starting earlier than early start")); } if (cs->endTime > cs->lateFinish) { cs->logWarning(i18n("Finishing later than late finish")); } cs->logInfo(i18n("Scheduled: %1 to %2", locale.toString(cs->startTime, QLocale::ShortFormat), locale.toString(cs->endTime, QLocale::ShortFormat))); m_visitedBackward = true; cs->incProgress(); m_requests.resetDynamicAllocations(); #ifndef PLAN_NLOGDEBUG cs->logDebug(QStringLiteral("Finished schedule backward: %1 ms").arg(timer.elapsed())); #endif return cs->startTime; } void Task::adjustSummarytask() { if (m_currentSchedule == 0) return; if (type() == Type_Summarytask) { DateTime start = m_currentSchedule->lateFinish; DateTime end = m_currentSchedule->earlyStart; foreach (Node *n, m_nodes) { n->adjustSummarytask(); if (n->startTime() < start) start = n->startTime(); if (n->endTime() > end) end = n->endTime(); } m_currentSchedule->startTime = start; m_currentSchedule->endTime = end; m_currentSchedule->duration = end - start; m_currentSchedule->notScheduled = false; //debugPlan<name<<":"<startTime.toString()<<" :"<endTime.toString(); } } Duration Task::duration(const DateTime &time, int use, bool backward) { //debugPlan; // TODO: handle risc if (m_currentSchedule == 0) { errorPlan<<"No current schedule"; return Duration::zeroDuration; } if (!time.isValid()) { #ifndef PLAN_NLOGDEBUG m_currentSchedule->logDebug(QStringLiteral("Calculate duration: Start time is not valid")); #endif return Duration::zeroDuration; } //debugPlan< calcDuration"<<(backward?"(B)":"(F)")<resourceNotAvailable = true; dur = effort; //??? } return dur; } if (m_estimate->type() == Estimate::Type_Duration) { return length(time, dur, backward); } errorPlan<<"Unsupported estimate type: "<type(); return dur; } Duration Task::length(const DateTime &time, KPlato::Duration duration, bool backward) { return length(time, duration, m_currentSchedule, backward); } Duration Task::length(const DateTime &time, KPlato::Duration duration, Schedule *sch, bool backward) { //debugPlan<<"--->"<<(backward?"(B)":"(F)")<logDebug(QStringLiteral("Calculate length: estimate == 0")); #else Q_UNUSED(sch) #endif return l; } Calendar *cal = m_estimate->calendar(); if (cal == 0) { #ifndef PLAN_NLOGDEBUG if (sch) sch->logDebug("Calculate length: No calendar, return estimate " + duration.toString()); #endif return duration; } #ifndef PLAN_NLOGDEBUG if (sch) sch->logDebug("Calculate length from: " + time.toString()); #endif DateTime logtime = time; bool sts=true; bool match = false; DateTime start = time; int inc = backward ? -1 : 1; DateTime end = start; Duration l1; int nDays = backward ? projectNode()->constraintStartTime().daysTo(time) : time.daysTo(projectNode()->constraintEndTime()); for (int i=0; !match && i <= nDays; ++i) { // days end = end.addDays(inc); l1 = backward ? cal->effort(end, start) : cal->effort(start, end); //debugPlan<<"["<logDebug("Days: duration " + logtime.toString() + " - " + end.toString() + " = " + l.toString() + " (" + (duration - l).toString() + ')'); #endif logtime = start; for (int i=0; !match && i < 24; ++i) { // hours end = end.addSecs(inc*60*60); l1 = backward ? cal->effort(end, start) : cal->effort(start, end); if (l + l1 < duration) { l += l1; start = end; } else if (l + l1 == duration) { l += l1; match = true; } else { end = start; break; } //debugPlan<<"duration(h)["<effort(end, start) : cal->effort(start, end); if (l + l1 < duration) { l += l1; start = end; } else if (l + l1 == duration) { l += l1; match = true; } else if (l + l1 > duration) { end = start; break; } //debugPlan<<"duration(m)"<<(backward?"backward":"forward:")<<" time="<effort(end, start) : cal->effort(start, end); if (l + l1 < duration) { l += l1; start = end; } else if (l + l1 == duration) { l += l1; match = true; } else if (l + l1 > duration) { end = start; break; } //debugPlan<<"duration(s)["<logDebug("Seconds: duration " + logtime.toString() + " - " + end.toString() + " l " + l.toString() + " (" + (duration - l).toString() + ')'); #endif for (int i=0; !match && i < 1000; ++i) { //milliseconds end.setTime(end.time().addMSecs(inc)); l1 = backward ? cal->effort(end, start) : cal->effort(start, end); if (l + l1 < duration) { l += l1; start = end; } else if (l + l1 == duration) { l += l1; match = true; } else { #ifndef PLAN_NLOGDEBUG if (sch) sch->logDebug("Got more than asked for, should not happen! Want: " + duration.toString(Duration::Format_Hour) + " got: " + l.toString(Duration::Format_Hour)); #endif break; } //debugPlan<<"duration(ms)["<logError(i18n("Could not match work duration. Want: %1 got: %2", l.toString(Duration::Format_i18nHour), duration.toString(Duration::Format_i18nHour))); } DateTime t = end; if (l != Duration::zeroDuration) { if (backward) { if (end < projectNode()->constraintEndTime()) { t = cal->firstAvailableAfter(end, projectNode()->constraintEndTime()); } } else { if (end > projectNode()->constraintStartTime()) { t = cal->firstAvailableBefore(end, projectNode()->constraintStartTime()); } } #ifndef PLAN_NLOGDEBUG if (sch) sch->logDebug("Moved end to work: " + end.toString() + " -> " + t.toString()); #endif } end = t.isValid() ? t : time; //debugPlan<<"<---"<<(backward?"(B)":"(F)")<time ? end-time : time-end; if (match) { #ifndef PLAN_NLOGDEBUG if (sch) sch->logDebug("Calculated length: " + time.toString() + " - " + end.toString() + " = " + l.toString()); #endif } return l; } void Task::clearProxyRelations() { m_parentProxyRelations.clear(); m_childProxyRelations.clear(); } void Task::addParentProxyRelations(const QList &list) { //debugPlan<addParentProxyRelations(list); n->addParentProxyRelations(dependParentNodes()); } } else { // add 'this' as child relation to the relations parent //debugPlan<parent()->addChildProxyRelation(this, r); // add a parent relation to myself addParentProxyRelation(r->parent(), r); } } } void Task::addChildProxyRelations(const QList &list) { //debugPlan<addChildProxyRelations(list); n->addChildProxyRelations(dependChildNodes()); } } else { // add 'this' as parent relation to the relations child //debugPlan<child()->addParentProxyRelation(this, r); // add a child relation to myself addChildProxyRelation(r->child(), r); } } } void Task::addParentProxyRelation(Node *node, const Relation *rel) { if (node->type() != Type_Summarytask) { if (type() == Type_Summarytask) { //debugPlan<<"Add parent proxy from my children"<name(); foreach (Node *n, m_nodes) { n->addParentProxyRelation(node, rel); } } else { //debugPlan<<"Add parent proxy from"<name()<<" to (me)"<type(), rel->lag())); } } } void Task::addChildProxyRelation(Node *node, const Relation *rel) { if (node->type() != Type_Summarytask) { if (type() == Type_Summarytask) { //debugPlan<<"Add child proxy from my children"<name(); foreach (Node *n, m_nodes) { n->addChildProxyRelation(node, rel); } } else { //debugPlan<<"Add child proxy from (me)"<name(); m_childProxyRelations.append(new ProxyRelation(this, node, rel->type(), rel->lag())); } } } bool Task::isEndNode() const { return m_dependChildNodes.isEmpty() && m_childProxyRelations.isEmpty(); } bool Task::isStartNode() const { return m_dependParentNodes.isEmpty() && m_parentProxyRelations.isEmpty(); } DateTime Task::workTimeAfter(const DateTime &dt, Schedule *sch) const { DateTime t; if (m_estimate->type() == Estimate::Type_Duration) { if (m_estimate->calendar()) { t = m_estimate->calendar()->firstAvailableAfter(dt, projectNode()->constraintEndTime()); } } else { t = m_requests.workTimeAfter(dt, sch); #ifndef PLAN_NLOGDEBUG if (sch) sch->logDebug(QStringLiteral("workTimeAfter: %1 = %2").arg(dt.toString()).arg(t.toString())); #endif } return t.isValid() ? t : dt; } DateTime Task::workTimeBefore(const DateTime &dt, Schedule *sch) const { DateTime t; if (m_estimate->type() == Estimate::Type_Duration) { if (m_estimate->calendar()) { t = m_estimate->calendar()->firstAvailableBefore(dt, projectNode()->constraintStartTime()); } } else { t = m_requests.workTimeBefore(dt, sch); } return t.isValid() ? t : dt; } Duration Task::positiveFloat(long id) const { Schedule *s = schedule(id); return s == 0 ? Duration::zeroDuration : s->positiveFloat; } void Task::setPositiveFloat(KPlato::Duration fl, long id) const { Schedule *s = schedule(id); if (s) s->positiveFloat = fl; } Duration Task::negativeFloat(long id) const { Schedule *s = schedule(id); return s == 0 ? Duration::zeroDuration : s->negativeFloat; } void Task::setNegativeFloat(KPlato::Duration fl, long id) const { Schedule *s = schedule(id); if (s) s->negativeFloat = fl; } Duration Task::freeFloat(long id) const { Schedule *s = schedule(id); return s == 0 ? Duration::zeroDuration : s->freeFloat; } void Task::setFreeFloat(KPlato::Duration fl, long id) const { Schedule *s = schedule(id); if (s) s->freeFloat = fl; } Duration Task::startFloat(long id) const { Schedule *s = schedule(id); return s == 0 || s->earlyStart > s->lateStart ? Duration::zeroDuration : (s->earlyStart - s->lateStart); } Duration Task::finishFloat(long id) const { Schedule *s = schedule(id); return s == 0 || s->lateFinish < s->earlyFinish ? Duration::zeroDuration : (s->lateFinish - s->earlyFinish); } bool Task::isCritical(long id) const { Schedule *s = schedule(id); return s == 0 ? false : s->isCritical(); } bool Task::calcCriticalPath(bool fromEnd) { if (m_currentSchedule == 0) return false; //debugPlan<child()->calcCriticalPath(fromEnd)) { m_currentSchedule->inCriticalPath = true; } } foreach (Relation *r, m_dependChildNodes) { if (r->child()->calcCriticalPath(fromEnd)) { m_currentSchedule->inCriticalPath = true; } } } else { if (isStartNode() && startFloat() == 0 && finishFloat() == 0) { m_currentSchedule->inCriticalPath = true; //debugPlan<parent()->calcCriticalPath(fromEnd)) { m_currentSchedule->inCriticalPath = true; } } foreach (Relation *r, m_dependParentNodes) { if (r->parent()->calcCriticalPath(fromEnd)) { m_currentSchedule->inCriticalPath = true; } } } //debugPlan<freeFloat.toString(); } } void Task::setCurrentSchedule(long id) { setCurrentSchedulePtr(findSchedule(id)); Node::setCurrentSchedule(id); } bool Task::effortMetError(long id) const { Schedule *s = schedule(id); if (s == 0 || s->notScheduled || m_estimate->type() != Estimate::Type_Effort) { return false; } return s->effortNotMet; } uint Task::state(long id) const { int st = Node::State_None; if (! isScheduled(id)) { st |= State_NotScheduled; } if (completion().isFinished()) { st |= Node::State_Finished; if (completion().finishTime() > endTime(id)) { st |= State_FinishedLate; } if (completion().finishTime() < endTime(id)) { st |= State_FinishedEarly; } } else if (completion().isStarted()) { st |= Node::State_Started; if (completion().startTime() > startTime(id)) { st |= State_StartedLate; } if (completion().startTime() < startTime(id)) { st |= State_StartedEarly; } if (completion().percentFinished() > 0) { st |= State_Running; } if (endTime(id) < QDateTime::currentDateTime()) { st |= State_Late; } } else if (isScheduled(id)) { if (startTime(id) < QDateTime::currentDateTime()) { st |= State_Late; } } st |= State_ReadyToStart; //TODO: check proxy relations foreach (const Relation *r, m_dependParentNodes) { if (! static_cast(r->parent())->completion().isFinished()) { st &= ~Node::State_ReadyToStart; st |= Node::State_NotReadyToStart; break; } } return st; } void Task::addWorkPackage(WorkPackage *wp) { emit workPackageToBeAdded(this, m_packageLog.count()); m_packageLog.append(wp); emit workPackageAdded(this); } void Task::removeWorkPackage(WorkPackage *wp) { int index = m_packageLog.indexOf(wp); if (index < 0) { return; } emit workPackageToBeRemoved(this, index); m_packageLog.removeAt(index); emit workPackageRemoved(this); } WorkPackage *Task::workPackageAt(int index) const { Q_ASSERT (index >= 0 && index < m_packageLog.count()); return m_packageLog.at(index); } QString Task::wpOwnerName() const { if (m_packageLog.isEmpty()) { return m_workPackage.ownerName(); } return m_packageLog.last()->ownerName(); } WorkPackage::WPTransmitionStatus Task::wpTransmitionStatus() const { if (m_packageLog.isEmpty()) { return m_workPackage.transmitionStatus(); } return m_packageLog.last()->transmitionStatus(); } DateTime Task::wpTransmitionTime() const { if (m_packageLog.isEmpty()) { return m_workPackage.transmitionTime(); } return m_packageLog.last()->transmitionTime(); } //------------------------------------------ Completion::Completion(Node *node) : m_node(node), m_started(false), m_finished(false), m_entrymode(EnterEffortPerResource) {} Completion::Completion(const Completion &c) { copy(c); } Completion::~Completion() { qDeleteAll(m_entries); qDeleteAll(m_usedEffort); } void Completion::copy(const Completion &p) { m_node = 0; //NOTE m_started = p.isStarted(); m_finished = p.isFinished(); m_startTime = p.startTime(); m_finishTime = p.finishTime(); m_entrymode = p.entrymode(); qDeleteAll(m_entries); m_entries.clear(); Completion::EntryList::ConstIterator entriesIt = p.entries().constBegin(); const Completion::EntryList::ConstIterator entriesEnd = p.entries().constEnd(); for (; entriesIt != entriesEnd; ++entriesIt) { addEntry(entriesIt.key(), new Entry(*entriesIt.value())); } qDeleteAll(m_usedEffort); m_usedEffort.clear(); Completion::ResourceUsedEffortMap::ConstIterator usedEffortMapIt = p.usedEffortMap().constBegin(); const Completion::ResourceUsedEffortMap::ConstIterator usedEffortMapEnd = p.usedEffortMap().constEnd(); for (; usedEffortMapIt != usedEffortMapEnd; ++usedEffortMapIt) { addUsedEffort(usedEffortMapIt.key(), new UsedEffort(*usedEffortMapIt.value())); } } bool Completion::operator==(const Completion &p) { return m_started == p.isStarted() && m_finished == p.isFinished() && m_startTime == p.startTime() && m_finishTime == p.finishTime() && m_entries == p.entries() && m_usedEffort == p.usedEffortMap(); } Completion &Completion::operator=(const Completion &p) { copy(p); return *this; } void Completion::changed(int property) { if (m_node) { m_node->changed(property); } } void Completion::setStarted(bool on) { m_started = on; changed(Node::CompletionStartedProperty); } void Completion::setFinished(bool on) { m_finished = on; changed(Node::CompletionFinishedProperty); } void Completion::setStartTime(const DateTime &dt) { m_startTime = dt; changed(Node::CompletionStartTimeProperty); } void Completion::setFinishTime(const DateTime &dt) { m_finishTime = dt; changed(Node::CompletionFinishTimeProperty); } void Completion::setPercentFinished(QDate date, int value) { Entry *e = 0; if (m_entries.contains(date)) { e = m_entries[ date ]; } else { e = new Entry(); m_entries[ date ] = e; } e->percentFinished = value; changed(Node::CompletionPercentageProperty); } void Completion::setRemainingEffort(QDate date, KPlato::Duration value) { Entry *e = 0; if (m_entries.contains(date)) { e = m_entries[ date ]; } else { e = new Entry(); m_entries[ date ] = e; } e->remainingEffort = value; changed(Node::CompletionRemainingEffortProperty); } void Completion::setActualEffort(QDate date, KPlato::Duration value) { Entry *e = 0; if (m_entries.contains(date)) { e = m_entries[ date ]; } else { e = new Entry(); m_entries[ date ] = e; } e->totalPerformed = value; changed(Node::CompletionActualEffortProperty); } void Completion::addEntry(QDate date, Entry *entry) { m_entries.insert(date, entry); //debugPlan<percentFinished; } int Completion::percentFinished(QDate date) const { int x = 0; EntryList::const_iterator it; for (it = m_entries.constBegin(); it != m_entries.constEnd() && it.key() <= date; ++it) { x = it.value()->percentFinished; } return x; } Duration Completion::remainingEffort() const { return m_entries.isEmpty() ? Duration::zeroDuration : m_entries.last()->remainingEffort; } Duration Completion::remainingEffort(QDate date) const { Duration x; EntryList::const_iterator it; for (it = m_entries.constBegin(); it != m_entries.constEnd() && it.key() <= date; ++it) { x = it.value()->remainingEffort; } return x; } Duration Completion::actualEffort() const { Duration eff; if (m_entrymode == EnterEffortPerResource) { foreach(const UsedEffort *ue, m_usedEffort) { const QMap map = ue->actualEffortMap(); QMap::const_iterator it; for (it = map.constBegin(); it != map.constEnd(); ++it) { eff += it.value().effort(); } } } else if (! m_entries.isEmpty()) { eff = m_entries.last()->totalPerformed; } return eff; } Duration Completion::actualEffort(const Resource *resource, QDate date) const { UsedEffort *ue = usedEffort(resource); if (ue == 0) { return Duration::zeroDuration; } UsedEffort::ActualEffort ae = ue->effort(date); return ae.effort(); } Duration Completion::actualEffort(QDate date) const { Duration eff; if (m_entrymode == EnterEffortPerResource) { foreach(const UsedEffort *ue, m_usedEffort) { if (ue && ue->actualEffortMap().contains(date)) { eff += ue->actualEffortMap().value(date).effort(); } } } else { // Hmmm: How to really know a specific date? if (m_entries.contains(date)) { eff = m_entries[ date ]->totalPerformed; } } return eff; } Duration Completion::actualEffortTo(QDate date) const { //debugPlan<effortTo(date); } } else { QListIterator it(m_entries.uniqueKeys()); it.toBack(); while (it.hasPrevious()) { QDate d = it.previous(); if (d <= date) { eff = m_entries[ d ]->totalPerformed; break; } } } return eff; } double Completion::averageCostPrHour(QDate date, long id) const { Schedule *s = m_node->schedule(id); if (s == 0) { return 0.0; } double cost = 0.0; double eff = 0.0; QList cl; foreach (const Appointment *a, s->appointments()) { cl << a->resource()->resource()->normalRate(); double e = a->plannedEffort(date).toDouble(Duration::Unit_h); if (e > 0.0) { eff += e; cost += e * cl.last(); } } if (eff > 0.0) { cost /= eff; } else { foreach (double c, cl) { cost += c; } cost /= cl.count(); } return cost; } EffortCostMap Completion::effortCostPrDay(QDate start, QDate end, long id) const { //debugPlan<name()< et) { break; } Duration e = m_entries[ d ]->totalPerformed; if (e != Duration::zeroDuration && e != last) { Duration eff = e - last; ec.insert(d, eff, eff.toDouble(Duration::Unit_h) * averageCostPrHour(d, id)); last = e; } } break; } case EnterEffortPerResource: { std::pair dates = actualStartEndDates(); if (! dates.first.isValid()) { // no data, so just break break; } QDate st = start.isValid() ? start : dates.first; QDate et = end.isValid() ? end : dates.second; for (QDate d = st; d <= et; d = d.addDays(1)) { ec.add(d, actualEffort(d), actualCost(d)); } break; } } return ec; } EffortCostMap Completion::effortCostPrDay(const Resource *resource, QDate start, QDate end, long id) const { Q_UNUSED(id); //debugPlan<name()< dates = actualStartEndDates(); if (! dates.first.isValid()) { // no data, so just break break; } QDate st = start.isValid() ? start : dates.first; QDate et = end.isValid() ? end : dates.second; for (QDate d = st; d <= et; d = d.addDays(1)) { ec.add(d, actualEffort(resource, d), actualCost(resource, d)); } break; } } return ec; } void Completion::addUsedEffort(const Resource *resource, Completion::UsedEffort *value) { UsedEffort *v = value == 0 ? new UsedEffort() : value; if (m_usedEffort.contains(resource)) { m_usedEffort[ resource ]->mergeEffort(*v); delete v; } else { m_usedEffort.insert(resource, v); } changed(Node::CompletionUsedEffortProperty); } void Completion::setActualEffort(Resource *resource, const QDate &date, const Completion::UsedEffort::ActualEffort &value) { if (value.isNull()) { if (!m_usedEffort.contains(resource)) { return; } UsedEffort *ue = m_usedEffort.value(resource); if (!ue) { return; } ue->takeEffort(date); } else { UsedEffort *ue = m_usedEffort[resource]; if (!ue) { ue = new UsedEffort(); m_usedEffort.insert(resource, ue); } ue->setEffort(date, value); } changed(Node::CompletionActualEffortProperty); } Completion::UsedEffort::ActualEffort Completion::getActualEffort(Resource *resource, const QDate &date) const { UsedEffort::ActualEffort value; UsedEffort *ue = m_usedEffort.value(resource); if (ue) { value = ue->effort(date); } return value; } QString Completion::note() const { return m_entries.isEmpty() ? QString() : m_entries.last()->note; } void Completion::setNote(const QString &str) { if (! m_entries.isEmpty()) { m_entries.last()->note = str; changed(Node::CompletionNoteProperty); } } std::pair Completion::actualStartEndDates() const { std::pair p; ResourceUsedEffortMap::const_iterator it; for (it = m_usedEffort.constBegin(); it != m_usedEffort.constEnd(); ++it) { if (!it.value()->actualEffortMap().isEmpty()) { QDate d = it.value()->firstDate(); if (!p.first.isValid() || d < p.first) { p.first = d; } d = it.value()->lastDate(); if (!p.second.isValid() || d > p.second) { p.second = d; } } } return p; } double Completion::actualCost(QDate date) const { //debugPlan<normalRate(); double oc = it.key()->overtimeRate(); if (it.value()->actualEffortMap().contains(date)) { UsedEffort::ActualEffort a = it.value()->effort(date); c += a.normalEffort().toDouble(Duration::Unit_h) * nc; c += a.overtimeEffort().toDouble(Duration::Unit_h) * oc; } } return c; } double Completion::actualCost(const Resource *resource) const { UsedEffort *ue = usedEffort(resource); if (ue == 0) { return 0.0; } double c = 0.0; double nc = resource->normalRate(); double oc = resource->overtimeRate(); foreach (const UsedEffort::ActualEffort &a, ue->actualEffortMap()) { c += a.normalEffort().toDouble(Duration::Unit_h) * nc; c += a.overtimeEffort().toDouble(Duration::Unit_h) * oc; } return c; } double Completion::actualCost() const { double c = 0.0; ResourceUsedEffortMap::const_iterator it; for (it = m_usedEffort.constBegin(); it != m_usedEffort.constEnd(); ++it) { c += actualCost(it.key()); } return c; } double Completion::actualCost(const Resource *resource, QDate date) const { UsedEffort *ue = usedEffort(resource); if (ue == 0) { return 0.0; } UsedEffort::ActualEffort a = ue->actualEffortMap().value(date); double c = a.normalEffort().toDouble(Duration::Unit_h) * resource->normalRate(); c += a.overtimeEffort().toDouble(Duration::Unit_h) * resource->overtimeRate(); return c; } EffortCostMap Completion::actualEffortCost(long int id, KPlato::EffortCostCalculationType type) const { //debugPlan; EffortCostMap map; if (! isStarted()) { return map; } QList< QMap > lst; QList< double > rate; QDate start, end; ResourceUsedEffortMap::const_iterator it; for (it = m_usedEffort.constBegin(); it != m_usedEffort.constEnd(); ++it) { const Resource *r = it.key(); //debugPlan<name()<name(); lst << usedEffort(r)->actualEffortMap(); if (lst.last().isEmpty()) { lst.takeLast(); continue; } if (r->type() == Resource::Type_Material) { if (type == ECCT_All) { rate.append(r->normalRate()); } else if (type == ECCT_EffortWork) { rate.append(0.0); } else { lst.takeLast(); continue; } } else { rate.append(r->normalRate()); } if (! start.isValid() || start > lst.last().firstKey()) { start = lst.last().firstKey(); } if (! end.isValid() || end < lst.last().lastKey()) { end = lst.last().lastKey(); } } if (! lst.isEmpty() && start.isValid() && end.isValid()) { for (QDate d = start; d <= end; d = d.addDays(1)) { EffortCost c; for (int i = 0; i < lst.count(); ++i) { UsedEffort::ActualEffort a = lst.at(i).value(d); double nc = rate.value(i); Duration eff = a.normalEffort(); double cost = eff.toDouble(Duration::Unit_h) * nc; c.add(eff, cost); } if (c.effort() != Duration::zeroDuration || c.cost() != 0.0) { map.add(d, c); } } } else if (! m_entries.isEmpty()) { QDate st = start.isValid() ? start : m_startTime.date(); QDate et = end.isValid() ? end : m_finishTime.date(); Duration last; foreach (const QDate &d, m_entries.uniqueKeys()) { if (d < st) { continue; } Duration e = m_entries[ d ]->totalPerformed; if (e != Duration::zeroDuration && e != last) { //debugPlan<name()< et) { break; } } } return map; } EffortCost Completion::actualCostTo(long int id, QDate date) const { //debugPlan<(m); } QString Completion::entryModeToString() const { return entrymodeList().value(m_entrymode); } bool Completion::loadXML(KoXmlElement &element, XMLLoaderObject &status) { //debugPlan; QString s; m_started = (bool)element.attribute(QStringLiteral("started"), QStringLiteral("0")).toInt(); m_finished = (bool)element.attribute(QStringLiteral("finished"), QStringLiteral("0")).toInt(); s = element.attribute(QStringLiteral("startTime")); if (!s.isEmpty()) { m_startTime = DateTime::fromString(s, status.projectTimeZone()); } s = element.attribute(QStringLiteral("finishTime")); if (!s.isEmpty()) { m_finishTime = DateTime::fromString(s, status.projectTimeZone()); } setEntrymode(element.attribute(QStringLiteral("entrymode"))); if (status.version() < QLatin1String("0.6")) { if (m_started) { Entry *entry = new Entry(element.attribute(QStringLiteral("percent-finished"), QStringLiteral("0")).toInt(), Duration::fromString(element.attribute(QStringLiteral("remaining-effort"))), Duration::fromString(element.attribute(QStringLiteral("performed-effort")))); entry->note = element.attribute(QStringLiteral("note")); QDate date = m_startTime.date(); if (m_finished) { date = m_finishTime.date(); } // almost the best we can do ;) addEntry(date, entry); } } else { KoXmlElement e; forEachElement(e, element) { if (e.tagName() == QLatin1String("completion-entry")) { QDate date; s = e.attribute(QStringLiteral("date")); if (!s.isEmpty()) { date = QDate::fromString(s, Qt::ISODate); } if (!date.isValid()) { warnPlan<<"Invalid date: "<percentFinished); elm.setAttribute(QStringLiteral("remaining-effort"), e->remainingEffort.toString()); elm.setAttribute(QStringLiteral("performed-effort"), e->totalPerformed.toString()); elm.setAttribute(QStringLiteral("note"), e->note); } if (! m_usedEffort.isEmpty()) { QDomElement elm = el.ownerDocument().createElement(QStringLiteral("used-effort")); el.appendChild(elm); ResourceUsedEffortMap::ConstIterator i = m_usedEffort.constBegin(); for (; i != m_usedEffort.constEnd(); ++i) { if (i.value() == 0) { continue; } QDomElement e = elm.ownerDocument().createElement(QStringLiteral("resource")); elm.appendChild(e); e.setAttribute(QStringLiteral("id"), i.key()->id()); i.value()->saveXML(e); } } } //-------------- Completion::UsedEffort::UsedEffort() { } Completion::UsedEffort::UsedEffort(const UsedEffort &e) { mergeEffort(e); } Completion::UsedEffort::~UsedEffort() { } void Completion::UsedEffort::mergeEffort(const Completion::UsedEffort &value) { const QMap map = value.actualEffortMap(); QMap::const_iterator it; for (it = map.constBegin(); it != map.constEnd(); ++it) { setEffort(it.key(), it.value()); } } void Completion::UsedEffort::setEffort(QDate date, const ActualEffort &value) { m_actual.insert(date, value); } Duration Completion::UsedEffort::effortTo(QDate date) const { Duration eff; QMap::const_iterator it; for (it = m_actual.constBegin(); it != m_actual.constEnd() && it.key() <= date; ++it) { eff += it.value().effort(); } return eff; } Duration Completion::UsedEffort::effort() const { Duration eff; foreach (const ActualEffort &e, m_actual) { eff += e.effort(); } return eff; } bool Completion::UsedEffort::operator==(const Completion::UsedEffort &e) const { return m_actual == e.actualEffortMap(); } bool Completion::UsedEffort::loadXML(KoXmlElement &element, XMLLoaderObject &) { //debugPlan; KoXmlElement e; forEachElement(e, element) { if (e.tagName() == QLatin1String("actual-effort")) { QDate date = QDate::fromString(e.attribute(QStringLiteral("date")), Qt::ISODate); if (date.isValid()) { ActualEffort a; a.setNormalEffort(Duration::fromString(e.attribute(QStringLiteral("normal-effort")))); a.setOvertimeEffort(Duration::fromString(e.attribute(QStringLiteral("overtime-effort")))); setEffort(date, a); } } } return true; } void Completion::UsedEffort::saveXML(QDomElement &element) const { if (m_actual.isEmpty()) { return; } DateUsedEffortMap::ConstIterator i = m_actual.constBegin(); for (; i != m_actual.constEnd(); ++i) { QDomElement el = element.ownerDocument().createElement(QStringLiteral("actual-effort")); element.appendChild(el); el.setAttribute(QStringLiteral("overtime-effort"), i.value().overtimeEffort().toString()); el.setAttribute(QStringLiteral("normal-effort"), i.value().normalEffort().toString()); el.setAttribute(QStringLiteral("date"), i.key().toString(Qt::ISODate)); } } //---------------------------------- WorkPackage::WorkPackage(Task *task) : m_task(task), m_manager(0), m_transmitionStatus(TS_None) { m_completion.setNode(task); } WorkPackage::WorkPackage(const WorkPackage &wp) : m_task(0), m_manager(0), m_completion(wp.m_completion), m_ownerName(wp.m_ownerName), m_ownerId(wp.m_ownerId), m_transmitionStatus(wp.m_transmitionStatus), m_transmitionTime(wp.m_transmitionTime) { } WorkPackage::~WorkPackage() { } bool WorkPackage::loadXML(KoXmlElement &element, XMLLoaderObject &status) { Q_UNUSED(status); m_ownerName = element.attribute(QStringLiteral("owner")); m_ownerId = element.attribute(QStringLiteral("owner-id")); return true; } void WorkPackage::saveXML(QDomElement &element) const { QDomElement el = element.ownerDocument().createElement(QStringLiteral("workpackage")); element.appendChild(el); el.setAttribute(QStringLiteral("owner"), m_ownerName); el.setAttribute(QStringLiteral("owner-id"), m_ownerId); } bool WorkPackage::loadLoggedXML(KoXmlElement &element, XMLLoaderObject &status) { m_ownerName = element.attribute(QStringLiteral("owner")); m_ownerId = element.attribute(QStringLiteral("owner-id")); m_transmitionStatus = transmitionStatusFromString(element.attribute(QStringLiteral("status"))); m_transmitionTime = DateTime(QDateTime::fromString(element.attribute(QStringLiteral("time")), Qt::ISODate)); return m_completion.loadXML(element, status); } void WorkPackage::saveLoggedXML(QDomElement &element) const { QDomElement el = element.ownerDocument().createElement(QStringLiteral("workpackage")); element.appendChild(el); el.setAttribute(QStringLiteral("owner"), m_ownerName); el.setAttribute(QStringLiteral("owner-id"), m_ownerId); el.setAttribute(QStringLiteral("status"), transmitionStatusToString(m_transmitionStatus)); el.setAttribute(QStringLiteral("time"), m_transmitionTime.toString(Qt::ISODate)); m_completion.saveXML(el); } QList WorkPackage::fetchResources() { return fetchResources(id()); } QList WorkPackage::fetchResources(long id) { //debugPlan< lst; if (id == NOTSCHEDULED) { if (m_task) { lst << m_task->requestedResources(); } } else { if (m_task) lst = m_task->assignedResources(id); foreach (const Resource *r, m_completion.resources()) { if (! lst.contains(const_cast(r))) { lst << const_cast(r); } } } return lst; } Completion &WorkPackage::completion() { return m_completion; } const Completion &WorkPackage::completion() const { return m_completion; } void WorkPackage::setScheduleManager(ScheduleManager *sm) { m_manager = sm; } QString WorkPackage::transmitionStatusToString(WorkPackage::WPTransmitionStatus sts, bool trans) { QString s = trans ? i18n("None") : QStringLiteral("None"); switch (sts) { case TS_Send: s = trans ? i18n("Send") : QStringLiteral("Send"); break; case TS_Receive: s = trans ? i18n("Receive") : QStringLiteral("Receive"); break; case TS_Rejected: s = trans ? i18n("Rejected") : QStringLiteral("Rejected"); break; default: break; } return s; } WorkPackage::WPTransmitionStatus WorkPackage::transmitionStatusFromString(const QString &sts) { QStringList lst; lst << QStringLiteral("None") << QStringLiteral("Send") << QStringLiteral("Receive"); int s = lst.indexOf(sts); return s < 0 ? TS_None : static_cast(s); } void WorkPackage::clear() { //m_task = 0; m_manager = 0; m_ownerName.clear(); m_ownerId.clear(); m_transmitionStatus = TS_None; m_transmitionTime = DateTime(); m_log.clear(); m_completion = Completion(); m_completion.setNode(m_task); } //-------------------------------- WorkPackageSettings::WorkPackageSettings() : usedEffort(true), progress(true), documents(true), remainingEffort(true) { } void WorkPackageSettings::saveXML(QDomElement &element) const { QDomElement el = element.ownerDocument().createElement(QStringLiteral("settings")); element.appendChild(el); el.setAttribute(QStringLiteral("used-effort"), QString::number(usedEffort)); el.setAttribute(QStringLiteral("progress"), QString::number(progress)); el.setAttribute(QStringLiteral("documents"), QString::number(documents)); el.setAttribute(QStringLiteral("remaining-effort"), QString::number(remainingEffort)); } bool WorkPackageSettings::loadXML(const KoXmlElement &element) { usedEffort = (bool)element.attribute(QStringLiteral("used-effort")).toInt(); progress = (bool)element.attribute(QStringLiteral("progress")).toInt(); documents = (bool)element.attribute(QStringLiteral("documents")).toInt(); remainingEffort = (bool)element.attribute(QStringLiteral("remaining-effort")).toInt(); return true; } bool WorkPackageSettings::operator==(KPlato::WorkPackageSettings s) const { return usedEffort == s.usedEffort && progress == s.progress && documents == s.documents && remainingEffort == s.remainingEffort; } bool WorkPackageSettings::operator!=(KPlato::WorkPackageSettings s) const { return ! operator==(s); } } //KPlato namespace #ifndef QT_NO_DEBUG_STREAM QDebug operator<<(QDebug dbg, const KPlato::Completion::UsedEffort::ActualEffort &ae) { dbg << QStringLiteral("%1").arg(ae.normalEffort().toDouble(KPlato::Duration::Unit_h), 1); return dbg; } #endif diff --git a/src/libs/kernel/kpttask.h b/src/libs/kernel/kpttask.h index 96ec0212..9fe924f9 100644 --- a/src/libs/kernel/kpttask.h +++ b/src/libs/kernel/kpttask.h @@ -1,773 +1,773 @@ /* This file is part of the KDE project Copyright (C) 2001 Thomas Zander zander@kde.org Copyright (C) 2004 - 2007 Dag Andersen Copyright (C) 2007 Florian Piquemal Copyright (C) 2007 Alexis Ménard This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef KPTTASK_H #define KPTTASK_H #include "plankernel_export.h" #include "kptnode.h" #include "kptglobal.h" #include "kptdatetime.h" #include "kptduration.h" #include "kptresource.h" #include #include #include /// The main namespace. namespace KPlato { class Completion; class XmlSaveContext; /** * The Completion class holds information about the tasks progress. */ class PLANKERNEL_EXPORT Completion { public: class PLANKERNEL_EXPORT UsedEffort { public: class PLANKERNEL_EXPORT ActualEffort : public std::pair { public: explicit ActualEffort(KPlato::Duration ne = Duration::zeroDuration, KPlato::Duration oe = Duration::zeroDuration) : std::pair(ne, oe) {} ActualEffort(const ActualEffort &e) : std::pair(e.first, e.second) {} ~ActualEffort() {} bool isNull() const { return (first + second) == Duration::zeroDuration; } Duration normalEffort() const { return first; } void setNormalEffort(KPlato::Duration e) { first = e; } Duration overtimeEffort() const { return second; } void setOvertimeEffort(KPlato::Duration e) { second = e; } /// Returns the sum of normalEffort + overtimeEffort Duration effort() const { return first + second; } void setEffort(KPlato::Duration ne, KPlato::Duration oe = Duration::zeroDuration) { first = ne; second = oe; } }; UsedEffort(); UsedEffort(const UsedEffort &e); ~UsedEffort(); bool operator==(const UsedEffort &e) const; bool operator!=(const UsedEffort &e) const { return !operator==(e); } void mergeEffort(const UsedEffort &value); void setEffort(QDate date, const ActualEffort &value); /// Returns the total effort up to @p date Duration effortTo(QDate date) const; /// Returns the total effort on @p date ActualEffort effort(QDate date) const { return m_actual.value(date); } ActualEffort takeEffort(QDate date) { return m_actual.take(date); } /// Returns the total effort for all registered dates Duration effort() const; QDate firstDate() const { return m_actual.firstKey(); } QDate lastDate() const { return m_actual.lastKey(); } QMap actualEffortMap() const { return m_actual; } /// Load from document bool loadXML(KoXmlElement &element, XMLLoaderObject &status); /// Save to document void saveXML(QDomElement &element) const; bool contains(QDate date) const { return m_actual.contains(date); } private: QMap m_actual; }; typedef QMap DateUsedEffortMap; class PLANKERNEL_EXPORT Entry { public: Entry() : percentFinished(0), remainingEffort(Duration::zeroDuration), totalPerformed(Duration::zeroDuration) {} Entry(int percent, Duration remaining, Duration performed) : percentFinished(percent), remainingEffort(remaining), totalPerformed(performed) {} Entry(const Entry &e) { copy(e); } bool operator==(const Entry &e) const { return percentFinished == e.percentFinished && remainingEffort == e.remainingEffort && totalPerformed == e.totalPerformed && note == e.note; } bool operator!=(const Entry &e) const { return ! operator==(e); } Entry &operator=(const Entry &e) { copy(e); return *this; } int percentFinished; Duration remainingEffort; Duration totalPerformed; QString note; protected: void copy(const Entry &e) { percentFinished = e.percentFinished; remainingEffort = e.remainingEffort; totalPerformed = e.totalPerformed; note = e.note; } }; typedef QMap EntryList; typedef QHash ResourceUsedEffortMap; explicit Completion(Node *node = 0); // review * or &, or at all? Completion(const Completion ©); virtual ~Completion(); bool operator==(const Completion &p); bool operator!=(Completion &p) { return !(*this == p); } Completion &operator=(const Completion &p); /// Load from document bool loadXML(KoXmlElement &element, XMLLoaderObject &status); /// Save to document void saveXML(QDomElement &element) const; bool startIsValid() const { return m_started && m_startTime.isValid(); } bool isStarted() const { return m_started; } void setStarted(bool on); bool finishIsValid() const { return m_finished && m_finishTime.isValid(); } bool isFinished() const { return m_finished; } void setFinished(bool on); DateTime startTime() const { return m_startTime; } void setStartTime(const DateTime &dt); DateTime finishTime() const { return m_finishTime; } void setFinishTime(const DateTime &dt); void setPercentFinished(QDate date, int value); void setRemainingEffort(QDate date, Duration value); void setActualEffort(QDate date, Duration value); /// Return a list of the resource that has done any work on this task QList resources() const { return m_usedEffort.keys(); } const EntryList &entries() const { return m_entries; } void addEntry(QDate date, Entry *entry); Entry *takeEntry(QDate date) { return m_entries.take(date); changed(); } Entry *entry(QDate date) const { return m_entries[ date ]; } /// Returns the date of the latest entry QDate entryDate() const; /// Returns the percentFinished of the latest entry int percentFinished() const; /// Returns the percentFinished on @p date int percentFinished(QDate date) const; /// Returns the estimated remaining effort Duration remainingEffort() const; /// Returns the estimated remaining effort on @p date Duration remainingEffort(QDate date) const; /// Returns the total actual effort Duration actualEffort() const; /// Returns the total actual effort on @p date Duration actualEffort(QDate date) const; /// Returns the total actual effort upto and including @p date Duration actualEffortTo(QDate date) const; /// Returns the actual effort for @p resource on @p date Duration actualEffort(const Resource *resource, QDate date) const; /// TODO QString note() const; /// TODO void setNote(const QString &str); /// Returns the total actual cost double actualCost() const; /// Returns the actual cost for @p resource double actualCost(const Resource *resource) const; /// Returns the actual cost on @p date double actualCost(QDate date) const; /// Returns the total actual cost for @p resource on @p date double actualCost(const Resource *resource, QDate date) const; /// Returns the total actual effort and cost upto and including @p date EffortCost actualCostTo(long int id, QDate date) const; /** * Returns a map of all actual effort and cost entered */ virtual EffortCostMap actualEffortCost(long id, EffortCostCalculationType type = ECCT_All) const; void addUsedEffort(const Resource *resource, UsedEffort *value = 0); UsedEffort *takeUsedEffort(const Resource *r) { return m_usedEffort.take(const_cast(r) ); changed(); } UsedEffort *usedEffort(const Resource *r) const { return m_usedEffort.value(const_cast(r) ); } const ResourceUsedEffortMap &usedEffortMap() const { return m_usedEffort; } void setActualEffort(Resource *resource, const QDate &date, const UsedEffort::ActualEffort &value); // FIXME name clash UsedEffort::ActualEffort getActualEffort(Resource *resource, const QDate &date) const; void changed(int property = -1); Node *node() const { return m_node; } void setNode(Node *node) { m_node = node; } enum Entrymode { FollowPlan, EnterCompleted, EnterEffortPerTask, EnterEffortPerResource }; void setEntrymode(Entrymode mode) { m_entrymode = mode; } Entrymode entrymode() const { return m_entrymode; } void setEntrymode(const QString &mode); QString entryModeToString() const; QStringList entrymodeList() const; EffortCostMap effortCostPrDay(QDate start, QDate end, long id = -1) const; /// Returns the actual effort and cost pr day used by @p resource EffortCostMap effortCostPrDay(const Resource *resource, QDate start, QDate end, long id = CURRENTSCHEDULE) const; protected: void copy(const Completion ©); double averageCostPrHour(QDate date, long id) const; std::pair actualStartEndDates() const; private: Node *m_node; bool m_started, m_finished; DateTime m_startTime, m_finishTime; EntryList m_entries; ResourceUsedEffortMap m_usedEffort; Entrymode m_entrymode; #ifndef NDEBUG public: void printDebug(const QByteArray &ident) const; #endif }; /** * The WorkPackage class controls work flow for a task */ class PLANKERNEL_EXPORT WorkPackage { public: /// @enum WPTransmitionStatus describes if this package was sent or received enum WPTransmitionStatus { TS_None, /// Not sent nor received TS_Send, /// Package was sent to resource TS_Receive, /// Package was received from resource TS_Rejected /// Received package was rejected by project manager }; explicit WorkPackage(Task *task = 0); explicit WorkPackage(const WorkPackage &wp); virtual ~WorkPackage(); Task *parentTask() const { return m_task; } void setParentTask(Task *task) { m_task = task; } /// Returns the transmission status of this package WPTransmitionStatus transmitionStatus() const { return m_transmitionStatus; } void setTransmitionStatus(WPTransmitionStatus sts) { m_transmitionStatus = sts; } static QString transmitionStatusToString(WPTransmitionStatus sts, bool trans = false); static WPTransmitionStatus transmitionStatusFromString(const QString &sts); /// Load from document virtual bool loadXML(KoXmlElement &element, XMLLoaderObject &status); /// Save the full workpackage virtual void saveXML(QDomElement &element) const; /// Load from document virtual bool loadLoggedXML(KoXmlElement &element, XMLLoaderObject &status); /// Save the full workpackage virtual void saveLoggedXML(QDomElement &element) const; /// Set schedule manager void setScheduleManager(ScheduleManager *sm); /// Return schedule manager ScheduleManager *scheduleManager() const { return m_manager; } /// Return the schedule id, or NOTSCHEDULED if no schedule manager is set long id() const { return m_manager ? m_manager->scheduleId() : NOTSCHEDULED; } Completion &completion(); const Completion &completion() const; void addLogEntry(DateTime &dt, const QString &str); QMap log() const; QStringList log(); /// Return a list of resources fetched from the appointments or requests /// merged with resources added to completion QList fetchResources(); /// Return a list of resources fetched from the appointments or requests /// merged with resources added to completion QList fetchResources(long id); /// Returns id of the resource that owns this package. If empty, task leader owns it. QString ownerId() const { return m_ownerId; } /// Set the resource that owns this package to @p owner. If empty, task leader owns it. void setOwnerId(const QString &id) { m_ownerId = id; } /// Returns the name of the resource that owns this package. QString ownerName() const { return m_ownerName; } /// Set the name of the resource that owns this package. void setOwnerName(const QString &name) { m_ownerName = name; } DateTime transmitionTime() const { return m_transmitionTime; } void setTransmitionTime(const DateTime &dt) { m_transmitionTime = dt; } /// Clear workpackage data void clear(); private: Task *m_task; ScheduleManager *m_manager; Completion m_completion; QString m_ownerName; QString m_ownerId; WPTransmitionStatus m_transmitionStatus; DateTime m_transmitionTime; QMap m_log; }; class PLANKERNEL_EXPORT WorkPackageSettings { public: WorkPackageSettings(); bool loadXML(const KoXmlElement &element); void saveXML(QDomElement &element) const; bool operator==(WorkPackageSettings settings) const; bool operator!=(WorkPackageSettings settings) const; bool usedEffort; bool progress; bool documents; bool remainingEffort; }; /** * A task in the scheduling software is represented by this class. A task * can be anything from 'build house' to 'drill hole' It will always mean * an activity. */ class PLANKERNEL_EXPORT Task : public Node { Q_OBJECT public: explicit Task(Node *parent = 0); explicit Task(const Task &task, Node *parent = 0); ~Task() override; /// Return task type. Can be Type_Task, Type_Summarytask ot Type_Milestone. int type() const override; /** * Instead of using the expected duration, generate a random value using * the Distribution of each Task. This can be used for Monte-Carlo * estimation of Project duration. */ Duration *getRandomDuration() override; /** * Return the resource request made to group * (There should be only one) */ ResourceGroupRequest *resourceGroupRequest(const ResourceGroup *group) const override; - void clearResourceRequests(); +// void clearResourceRequests(); void addRequest(ResourceGroup *group, int numResources); void addRequest(ResourceGroupRequest *request); void takeRequest(ResourceGroupRequest *request); void makeAppointments() override; QStringList requestNameList() const override; virtual QList requestedResources() const; bool containsRequest(const QString &/*identity*/) const override; ResourceRequest *resourceRequest(const QString &/*name*/) const override; /// Return the list of resources assigned to this task QStringList assignedNameList(long id = CURRENTSCHEDULE) const override; /** * Calculates if the assigned resource is overbooked * within the duration of this task */ void calcResourceOverbooked() override; /// Load from document bool load(KoXmlElement &element, XMLLoaderObject &status) override; /// Save to document void save(QDomElement &element, const XmlSaveContext &context) const override; /// Save appointments for schedule with id void saveAppointments(QDomElement &element, long id) const override; /// Save a workpackage document with schedule identity @p id void saveWorkPackageXML(QDomElement &element, long id) const override; /** * Returns a list of planned effort and cost for this task * for the interval start, end inclusive */ EffortCostMap plannedEffortCostPrDay(QDate start, QDate end, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All) const override; /** * Returns a list of planned effort and cost for the @p resource * for the interval @p start, @p end inclusive, useng schedule with identity @p id */ EffortCostMap plannedEffortCostPrDay(const Resource *resource, QDate start, QDate end, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All) const override; /// Returns the total planned effort for @p resource on this task (or subtasks) Duration plannedEffort(const Resource *resource, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All) const override; /// Returns the total planned effort for this task (or subtasks) Duration plannedEffort(long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All) const override; /// Returns the total planned effort for this task (or subtasks) on date Duration plannedEffort(QDate date, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All) const override; /// Returns the total planned effort for @p resource on this task (or subtasks) on date Duration plannedEffort(const Resource *resource, QDate date, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All) const override; /// Returns the planned effort up to and including date Duration plannedEffortTo(QDate date, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All) const override; /// Returns the planned effort for @p resource up to and including date Duration plannedEffortTo(const Resource *resource, QDate date, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All) const override; /// Returns the total actual effort for this task (or subtasks) Duration actualEffort() const override; /// Returns the total actual effort for this task (or subtasks) on date Duration actualEffort(QDate date) const override; /// Returns the actual effort up to and including date Duration actualEffortTo(QDate date) const override; /** * Returns the total planned cost for this task (or subtasks) */ EffortCost plannedCost(long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All) const override; /// Planned cost up to and including date double plannedCostTo(QDate /*date*/, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All) const override; /// Returns actual effort and cost up to and including @p date EffortCost actualCostTo(long int id, QDate date) const override; /** * Returns a list of actual effort and cost for this task * for the interval start, end inclusive */ EffortCostMap actualEffortCostPrDay(QDate start, QDate end, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All) const override; /// Returns the actual effort and cost pr day used by @p resource EffortCostMap actualEffortCostPrDay(const Resource *resource, QDate start, QDate end, long id = CURRENTSCHEDULE, EffortCostCalculationType = ECCT_All) const override; /// Returns the effort planned to be used to reach the actual percent finished Duration budgetedWorkPerformed(QDate date, long id = CURRENTSCHEDULE) const override; /// Returns the cost planned to be used to reach the actual percent finished double budgetedCostPerformed(QDate date, long id = CURRENTSCHEDULE) const override; using Node::bcwsPrDay; /// Return map of Budgeted Cost of Work Scheduled pr day EffortCostMap bcwsPrDay(long id = CURRENTSCHEDULE, EffortCostCalculationType type = ECCT_All) override; /// Budgeted Cost of Work Scheduled double bcws(QDate date, long id = CURRENTSCHEDULE) const override; using Node::bcwpPrDay; /// Return map of Budgeted Cost of Work Performed pr day (also includes bcwsPrDay) EffortCostMap bcwpPrDay(long id = CURRENTSCHEDULE, EffortCostCalculationType type = ECCT_All) override; /// Budgeted Cost of Work Performed double bcwp(long id = CURRENTSCHEDULE) const override; /// Budgeted Cost of Work Performed (up to @p date) double bcwp(QDate date, long id = CURRENTSCHEDULE) const override; using Node::acwp; /// Map of Actual Cost of Work Performed EffortCostMap acwp(long id = CURRENTSCHEDULE, EffortCostCalculationType type = ECCT_All) override; /// Actual Cost of Work Performed up to dat EffortCost acwp(QDate date, long id = CURRENTSCHEDULE) const override; /// Effort based performance index double effortPerformanceIndex(QDate date, long id = CURRENTSCHEDULE) const override; /// Schedule performance index double schedulePerformanceIndex(QDate date, long id = CURRENTSCHEDULE) const override; /// Cost performance index double costPerformanceIndex(long int id, QDate date, bool *error=0) const override; /** * Return the duration that an activity's start can be delayed * without affecting the project completion date. * An activity with positive float is not on the critical path. * @param id Schedule identity. If id is CURRENTSCHEDULE, use current schedule. */ Duration positiveFloat(long id = CURRENTSCHEDULE) const; void setPositiveFloat(Duration fl, long id = CURRENTSCHEDULE) const; /** * Return the duration by which the duration of an activity or path * has to be reduced in order to fulfill a timing- or dependency constraint. * @param id Schedule identity. If id is CURRENTSCHEDULE, use current schedule. */ Duration negativeFloat(long id = CURRENTSCHEDULE) const; void setNegativeFloat(Duration fl, long id = CURRENTSCHEDULE) const; /** * Return the duration by which an activity can be delayed or extended * without affecting the start of any succeeding activity. * @param id Schedule identity. If id is CURRENTSCHEDULE, use current schedule. */ Duration freeFloat(long id = CURRENTSCHEDULE) const; void setFreeFloat(Duration fl, long id = CURRENTSCHEDULE) const; /** * Return the duration from Early Start to Late Start. * @param id Schedule identity. If id is CURRENTSCHEDULE, use current schedule. */ Duration startFloat(long id = CURRENTSCHEDULE) const; /** * Return the duration from Early Finish to Late Finish. * @param id Schedule identity. If id is CURRENTSCHEDULE, use current schedule. */ Duration finishFloat(long id = CURRENTSCHEDULE) const; /** * A task is critical if positive float equals 0 * @param id Schedule identity. If id is CURRENTSCHEDULE, use current schedule. */ bool isCritical(long id = CURRENTSCHEDULE) const override; /** * Set current schedule to schedule with identity id, for me and my children. * @param id Schedule identity */ void setCurrentSchedule(long id) override; /** * The assigned resources can not fulfill the estimated effort. * @param id Schedule identity. If id is CURRENTSCHEDULE, use current schedule. */ bool effortMetError(long id = CURRENTSCHEDULE) const override; /// @return true if this task has been started bool isStarted() const; Completion &completion() { return m_workPackage.completion(); } const Completion &completion() const { return m_workPackage.completion(); } WorkPackage &workPackage() { return m_workPackage; } const WorkPackage &workPackage() const { return m_workPackage; } int workPackageLogCount() const { return m_packageLog.count(); } QList workPackageLog() const { return m_packageLog; } void addWorkPackage(WorkPackage *wp); void removeWorkPackage(WorkPackage *wp); WorkPackage *workPackageAt(int index) const; QString wpOwnerName() const; WorkPackage::WPTransmitionStatus wpTransmitionStatus() const; DateTime wpTransmitionTime() const; /** * Returns the state of the task * @param id The identity of the schedule used when calculating the state */ uint state(long id = CURRENTSCHEDULE) const override; /// Check if this node has any dependent child nodes bool isEndNode() const override; /// Check if this node has any dependent parent nodes bool isStartNode() const override; QList parentProxyRelations() const { return m_parentProxyRelations; } QList childProxyRelations() const { return m_childProxyRelations; } /** * Calculates and returns the duration of the node. * Uses the correct expected-, optimistic- or pessimistic effort * dependent on @p use. * @param time Where to start calculation. * @param use Calculate using expected-, optimistic- or pessimistic estimate. * @param backward If true, time specifies when the task should end. */ Duration duration(const DateTime &time, int use, bool backward) override; /** * Return the duration calculated on bases of the estimates calendar */ Duration length(const DateTime &time, Duration duration, bool backward); Duration length(const DateTime &time, Duration uration, Schedule *sch, bool backward); /// Copy info from parent schedule void copySchedule(); /// Copy intervals from parent schedule void copyAppointments(); /// Copy intervals from parent schedule in the range @p start, @p end void copyAppointments(const DateTime &start, const DateTime &end = DateTime()); Q_SIGNALS: void workPackageToBeAdded(KPlato::Node *node, int row); void workPackageAdded(KPlato::Node *node); void workPackageToBeRemoved(KPlato::Node *node, int row); void workPackageRemoved(KPlato::Node *node); public: void initiateCalculation(MainSchedule &sch) override; /** * Sets up the lists used for calculation. * This includes adding summarytasks relations to subtasks * and lists for start- and endnodes. */ void initiateCalculationLists(MainSchedule &sch) override; /** * Calculates early start and early finish, first for all predeccessors, * then for this task. * @param use Calculate using expected-, optimistic- or pessimistic estimate. */ DateTime calculateForward(int use) override; /** * Calculates ref m_durationForward from ref earliestStart and * returns the resulting end time (early finish), * which will be used as the successors ref earliestStart. * * @param use Calculate using expected-, optimistic- or pessimistic estimate. */ DateTime calculateEarlyFinish(int use) override; /** * Calculates late start and late finish, first for all successors, * then for this task. * @param use Calculate using expected-, optimistic- or pessimistic estimate. */ DateTime calculateBackward(int use) override; /** * Calculates ref m_durationBackward from ref latestFinish and * returns the resulting start time (late start), * which will be used as the predecessors ref latestFinish. * * @param use Calculate using expected-, optimistic- or pessimistic estimate. */ DateTime calculateLateStart(int use) override; /** * Schedules the task within the limits of earliestStart and latestFinish. * Calculates ref m_startTime, ref m_endTime and ref m_duration, * Assumes ref calculateForward() and ref calculateBackward() has been run. * * @param earliest The task is not scheduled to start earlier than this * @param use Calculate using expected-, optimistic- or pessimistic estimate. * @return The tasks endtime which can be used for scheduling the successor. */ DateTime scheduleForward(const DateTime &earliest, int use) override; /** * Schedules the task within the limits of start time and latestFinish, * Calculates end time and duration. * Assumes ref calculateForward() and ref calculateBackward() has been run. * * @param use Calculate using expected-, optimistic- or pessimistic estimate. * @return The tasks endtime which can be used for scheduling the successor. */ DateTime scheduleFromStartTime(int use) override; /** * Schedules the task within the limits of earliestStart and latestFinish. * Calculates ref m_startTime, ref m_endTime and ref m_duration, * Assumes ref calculateForward() and ref calculateBackward() has been run. * * @param latest The task is not scheduled to end later than this * @param use Calculate using expected-, optimistic- or pessimistic estimate. * @return The tasks starttime which can be used for scheduling the predeccessor. */ DateTime scheduleBackward(const DateTime &latest, int use) override; /** * Schedules the task within the limits of end time and latestFinish. * Calculates endTime and duration. * Assumes ref calculateForward() and ref calculateBackward() has been run. * * @param use Calculate using expected-, optimistic- or pessimistic estimate. * @return The tasks starttime which can be used for scheduling the predeccessor. */ DateTime scheduleFromEndTime(int use) override; /** * Summarytasks (with milestones) need special treatment because * milestones are always 'glued' to their predecessors. */ void adjustSummarytask() override; /// Calculate the critical path bool calcCriticalPath(bool fromEnd) override; void calcFreeFloat() override; // Proxy relations are relations to/from summarytasks. // These relations are distributed to the child tasks before calculation. void clearProxyRelations() override; void addParentProxyRelations(const QList &) override; void addChildProxyRelations(const QList &) override; void addParentProxyRelation(Node *, const Relation *) override; void addChildProxyRelation(Node *, const Relation *) override; public: DateTime earlyStartDate(); void setEarlyStartDate(DateTime value); DateTime earlyFinishDate(); void setEarlyFinishDate(DateTime value); DateTime lateStartDate(); void setLateStartDate(DateTime value); DateTime lateFinishDate(); void setLateFinishDate(DateTime value); int activitySlack(); void setActivitySlack(int value); int activityFreeMargin(); void setActivityFreeMargin(int value); protected: /** * Return the duration calculated on bases of the requested resources */ Duration calcDuration(const DateTime &time, Duration effort, bool backward); private: DateTime calculateSuccessors(const QList &list, int use); DateTime calculatePredeccessors(const QList &list, int use); DateTime scheduleSuccessors(const QList &list, int use); DateTime schedulePredeccessors(const QList &list, int use); /// Fixed duration: Returns @p dt /// Duration with calendar: Returns first available after @p dt /// Has working resource(s) allocated: Returns the earliest time a resource can start work after @p dt, and checks appointments if @p sch is not null. DateTime workTimeAfter(const DateTime &dt, Schedule *sch = 0) const; /// Fixed duration: Returns @p dt /// Duration with calendar: Returns first available before @p dt /// Has working resource(s) allocated: Returns the latest time a resource can finish work, and checks appointments if @p sch is not null. DateTime workTimeBefore(const DateTime &dt, Schedule *sch = 0) const; private: QList m_resource; QList m_parentProxyRelations; QList m_childProxyRelations; // This list store pointers to linked task QList m_requiredTasks; WorkPackage m_workPackage; QList m_packageLog; bool m_calculateForwardRun; bool m_calculateBackwardRun; bool m_scheduleForwardRun; bool m_scheduleBackwardRun; }; } //KPlato namespace Q_DECLARE_METATYPE(KPlato::Completion::UsedEffort::ActualEffort) #ifndef QT_NO_DEBUG_STREAM PLANKERNEL_EXPORT QDebug operator<<(QDebug dbg, const KPlato::Completion::UsedEffort::ActualEffort &ae); #endif #endif diff --git a/src/libs/kernel/tests/AccountsTester.cpp b/src/libs/kernel/tests/AccountsTester.cpp index 1076494a..749ce1d3 100644 --- a/src/libs/kernel/tests/AccountsTester.cpp +++ b/src/libs/kernel/tests/AccountsTester.cpp @@ -1,624 +1,624 @@ /* This file is part of the KDE project Copyright (C) 2008 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 "AccountsTester.h" #include "kptaccount.h" #include "kptduration.h" #include "kptnode.h" #include "kpttask.h" #include "debug.cpp" #include namespace KPlato { void AccountsTester::init() { project = new Project(); project->setId(project->uniqueCalendarId()); project->registerNodeId(project); today = QDate::currentDate(); tomorrow = today.addDays(1); yesterday = today.addDays(-1); nextweek = today.addDays(7); t1 = QTime(9, 0, 0); t2 = QTime(17, 0, 0); length = t1.msecsTo(t2); t = project->createTask(); t->setName("T1"); project->addTask(t, project); t->estimate()->setUnit(Duration::Unit_d); t->estimate()->setExpectedEstimate(1.0); sm = project->createScheduleManager("Test Plan"); project->addScheduleManager(sm); // standard worktime defines 8 hour day as default Calendar *c = new Calendar(); c->setDefault(true); for (int i=1; i <= 7; ++i) { CalendarDay *d = c->weekday(i); d->setState(CalendarDay::Working); d->addInterval(t1, length); } project->addCalendar(c); ResourceGroup *g = new ResourceGroup(); project->addResourceGroup(g); r = new Resource(); r->setAvailableFrom(QDateTime(yesterday, QTime(), Qt::LocalTime)); r->setCalendar(c); r->setNormalRate(100.0); project->addResource(r); r->addParentGroup(g); ResourceGroupRequest *gr = new ResourceGroupRequest(g); t->addRequest(gr); ResourceRequest *rr = new ResourceRequest(r, 100); - gr->addResourceRequest(rr); + t->requests().addResourceRequest(rr, gr); t->estimate()->setType(Estimate::Type_Effort); //qDebug()<<"Calculate forward, Task: ASAP -----------------------------------"; project->setConstraintStartTime(DateTime(today, QTime())); project->setConstraintEndTime(DateTime(tomorrow, QTime())); sm->createSchedules(); project->calculate(*sm); QCOMPARE(t->earlyStart(), t->requests().workTimeAfter(project->startTime())); QVERIFY(t->lateStart() >= t->earlyStart()); QVERIFY(t->earlyFinish() <= t->endTime()); QVERIFY(t->lateFinish() >= t->endTime()); QCOMPARE(t->startTime(), DateTime(today, t1)); QCOMPARE(t->endTime(), t->startTime() + Duration(0, 8, 0)); //### QVERIFY(t->schedulingError() == false); sm->setBaselined(true); } void AccountsTester::cleanup() { delete project; project = 0; t = 0; r = 0; sm = 0; topaccount = 0; } void AccountsTester::defaultAccount() { Account *a = new Account("Default Account"); project->accounts().insert(a); project->accounts().setDefaultAccount(a); EffortCostMap ec = project->accounts().plannedCost(*a, t->startTime().date(), t->endTime().date()); qDebug()<startTime()<endTime()<accounts().actualCost(*a, t->startTime().date(), t->endTime().date()); qDebug()<startTime()<endTime()<completion().setEntrymode(Completion::FollowPlan); ec = project->accounts().actualCost(*a, t->startTime().date(), t->endTime().date()); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 8.0); QCOMPARE(ec.totalCost(), 800.0); t->completion().setEntrymode(Completion::EnterCompleted); ec = project->accounts().actualCost(*a, t->startTime().date(), t->endTime().date()); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 0.0); QCOMPARE(ec.totalCost(), 0.0); t->completion().setEntrymode(Completion::EnterCompleted); t->completion().setStarted(true); t->completion().setStartTime(DateTime(tomorrow, QTime())); t->completion().setPercentFinished(tomorrow, 50); ec = project->accounts().actualCost(*a, t->startTime().date(), tomorrow); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 0.0); QCOMPARE(ec.totalCost(), 0.0); t->completion().setEntrymode(Completion::EnterEffortPerTask); t->completion().setActualEffort(tomorrow, Duration(0, 4, 0)); ec = project->accounts().actualCost(*a, t->startTime().date(), tomorrow); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 4.0); QCOMPARE(ec.totalCost(), 400.0); t->completion().setEntrymode(Completion::EnterEffortPerResource); Completion::UsedEffort *ue = new Completion::UsedEffort(); Completion::UsedEffort::ActualEffort e(Duration(0, 6, 0)) ; ue->setEffort(tomorrow, e); t->completion().addUsedEffort(r, ue); ec = project->accounts().actualCost(*a, t->startTime().date(), tomorrow); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 6.0); QCOMPARE(ec.totalCost(), 600.0); } void AccountsTester::costPlaces() { EffortCostMap ec; Account *top = new Account("Top account"); project->accounts().insert(top); Account *a = new Account("Running account"); project->accounts().insert(a, top); a->addRunning(*t); ec = a->plannedCost(sm->scheduleId()); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 8.0); QCOMPARE(ec.totalCost(), 800.0); a = new Account("Startup account"); project->accounts().insert(a, top); a->addStartup(*t); t->setStartupCost(200.0); ec = a->plannedCost(sm->scheduleId()); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 0.0); QCOMPARE(ec.totalCost(), 200.0); a = new Account("Shutdown cost"); project->accounts().insert(a, top); a->addShutdown(*t); t->setShutdownCost(300.0); ec = a->plannedCost(sm->scheduleId()); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 0.0); QCOMPARE(ec.totalCost(), 300.0); ec = top->plannedCost(sm->scheduleId()); // Debug::print(top, sm->scheduleId(), "All planned cost in child accounts--------"); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 8.0); QCOMPARE(ec.totalCost(), 1300.0); a = new Account("All cost in one account"); project->accounts().insert(a); a->addRunning(*t); a->addStartup(*t); a->addShutdown(*t); ec = a->plannedCost(sm->scheduleId()); // Debug::print(a, sm->scheduleId(), "All planned cost in one account-----------"); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 8.0); QCOMPARE(ec.totalCost(), 1300.0); } void AccountsTester::startupDefault() { Account *a = new Account("Default Account"); project->accounts().insert(a); project->accounts().setDefaultAccount(a); EffortCostMap ec = project->accounts().plannedCost(*a, t->startTime().date(), t->endTime().date()); qDebug()<startTime()<endTime()<accounts().actualCost(*a, t->startTime().date(), t->endTime().date()); qDebug()<startTime()<endTime()<setStartupCost(25.0); ec = project->accounts().plannedCost(*a, t->startTime().date(), t->endTime().date()); qDebug()<startTime()<endTime()<completion().entryModeToString(); ec = project->accounts().actualCost(*a, t->startTime().date(), t->endTime().date()); qDebug()<startTime()<endTime()<completion().isStarted()); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 0.0); QCOMPARE(ec.totalCost(), 0.0); t->completion().setEntrymode(Completion::EnterCompleted); t->completion().setStarted(true); t->completion().setStartTime(DateTime(tomorrow, QTime())); t->completion().setPercentFinished(tomorrow, 50); QVERIFY(t->completion().isStarted()); qDebug()<completion().startTime()<<":"<startTime().date()<accounts().actualCost(*a, t->startTime().date(), tomorrow); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 0.0); QCOMPARE(ec.totalCost(), 25.0); ec = project->accounts().actualCost(*a, QDate(), QDate()); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 0.0); QCOMPARE(ec.totalCost(), 25.0); t->completion().setEntrymode(Completion::EnterEffortPerTask); t->completion().setActualEffort(tomorrow, Duration(0, 4, 0)); ec = project->accounts().actualCost(*a, t->startTime().date(), tomorrow); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 4.0); QCOMPARE(ec.totalCost(), 425.0); ec = project->accounts().actualCost(*a, QDate(), QDate()); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 4.0); QCOMPARE(ec.totalCost(), 425.0); } void AccountsTester::startupAccount() { Account *a = new Account("Account"); project->accounts().insert(a); a->addStartup(*t); // planned wo startup cost EffortCostMap ec = project->accounts().plannedCost(*a, t->startTime().date(), t->endTime().date()); qDebug()<startTime()<endTime()<accounts().actualCost(*a, t->startTime().date(), t->endTime().date()); qDebug()<startTime()<endTime()<setStartupCost(25.0); ec = project->accounts().plannedCost(*a, t->startTime().date(), t->endTime().date()); qDebug()<startTime()<endTime()<accounts().actualCost(*a, t->startTime().date(), t->endTime().date()); qDebug()<startTime()<endTime()<completion().isStarted()); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 0.0); QCOMPARE(ec.totalCost(), 0.0); // start task (tomorrow), no actual effort t->completion().setEntrymode(Completion::EnterCompleted); t->completion().setStarted(true); t->completion().setStartTime(DateTime(tomorrow, QTime())); QVERIFY(t->completion().isStarted()); ec = project->accounts().actualCost(*a, tomorrow, tomorrow); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 0.0); QCOMPARE(ec.totalCost(), 25.0); ec = project->accounts().actualCost(*a, QDate(), QDate()); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 0.0); QCOMPARE(ec.totalCost(), 25.0); // add actual effort, but no running cost t->completion().setEntrymode(Completion::EnterEffortPerTask); t->completion().setActualEffort(tomorrow, Duration(0, 4, 0)); ec = project->accounts().actualCost(*a, t->startTime().date(), tomorrow); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 0.0); QCOMPARE(ec.totalCost(), 25.0); ec = project->accounts().actualCost(*a, QDate(), QDate()); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 0.0); QCOMPARE(ec.totalCost(), 25.0); // add running account a->addRunning(*t); // planned wo startup cost t->completion().setStarted(false); t->setStartupCost(0.0); ec = project->accounts().plannedCost(*a, t->startTime().date(), t->endTime().date()); qDebug()<startTime()<endTime()<accounts().actualCost(*a, t->startTime().date(), t->endTime().date()); qDebug()<startTime()<endTime()<setStartupCost(25.0); ec = project->accounts().plannedCost(*a, t->startTime().date(), t->endTime().date()); qDebug()<startTime()<endTime()<accounts().actualCost(*a, t->startTime().date(), t->endTime().date()); qDebug()<startTime()<endTime()<completion().isStarted()); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 0.0); QCOMPARE(ec.totalCost(), 0.0); // start task (tomorrow), 4h actual effort from before t->completion().setEntrymode(Completion::EnterCompleted); t->completion().setStarted(true); t->completion().setStartTime(DateTime(tomorrow, QTime())); QVERIFY(t->completion().isStarted()); Debug::print(t, "Started tomorrow, 4h actual effort"); Debug::print(t->completion(), t->name()); ec = project->accounts().actualCost(*a, tomorrow, tomorrow); Debug::print(ec); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 4.0); QCOMPARE(ec.totalCost(), 425.0); ec = project->accounts().actualCost(*a, QDate(), QDate()); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 4.0); QCOMPARE(ec.totalCost(), 425.0); } void AccountsTester::shutdownAccount() { Account *a = new Account("Account"); project->accounts().insert(a); a->addShutdown(*t); t->completion().setStarted(true); t->completion().setStartTime(t->startTime()); // planned wo shutdown cost EffortCostMap ec = project->accounts().plannedCost(*a, t->startTime().date(), t->endTime().date()); qDebug()<startTime()<endTime()<accounts().actualCost(*a, t->startTime().date(), t->endTime().date()); qDebug()<startTime()<endTime()<setShutdownCost(25.0); ec = project->accounts().plannedCost(*a, t->startTime().date(), t->endTime().date()); qDebug()<startTime()<endTime()<accounts().actualCost(*a, t->startTime().date(), t->endTime().date()); qDebug()<startTime()<endTime()<completion().isFinished()); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 0.0); QCOMPARE(ec.totalCost(), 0.0); // finish task (tomorrow), no actual effort t->completion().setEntrymode(Completion::EnterCompleted); t->completion().setFinished(true); t->completion().setFinishTime(DateTime(tomorrow, QTime())); QVERIFY(t->completion().isFinished()); ec = project->accounts().actualCost(*a, tomorrow, tomorrow); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 0.0); QCOMPARE(ec.totalCost(), 25.0); ec = project->accounts().actualCost(*a, QDate(), QDate()); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 0.0); QCOMPARE(ec.totalCost(), 25.0); // add actual effort, no running account t->completion().setEntrymode(Completion::EnterEffortPerTask); t->completion().setActualEffort(tomorrow, Duration(0, 4, 0)); ec = project->accounts().actualCost(*a, t->startTime().date(), tomorrow); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 0.0); QCOMPARE(ec.totalCost(), 25.0); ec = project->accounts().actualCost(*a, QDate(), QDate()); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 0.0); QCOMPARE(ec.totalCost(), 25.0); // add running account a->addRunning(*t); t->completion().setFinished(false); t->setShutdownCost(0.0); // planned wo finish cost ec = project->accounts().plannedCost(*a, t->startTime().date(), t->endTime().date()); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 8.0); QCOMPARE(ec.totalCost(), 800.0); ec = project->accounts().actualCost(*a, t->startTime().date(), t->endTime().date()); Debug::print(t, "planned wo finish cost, with running", true); Debug::print(t->completion(), t->name()); Debug::print(ec); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 0.0); QCOMPARE(ec.totalCost(), 0.0); // planned with startup cost t->setShutdownCost(25.0); ec = project->accounts().plannedCost(*a, t->startTime().date(), t->endTime().date()); qDebug()<startTime()<endTime()<accounts().actualCost(*a, t->startTime().date(), t->endTime().date()); qDebug()<startTime()<endTime()<completion().isFinished()); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 0.0); QCOMPARE(ec.totalCost(), 0.0); // finish task (tomorrow), 4h actual effort from before t->completion().setEntrymode(Completion::EnterCompleted); t->completion().setFinished(true); t->completion().setFinishTime(DateTime(tomorrow, QTime())); QVERIFY(t->completion().isFinished()); ec = project->accounts().actualCost(*a, tomorrow, tomorrow); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 4.0); QCOMPARE(ec.totalCost(), 425.0); ec = project->accounts().actualCost(*a, QDate(), QDate()); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 4.0); QCOMPARE(ec.totalCost(), 425.0); } void AccountsTester::subaccounts() { Account *a1 = new Account("Account"); project->accounts().insert(a1); Account *a2 = new Account("Sub-Account"); project->accounts().insert(a2, a1); project->accounts().setDefaultAccount(a2); EffortCostMap ec = project->accounts().plannedCost(*a2); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 8.0); QCOMPARE(ec.totalCost(), 800.0); ec = project->accounts().plannedCost(*a1); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 8.0); QCOMPARE(ec.totalCost(), 800.0); ec = project->accounts().actualCost(*a2); qDebug()<startTime()<endTime()<accounts().actualCost(*a1); qDebug()<startTime()<endTime()<completion().setEntrymode(Completion::FollowPlan); ec = project->accounts().actualCost(*a2); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 8.0); QCOMPARE(ec.totalCost(), 800.0); ec = project->accounts().actualCost(*a1); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 8.0); QCOMPARE(ec.totalCost(), 800.0); t->completion().setEntrymode(Completion::EnterCompleted); ec = project->accounts().actualCost(*a2); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 0.0); QCOMPARE(ec.totalCost(), 0.0); ec = project->accounts().actualCost(*a1); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 0.0); QCOMPARE(ec.totalCost(), 0.0); t->completion().setEntrymode(Completion::EnterEffortPerTask); t->completion().setStarted(true); t->completion().setStartTime(DateTime(tomorrow, QTime())); t->completion().setPercentFinished(tomorrow, 50); t->completion().setActualEffort(tomorrow, Duration(0, 4, 0)); ec = project->accounts().actualCost(*a2); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 4.0); QCOMPARE(ec.totalCost(), 400.0); ec = project->accounts().actualCost(*a1); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 4.0); QCOMPARE(ec.totalCost(), 400.0); t->setStartupCost(25.0); a1->addStartup(*t); ec = project->accounts().actualCost(*a2); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 4.0); QCOMPARE(ec.totalCost(), 400.0); ec = project->accounts().actualCost(*a1); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 4.0); QCOMPARE(ec.totalCost(), 425.0); project->accounts().setDefaultAccount(a1); ec = project->accounts().plannedCost(*a2); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 0.0); QCOMPARE(ec.totalCost(), 0.0); ec = project->accounts().plannedCost(*a1); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 8.0); QCOMPARE(ec.totalCost(), 825.0); ec = project->accounts().actualCost(*a2); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 0.0); QCOMPARE(ec.totalCost(), 0.0); ec = project->accounts().actualCost(*a1); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 4.0); QCOMPARE(ec.totalCost(), 425.0); t->setShutdownCost(1.0); a2->addShutdown(*t); ec = project->accounts().plannedCost(*a2); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 0.0); QCOMPARE(ec.totalCost(), 1.0); ec = project->accounts().plannedCost(*a1); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 8.0); QCOMPARE(ec.totalCost(), 826.0); t->completion().setFinished(true); t->completion().setFinishTime(DateTime(tomorrow, QTime())); ec = project->accounts().actualCost(*a2); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 0.0); QCOMPARE(ec.totalCost(), 1.0); ec = project->accounts().actualCost(*a1); QCOMPARE(ec.totalEffort().toDouble(Duration::Unit_h), 4.0); QCOMPARE(ec.totalCost(), 426.0); } void AccountsTester::deleteAccount() { QVERIFY(project->accounts().allAccounts().isEmpty()); qInfo()<<"Add/delete one account"; Account *a1 = new Account("Account"); project->accounts().insert(a1); QCOMPARE(project->accounts().allAccounts().count(), 1); QCOMPARE(project->accounts().allAccounts().first(), a1); delete a1; QVERIFY(project->accounts().allAccounts().isEmpty()); qInfo()<<"Add/delete one account with one sub-account"; a1 = new Account("Account 1"); project->accounts().insert(a1); Account *a2 = new Account("Account 1.1"); project->accounts().insert(a2, a1); QCOMPARE(project->accounts().allAccounts().count(), 2); qInfo()<<"Delete top account"; delete a1; QVERIFY(project->accounts().allAccounts().isEmpty()); qInfo()<<"Add/delete one account with one sub-account"; a1 = new Account("Account 1"); project->accounts().insert(a1); a2 = new Account("Account 1.1"); project->accounts().insert(a2, a1); project->accounts().setDefaultAccount(a2); QCOMPARE(project->accounts().allAccounts().count(), 2); qInfo()<<"Delete sub account"; delete a2; QVERIFY(project->accounts().defaultAccount() == 0); QCOMPARE(project->accounts().allAccounts().count(), 1); qInfo()<<"Delete top account"; delete a1; QVERIFY(project->accounts().allAccounts().isEmpty()); } } //namespace KPlato QTEST_GUILESS_MAIN(KPlato::AccountsTester) diff --git a/src/libs/kernel/tests/PerformanceTester.cpp b/src/libs/kernel/tests/PerformanceTester.cpp index 08022939..4fcaf13a 100644 --- a/src/libs/kernel/tests/PerformanceTester.cpp +++ b/src/libs/kernel/tests/PerformanceTester.cpp @@ -1,1127 +1,1127 @@ /* This file is part of the KDE project Copyright (C) 2010 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 "PerformanceTester.h" #include "kpttask.h" #include "kptduration.h" #include "kpteffortcostmap.h" #include "kptcommand.h" #include "debug.cpp" namespace KPlato { void PerformanceTester::cleanup() { delete p1; p1 = 0; r1 = 0; r2 = 0; s1 = 0; t1 = 0; s2 = 0; m1 = 0; } void PerformanceTester::init() { p1 = new Project(); p1->setId(p1->uniqueNodeId()); p1->registerNodeId(p1); p1->setName("PerformanceTester"); p1->setConstraintStartTime(DateTime(QDateTime::fromString("2010-11-19T08:00:00", Qt::ISODate))); p1->setConstraintEndTime(DateTime(QDateTime::fromString("2010-11-29T16:00:00", Qt::ISODate))); Calendar *c = new Calendar(); c->setDefault(true); QTime ta(8, 0, 0); QTime tb (16, 0, 0); int length = ta.msecsTo(tb); for (int i=1; i <= 7; ++i) { CalendarDay *d = c->weekday(i); d->setState(CalendarDay::Working); d->addInterval(ta, length); } p1->addCalendar(c); s1 = p1->createTask(); s1->setName("S1"); p1->addTask(s1, p1); t1 = p1->createTask(); t1->setName("T1"); t1->estimate()->setUnit(Duration::Unit_d); t1->estimate()->setExpectedEstimate(5.0); t1->estimate()->setType(Estimate::Type_Effort); p1->addSubTask(t1, s1); s2 = p1->createTask(); s2->setName("S2"); p1->addTask(s2, p1); m1 = p1->createTask(); m1->estimate()->setExpectedEstimate(0); m1->setName("M1"); p1->addSubTask(m1, s2); ResourceGroup *g = new ResourceGroup(); g->setName("G1"); p1->addResourceGroup(g); r1 = new Resource(); r1->setName("R1"); r1->setNormalRate(1.0); p1->addResource(r1); r1->addParentGroup(g); ResourceGroupRequest *gr = new ResourceGroupRequest(g); t1->addRequest(gr); ResourceRequest *rr = new ResourceRequest(r1, 100); - gr->addResourceRequest(rr); + t1->requests().addResourceRequest(rr, gr); // material resource ResourceGroup *m = new ResourceGroup(); m->setName("M1"); m->setType(ResourceGroup::Type_Material); p1->addResourceGroup(m); r2 = new Resource(); r2->setName("Material"); r2->setType(Resource::Type_Material); r2->setCalendar(c); // limit availability to working hours r2->setNormalRate(0.0); // NOTE p1->addResource(r2); r2->addParentGroup(m); r3 = new Resource(); r3->setName("Material 2"); r3->setType(Resource::Type_Material); r3->setNormalRate(6.0); p1->addResource(r3); r3->addParentGroup(m); gr = new ResourceGroupRequest(m); t1->addRequest(gr); rr = new ResourceRequest(r2, 100); - gr->addResourceRequest(rr); + t1->requests().addResourceRequest(rr, gr); ScheduleManager *sm = p1->createScheduleManager("S1"); p1->addScheduleManager(sm); sm->createSchedules(); p1->calculate(*sm); t1->completion().setStarted(true); t1->completion().setStartTime(t1->startTime()); Debug::print(p1, "Project data", true); QCOMPARE(t1->startTime(), p1->mustStartOn()); QCOMPARE(t1->endTime(), t1->startTime() + Duration(4, 8, 0)); } void PerformanceTester::bcwsPrDayTask() { QDate d = t1->startTime().date().addDays(-1); EffortCostMap ecm = t1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 0, 0)); QCOMPARE(ecm.costOnDate(d), 0.0); d = d.addDays(1); ecm = t1->bcwsPrDay(); qDebug()<bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 16, 0)); QCOMPARE(ecm.costOnDate(d), 8.0); d = d.addDays(1); ecm = t1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 16, 0)); QCOMPARE(ecm.costOnDate(d), 8.0); d = d.addDays(1); ecm = t1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 16, 0)); QCOMPARE(ecm.costOnDate(d), 8.0); d = d.addDays(1); ecm = t1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 16, 0)); QCOMPARE(ecm.costOnDate(d), 8.0); d = d.addDays(1); ecm = t1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 0, 0)); QCOMPARE(ecm.costOnDate(d), 0.0); // add startup cost t1->setStartupCost(0.5); QCOMPARE(t1->startupCost(), 0.5); d = t1->startTime().date(); ecm = t1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 16, 0)); QCOMPARE(ecm.costOnDate(d), 8.5); // add shutdown cost t1->setShutdownCost(0.5); QCOMPARE(t1->shutdownCost(), 0.5); d = t1->endTime().date(); ecm = t1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 16, 0)); QCOMPARE(ecm.costOnDate(d), 8.5); // check sub-task d = t1->startTime().date(); ecm = s1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 16, 0)); QCOMPARE(ecm.costOnDate(d), 8.5); d = s1->endTime().date(); ecm = s1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 16, 0)); QCOMPARE(ecm.costOnDate(d), 8.5); } void PerformanceTester::bcwpPrDayTask() { QDate d = t1->startTime().date(); EffortCostMap ecm = t1->bcwpPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 16, 0)); QCOMPARE(ecm.costOnDate(d), 8.0); QCOMPARE(ecm.bcwpEffortOnDate(d), 0.0); QCOMPARE(ecm.bcwpCostOnDate(d), 0.0); ModifyCompletionPercentFinishedCmd *cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 10); cmd->execute(); delete cmd; ecm = t1->bcwpPrDay(); QCOMPARE(ecm.bcwpEffortOnDate(d), 8.0); QCOMPARE(ecm.bcwpCostOnDate(d), 4.0); d = d.addDays(1); cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 20); cmd->execute(); delete cmd; ecm = t1->bcwpPrDay(); QCOMPARE(ecm.bcwpEffortOnDate(d), 16.0); QCOMPARE(ecm.bcwpCostOnDate(d), 8.0); d = d.addDays(1); cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 30); cmd->execute(); delete cmd; ecm = t1->bcwpPrDay(); QCOMPARE(ecm.bcwpEffortOnDate(d), 24.0); QCOMPARE(ecm.bcwpCostOnDate(d), 12.0); d = d.addDays(1); cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 40); cmd->execute(); delete cmd; ecm = t1->bcwpPrDay(); QCOMPARE(ecm.bcwpEffortOnDate(d), 32.0); QCOMPARE(ecm.bcwpCostOnDate(d), 16.0); d = d.addDays(1); cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 50); cmd->execute(); delete cmd; ecm = t1->bcwpPrDay(); QCOMPARE(ecm.bcwpEffortOnDate(d), 40.0); QCOMPARE(ecm.bcwpCostOnDate(d), 20.0); // modify last day cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 100); cmd->execute(); delete cmd; ecm = t1->bcwpPrDay(); QCOMPARE(ecm.bcwpEffortOnDate(d), 80.0); QCOMPARE(ecm.bcwpCostOnDate(d), 40.0); // re-check d = t1->startTime().date(); ecm = t1->bcwpPrDay(); QCOMPARE(ecm.bcwpEffortOnDate(d), 8.0); QCOMPARE(ecm.bcwpCostOnDate(d), 4.0); d = d.addDays(1); QCOMPARE(ecm.bcwpEffortOnDate(d), 16.0); QCOMPARE(ecm.bcwpCostOnDate(d), 8.0); d = d.addDays(1); QCOMPARE(ecm.bcwpEffortOnDate(d), 24.0); QCOMPARE(ecm.bcwpCostOnDate(d), 12.0); d = d.addDays(1); QCOMPARE(ecm.bcwpEffortOnDate(d), 32.0); QCOMPARE(ecm.bcwpCostOnDate(d), 16.0); d = d.addDays(1); QCOMPARE(ecm.bcwpEffortOnDate(d), 80.0); QCOMPARE(ecm.bcwpCostOnDate(d), 40.0); // add startup cost t1->completion().setStartTime(t1->startTime()); t1->setStartupCost(0.5); d = t1->startTime().date(); ecm = t1->bcwpPrDay(); QCOMPARE(ecm.bcwpEffortOnDate(d), 8.0); QCOMPARE(ecm.bcwpCostOnDate(d), 4.0 + 0.5); //10% progress // add shutdown cost t1->setShutdownCost(0.5); t1->completion().setFinished(true); t1->completion().setFinishTime(t1->endTime()); d = t1->endTime().date(); ecm = t1->bcwpPrDay(); Debug::print(t1->completion(), t1->name(), "BCWP Performance with shutdown cost"); QCOMPARE(ecm.bcwpEffortOnDate(d), 80.0); QCOMPARE(ecm.bcwpCostOnDate(d), 40.0 + 0.5 + 0.5); //100% progress // check sub-task d = s1->startTime().date(); ecm = s1->bcwpPrDay(); QCOMPARE(ecm.bcwpEffortOnDate(d), 8.0); QCOMPARE(ecm.bcwpCostOnDate(d), 4.0 + 0.5); //10% progress // add shutdown cost d = s1->endTime().date(); ecm = s1->bcwpPrDay(); QCOMPARE(ecm.bcwpEffortOnDate(d), 80.0); QCOMPARE(ecm.bcwpCostOnDate(d), 40.0 + 0.5 + 0.5); //100% progress } void PerformanceTester::acwpPrDayTask() { NamedCommand *cmd = new ModifyCompletionEntrymodeCmd(t1->completion(), Completion::EnterEffortPerResource); cmd->execute(); delete cmd; Completion::UsedEffort *ue = new Completion::UsedEffort(); t1->completion().addUsedEffort(r1, ue); QDate d = t1->startTime().date(); EffortCostMap ecb = t1->bcwpPrDay(); EffortCostMap eca = t1->acwp(); QCOMPARE(ecb.effortOnDate(d), Duration(0, 16, 0)); QCOMPARE(ecb.costOnDate(d), 8.0); QCOMPARE(ecb.bcwpEffortOnDate(d), 0.0); QCOMPARE(ecb.bcwpCostOnDate(d), 0.0); QCOMPARE(eca.hoursOnDate(d), 0.0); QCOMPARE(eca.costOnDate(d), 0.0); cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 10); cmd->execute(); delete cmd; cmd = new AddCompletionActualEffortCmd(t1, r1, d, Completion::UsedEffort::ActualEffort(Duration(0, 8, 0))); cmd->execute(); delete cmd; ecb = t1->bcwpPrDay(); eca = t1->acwp(); // Debug::print(t1->completion(), t1->name(), QString("ACWP on date: %1").arg(d.toString(Qt::ISODate))); // Debug::print(eca, "ACWP"); QCOMPARE(ecb.bcwpEffortOnDate(d), 8.0); QCOMPARE(ecb.bcwpCostOnDate(d), 4.0); QCOMPARE(eca.hoursOnDate(d), 8.0); QCOMPARE(eca.costOnDate(d), 8.0); d = d.addDays(1); cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 20); cmd->execute(); delete cmd; cmd = new AddCompletionActualEffortCmd(t1, r1, d, Completion::UsedEffort::ActualEffort(Duration(0, 6, 0))); cmd->execute(); delete cmd; ecb = t1->bcwpPrDay(); eca = t1->acwp(); QCOMPARE(ecb.bcwpEffortOnDate(d), 16.0); QCOMPARE(ecb.bcwpCostOnDate(d), 8.0); QCOMPARE(eca.hoursOnDate(d), 6.0); QCOMPARE(eca.costOnDate(d), 6.0); d = d.addDays(1); cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 30); cmd->execute(); delete cmd; cmd = new AddCompletionActualEffortCmd(t1, r1, d, Completion::UsedEffort::ActualEffort(Duration(0, 8, 0))); cmd->execute(); delete cmd; ecb = t1->bcwpPrDay(); eca = t1->acwp(); QCOMPARE(ecb.bcwpEffortOnDate(d), 24.0); QCOMPARE(ecb.bcwpCostOnDate(d), 12.0); QCOMPARE(eca.hoursOnDate(d), 8.0); QCOMPARE(eca.costOnDate(d), 8.0); d = d.addDays(1); cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 50); cmd->execute(); delete cmd; cmd = new AddCompletionActualEffortCmd(t1, r1, d, Completion::UsedEffort::ActualEffort(Duration(0, 8, 0))); cmd->execute(); delete cmd; ecb = t1->bcwpPrDay(); eca = t1->acwp(); QCOMPARE(ecb.bcwpEffortOnDate(d), 40.0); QCOMPARE(ecb.bcwpCostOnDate(d), 20.0); QCOMPARE(eca.hoursOnDate(d), 8.0); QCOMPARE(eca.costOnDate(d), 8.0); d = d.addDays(1); cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 80); cmd->execute(); delete cmd; cmd = new AddCompletionActualEffortCmd(t1, r1, d, Completion::UsedEffort::ActualEffort(Duration(0, 12, 0))); cmd->execute(); delete cmd; ecb = t1->bcwpPrDay(); eca = t1->acwp(); QCOMPARE(ecb.bcwpEffortOnDate(d), 64.0); QCOMPARE(ecb.bcwpCostOnDate(d), 32.0); QCOMPARE(eca.hoursOnDate(d), 12.0); QCOMPARE(eca.costOnDate(d), 12.0); // add startup cost t1->completion().setStartTime(t1->startTime()); t1->setStartupCost(0.5); d = t1->startTime().date(); eca = t1->acwp(); Debug::print(t1->completion(), t1->name(), "ACWP Performance with startup cost"); QCOMPARE(eca.hoursOnDate(d), 8.0); QCOMPARE(eca.costOnDate(d), 8.5); // add shutdown cost d = t1->endTime().date(); t1->setShutdownCost(0.25); cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 100); cmd->execute(); delete cmd; t1->completion().setFinished(true); t1->completion().setFinishTime(t1->endTime()); eca = t1->acwp(); Debug::print(t1->completion(), t1->name(), "ACWP Performance with shutdown cost"); QCOMPARE(eca.hoursOnDate(d), 12.0); QCOMPARE(eca.costOnDate(d), 12.25); // check sub-task d = s1->startTime().date(); eca = s1->acwp(); QCOMPARE(eca.hoursOnDate(d), 8.0); QCOMPARE(eca.costOnDate(d), 8.5); // add shutdown cost d = s1->endTime().date(); eca = s1->acwp(); QCOMPARE(eca.hoursOnDate(d), 12.0); QCOMPARE(eca.costOnDate(d), 12.25); } void PerformanceTester::bcwsMilestone() { QDate d = m1->startTime().date().addDays(-1); EffortCostMap ecm = m1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration::zeroDuration); QCOMPARE(ecm.costOnDate(d), 0.0); d = d.addDays(1); ecm = m1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration::zeroDuration); QCOMPARE(ecm.costOnDate(d), 0.0); // add startup cost m1->setStartupCost(0.5); QCOMPARE(m1->startupCost(), 0.5); d = m1->startTime().date(); ecm = m1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration::zeroDuration); QCOMPARE(ecm.costOnDate(d), 0.5); // add shutdown cost m1->setShutdownCost(0.25); QCOMPARE(m1->shutdownCost(), 0.25); d = m1->endTime().date(); ecm = m1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration::zeroDuration); QCOMPARE(ecm.costOnDate(d), 0.75); } void PerformanceTester::bcwpMilestone() { QDate d = m1->startTime().date(); EffortCostMap ecm = m1->bcwpPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration::zeroDuration); QCOMPARE(ecm.costOnDate(d), 0.0); QCOMPARE(ecm.bcwpEffortOnDate(d), 0.0); QCOMPARE(ecm.bcwpCostOnDate(d), 0.0); ModifyCompletionPercentFinishedCmd *cmd = new ModifyCompletionPercentFinishedCmd(m1->completion(), d, 100); cmd->execute(); delete cmd; ecm = m1->bcwpPrDay(); QCOMPARE(ecm.bcwpEffortOnDate(d), 0.0); QCOMPARE(ecm.bcwpCostOnDate(d), 0.0); // add startup cost d = m1->startTime().date(); m1->completion().setStarted(true); m1->completion().setStartTime(m1->startTime()); m1->setStartupCost(0.5); ecm = m1->bcwpPrDay(); QCOMPARE(ecm.bcwpEffortOnDate(d), 0.0); QCOMPARE(ecm.bcwpCostOnDate(d), 0.5); // add shutdown cost d = m1->endTime().date(); m1->setShutdownCost(0.25); m1->completion().setFinished(true); m1->completion().setFinishTime(m1->endTime()); ecm = m1->bcwpPrDay(); Debug::print(m1->completion(), m1->name(), "BCWP Milestone with shutdown cost"); Debug::print(ecm, "BCWP Milestone"); QCOMPARE(ecm.bcwpEffortOnDate(d), 0.0); QCOMPARE(ecm.bcwpCostOnDate(d), 0.75); // check sub-milestone d = s2->startTime().date(); QCOMPARE(ecm.bcwpEffortOnDate(d), 0.0); QCOMPARE(ecm.bcwpCostOnDate(d), 0.75); } void PerformanceTester::acwpMilestone() { QDate d = m1->startTime().date(); EffortCostMap eca = m1->acwp(); QCOMPARE(eca.hoursOnDate(d), 0.0); QCOMPARE(eca.costOnDate(d), 0.0); // add startup cost d = m1->startTime().date(); m1->completion().setStarted(true); m1->completion().setStartTime(m1->startTime()); m1->setStartupCost(0.5); eca = m1->acwp(); Debug::print(m1->completion(), m1->name(), "ACWP Milestone with startup cost"); QCOMPARE(eca.hoursOnDate(d), 0.0); QCOMPARE(eca.costOnDate(d), 0.5); // add shutdown cost d = m1->endTime().date(); m1->setShutdownCost(0.25); NamedCommand *cmd = new ModifyCompletionPercentFinishedCmd(m1->completion(), d, 100); cmd->execute(); delete cmd; m1->completion().setFinished(true); m1->completion().setFinishTime(m1->endTime()); eca = m1->acwp(); Debug::print(m1->completion(), m1->name(), "ACWP Milestone with shutdown cost"); QCOMPARE(eca.hoursOnDate(d), 0.0); QCOMPARE(eca.costOnDate(d), 0.75); // check sub-milestone d = s2->endTime().date(); eca = s2->acwp(); QCOMPARE(eca.hoursOnDate(d), 0.0); QCOMPARE(eca.costOnDate(d), 0.75); } void PerformanceTester::bcwsPrDayTaskMaterial() { r2->setNormalRate(0.1); QDate d = t1->startTime().date().addDays(-1); EffortCostMap ecm = t1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 0, 0)); QCOMPARE(ecm.costOnDate(d), 0.0); d = d.addDays(1); ecm = t1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 16, 0)); //material+work resource QCOMPARE(ecm.costOnDate(d), 8.8); d = d.addDays(1); ecm = t1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 16, 0)); QCOMPARE(ecm.costOnDate(d), 8.8); d = d.addDays(1); ecm = t1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 16, 0)); QCOMPARE(ecm.costOnDate(d), 8.8); d = d.addDays(1); ecm = t1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 16, 0)); QCOMPARE(ecm.costOnDate(d), 8.8); d = d.addDays(1); ecm = t1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 16, 0)); QCOMPARE(ecm.costOnDate(d), 8.8); d = d.addDays(1); ecm = t1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 0, 0)); QCOMPARE(ecm.costOnDate(d), 0.0); // add startup cost t1->setStartupCost(0.5); QCOMPARE(t1->startupCost(), 0.5); d = t1->startTime().date(); ecm = t1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 16, 0)); QCOMPARE(ecm.costOnDate(d), 8.0 + 0.5 + 0.8); // add shutdown cost t1->setShutdownCost(0.25); QCOMPARE(t1->shutdownCost(), 0.25); d = t1->endTime().date(); ecm = t1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 16, 0)); QCOMPARE(ecm.costOnDate(d), 8.0 + 0.25 + 0.8); // check sub-task d = s1->startTime().date(); ecm = s1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 16, 0)); QCOMPARE(ecm.costOnDate(d), 8.0 + 0.5 + 0.8); d = s1->endTime().date(); ecm = s1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 16, 0)); QCOMPARE(ecm.costOnDate(d), 8.0 + 0.25 + 0.8); } void PerformanceTester::bcwpPrDayTaskMaterial() { r2->setNormalRate(0.1); QDate d = t1->startTime().date(); EffortCostMap ecm = t1->bcwpPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 16, 0)); QCOMPARE(ecm.costOnDate(d), 8.0 + 0.8); QCOMPARE(ecm.bcwpEffortOnDate(d), 0.0); QCOMPARE(ecm.bcwpCostOnDate(d), 0.0); ModifyCompletionPercentFinishedCmd *cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 10); cmd->execute(); delete cmd; ecm = t1->bcwpPrDay(); QCOMPARE(ecm.bcwpEffortOnDate(d), 8.0); QCOMPARE(ecm.bcwpCostOnDate(d), 4.0 + 0.4); d = d.addDays(1); cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 20); cmd->execute(); delete cmd; ecm = t1->bcwpPrDay(); QCOMPARE(ecm.bcwpEffortOnDate(d), 16.0); QCOMPARE(ecm.bcwpCostOnDate(d), 8.0 + 0.8); d = d.addDays(1); cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 30); cmd->execute(); delete cmd; ecm = t1->bcwpPrDay(); QCOMPARE(ecm.bcwpEffortOnDate(d), 24.0); QCOMPARE(ecm.bcwpCostOnDate(d), 12.0 + 1.2); d = d.addDays(1); cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 40); cmd->execute(); delete cmd; ecm = t1->bcwpPrDay(); QCOMPARE(ecm.bcwpEffortOnDate(d), 32.0); QCOMPARE(ecm.bcwpCostOnDate(d), 16.0 + 1.6); d = d.addDays(1); cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 50); cmd->execute(); delete cmd; ecm = t1->bcwpPrDay(); QCOMPARE(ecm.bcwpEffortOnDate(d), 40.0); QCOMPARE(ecm.bcwpCostOnDate(d), 20.0 + 2.0); // modify last day cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 100); cmd->execute(); delete cmd; ecm = t1->bcwpPrDay(); QCOMPARE(ecm.bcwpEffortOnDate(d), 80.0); QCOMPARE(ecm.bcwpCostOnDate(d), 40.0 + 4.0); // add startup cost t1->completion().setStartTime(t1->startTime()); t1->setStartupCost(0.5); d = t1->startTime().date(); ecm = t1->bcwpPrDay(); QCOMPARE(ecm.bcwpEffortOnDate(d), 8.0); QCOMPARE(ecm.bcwpCostOnDate(d), 4.0 + 0.5 + 0.4); // 10% progress // add shutdown cost t1->setShutdownCost(0.25); t1->completion().setFinished(true); t1->completion().setFinishTime(t1->endTime()); d = t1->endTime().date(); ecm = t1->bcwpPrDay(); Debug::print(t1->completion(), t1->name(), "BCWP Material with shutdown cost"); QCOMPARE(ecm.bcwpEffortOnDate(d), 80.0); QCOMPARE(ecm.bcwpCostOnDate(d), 40.0 + 0.5 + 0.25 + 4.0); // 100% progress // check sub-task d = s1->startTime().date(); ecm = s1->bcwpPrDay(); QCOMPARE(ecm.bcwpEffortOnDate(d), 8.0); QCOMPARE(ecm.bcwpCostOnDate(d), 4.0 + 0.5 + 0.4); // 10% progress d = s1->endTime().date(); ecm = s1->bcwpPrDay(); QCOMPARE(ecm.bcwpEffortOnDate(d), 80.0); QCOMPARE(ecm.bcwpCostOnDate(d), 40.0 + 0.5 + 0.25 + 4.0); // 100% progress } void PerformanceTester::acwpPrDayTaskMaterial() { r2->setNormalRate(0.1); NamedCommand *cmd = new ModifyCompletionEntrymodeCmd(t1->completion(), Completion::EnterEffortPerResource); cmd->execute(); delete cmd; Completion::UsedEffort *ue = new Completion::UsedEffort(); t1->completion().addUsedEffort(r1, ue); QDate d = t1->startTime().date(); EffortCostMap ecb = t1->bcwpPrDay(); EffortCostMap eca = t1->acwp(); QCOMPARE(ecb.effortOnDate(d), Duration(0, 16, 0)); QCOMPARE(ecb.costOnDate(d), 8.0 + 0.8); QCOMPARE(ecb.bcwpEffortOnDate(d), 0.0); QCOMPARE(ecb.bcwpCostOnDate(d), 0.0); QCOMPARE(eca.hoursOnDate(d), 0.0); QCOMPARE(eca.costOnDate(d), 0.0); cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 10); cmd->execute(); delete cmd; cmd = new AddCompletionActualEffortCmd(t1, r1, d, Completion::UsedEffort::ActualEffort(Duration(0, 8, 0))); cmd->execute(); delete cmd; ecb = t1->bcwpPrDay(); eca = t1->acwp(); // Debug::print(t1->completion(), t1->name(), QString("ACWP on date: %1").arg(d.toString(Qt::ISODate))); // Debug::print(eca, "ACWP"); QCOMPARE(ecb.bcwpEffortOnDate(d), 8.0); QCOMPARE(ecb.bcwpCostOnDate(d), 4.0 + 0.4); QCOMPARE(eca.hoursOnDate(d), 8.0); QCOMPARE(eca.costOnDate(d), 8.0); d = d.addDays(1); cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 20); cmd->execute(); delete cmd; cmd = new AddCompletionActualEffortCmd(t1, r1, d, Completion::UsedEffort::ActualEffort(Duration(0, 6, 0))); cmd->execute(); delete cmd; ecb = t1->bcwpPrDay(); eca = t1->acwp(); QCOMPARE(ecb.bcwpEffortOnDate(d), 16.0); QCOMPARE(ecb.bcwpCostOnDate(d), 8.0 + 0.8); QCOMPARE(eca.hoursOnDate(d), 6.0); QCOMPARE(eca.costOnDate(d), 6.0); d = d.addDays(1); cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 30); cmd->execute(); delete cmd; cmd = new AddCompletionActualEffortCmd(t1, r1, d, Completion::UsedEffort::ActualEffort(Duration(0, 8, 0))); cmd->execute(); delete cmd; ecb = t1->bcwpPrDay(); eca = t1->acwp(); QCOMPARE(ecb.bcwpEffortOnDate(d), 24.0); QCOMPARE(ecb.bcwpCostOnDate(d), 12.0 + 1.2); QCOMPARE(eca.hoursOnDate(d), 8.0); QCOMPARE(eca.costOnDate(d), 8.0); d = d.addDays(1); cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 50); cmd->execute(); delete cmd; cmd = new AddCompletionActualEffortCmd(t1, r1, d, Completion::UsedEffort::ActualEffort(Duration(0, 8, 0))); cmd->execute(); delete cmd; ecb = t1->bcwpPrDay(); eca = t1->acwp(); QCOMPARE(ecb.bcwpEffortOnDate(d), 40.0); QCOMPARE(ecb.bcwpCostOnDate(d), 20.0 + 2.0); QCOMPARE(eca.hoursOnDate(d), 8.0); QCOMPARE(eca.costOnDate(d), 8.0); d = d.addDays(1); cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 80); cmd->execute(); delete cmd; cmd = new AddCompletionActualEffortCmd(t1, r1, d, Completion::UsedEffort::ActualEffort(Duration(0, 12, 0))); cmd->execute(); delete cmd; ecb = t1->bcwpPrDay(); eca = t1->acwp(); QCOMPARE(ecb.bcwpEffortOnDate(d), 64.0); QCOMPARE(ecb.bcwpCostOnDate(d), 32.0 + 3.2); QCOMPARE(eca.hoursOnDate(d), 12.0); QCOMPARE(eca.costOnDate(d), 12.0); // add startup cost t1->completion().setStartTime(t1->startTime()); t1->setStartupCost(0.5); d = t1->startTime().date(); eca = t1->acwp(); Debug::print(t1->completion(), t1->name(), "ACWP Performance with startup cost"); QCOMPARE(eca.hoursOnDate(d), 8.0); QCOMPARE(eca.costOnDate(d), 8.0 + 0.5); //NOTE: material not included // add shutdown cost d = t1->endTime().date(); t1->setShutdownCost(0.25); cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 100); cmd->execute(); delete cmd; t1->completion().setFinished(true); t1->completion().setFinishTime(t1->endTime()); eca = t1->acwp(); Debug::print(t1->completion(), t1->name(), "ACWP Performance with shutdown cost"); QCOMPARE(eca.hoursOnDate(d), 12.0); QCOMPARE(eca.costOnDate(d), 12.0 + 0.25); //NOTE: material not included // check sub-task d = s1->startTime().date(); eca = s1->acwp(); QCOMPARE(eca.hoursOnDate(d), 8.0); QCOMPARE(eca.costOnDate(d), 8.0 + 0.5); //NOTE: material not included // add shutdown cost d = s1->endTime().date(); eca = s1->acwp(); QCOMPARE(eca.hoursOnDate(d), 12.0); QCOMPARE(eca.costOnDate(d), 12.0 + 0.25); //NOTE: material not included } void PerformanceTester::bcwsPrDayProject() { QDate d = p1->startTime().date().addDays(-1); EffortCostMap ecm = p1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 0, 0)); QCOMPARE(ecm.costOnDate(d), 0.0); d = d.addDays(1); ecm = p1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 16, 0)); QCOMPARE(ecm.costOnDate(d), 8.0); d = d.addDays(1); ecm = p1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 16, 0)); QCOMPARE(ecm.costOnDate(d), 8.0); d = d.addDays(1); ecm = p1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 16, 0)); QCOMPARE(ecm.costOnDate(d), 8.0); d = d.addDays(1); ecm = p1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 16, 0)); QCOMPARE(ecm.costOnDate(d), 8.0); d = d.addDays(1); ecm = p1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 16, 0)); QCOMPARE(ecm.costOnDate(d), 8.0); d = d.addDays(1); ecm = p1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 0, 0)); QCOMPARE(ecm.costOnDate(d), 0.0); // add startup cost to task t1->setStartupCost(0.5); QCOMPARE(t1->startupCost(), 0.5); d = p1->startTime().date(); ecm = p1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 16, 0)); QCOMPARE(ecm.costOnDate(d), 8.0 + 0.5); // add shutdown cost to task t1->setShutdownCost(0.2); QCOMPARE(t1->shutdownCost(), 0.2); d = p1->endTime().date(); ecm = p1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 16, 0)); QCOMPARE(ecm.costOnDate(d), 8.0 + 0.2); // add startup cost to milestone m1->setStartupCost(0.4); QCOMPARE(m1->startupCost(), 0.4); d = p1->startTime().date(); ecm = p1->bcwsPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 16, 0)); QCOMPARE(ecm.costOnDate(d), 8.0 + 0.5 + 0.4); // add shutdown cost to milestone m1->setShutdownCost(0.3); QCOMPARE(m1->shutdownCost(), 0.3); d = p1->startTime().date(); ecm = p1->bcwsPrDay(); //Debug::print(p1, "BCWS Project", true); //Debug::print(ecm, "BCWS Project"); QCOMPARE(ecm.effortOnDate(d), Duration(0, 16, 0)); QCOMPARE(ecm.costOnDate(d), 8.0 + 0.5 + 0.4 + 0.3); } void PerformanceTester::bcwpPrDayProject() { QDate d = p1->startTime().date(); EffortCostMap ecm = p1->bcwpPrDay(); QCOMPARE(ecm.effortOnDate(d), Duration(0, 16, 0)); QCOMPARE(ecm.costOnDate(d), 8.0); QCOMPARE(ecm.bcwpEffortOnDate(d), 0.0); QCOMPARE(ecm.bcwpCostOnDate(d), 0.0); ModifyCompletionPercentFinishedCmd *cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 10); cmd->execute(); delete cmd; ecm = p1->bcwpPrDay(); QCOMPARE(ecm.bcwpEffortOnDate(d), 8.0); QCOMPARE(ecm.bcwpCostOnDate(d), 4.0); d = d.addDays(1); cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 20); cmd->execute(); delete cmd; ecm = p1->bcwpPrDay(); QCOMPARE(ecm.bcwpEffortOnDate(d), 16.0); QCOMPARE(ecm.bcwpCostOnDate(d), 8.0); d = d.addDays(1); cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 30); cmd->execute(); delete cmd; ecm = p1->bcwpPrDay(); QCOMPARE(ecm.bcwpEffortOnDate(d), 24.0); QCOMPARE(ecm.bcwpCostOnDate(d), 12.0); d = d.addDays(1); cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 40); cmd->execute(); delete cmd; ecm = p1->bcwpPrDay(); QCOMPARE(ecm.bcwpEffortOnDate(d), 32.0); QCOMPARE(ecm.bcwpCostOnDate(d), 16.0); d = d.addDays(1); cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 50); cmd->execute(); delete cmd; ecm = p1->bcwpPrDay(); QCOMPARE(ecm.bcwpEffortOnDate(d), 40.0); QCOMPARE(ecm.bcwpCostOnDate(d), 20.0); // modify last day cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 100); cmd->execute(); delete cmd; ecm = p1->bcwpPrDay(); QCOMPARE(ecm.bcwpEffortOnDate(d), 80.0); QCOMPARE(ecm.bcwpCostOnDate(d), 40.0); // re-check d = p1->startTime().date(); ecm = p1->bcwpPrDay(); QCOMPARE(ecm.bcwpEffortOnDate(d), 8.0); QCOMPARE(ecm.bcwpCostOnDate(d), 4.0); d = d.addDays(1); QCOMPARE(ecm.bcwpEffortOnDate(d), 16.0); QCOMPARE(ecm.bcwpCostOnDate(d), 8.0); d = d.addDays(1); QCOMPARE(ecm.bcwpEffortOnDate(d), 24.0); QCOMPARE(ecm.bcwpCostOnDate(d), 12.0); d = d.addDays(1); QCOMPARE(ecm.bcwpEffortOnDate(d), 32.0); QCOMPARE(ecm.bcwpCostOnDate(d), 16.0); d = d.addDays(1); QCOMPARE(ecm.bcwpEffortOnDate(d), 80.0); QCOMPARE(ecm.bcwpCostOnDate(d), 40.0); // add startup cost to task t1->completion().setStartTime(t1->startTime()); t1->setStartupCost(0.5); d = p1->startTime().date(); ecm = p1->bcwpPrDay(); QCOMPARE(ecm.bcwpEffortOnDate(d), 8.0); QCOMPARE(ecm.bcwpCostOnDate(d), 4.0 + 0.5); // 10% progress // add shutdown cost to task t1->setShutdownCost(0.25); t1->completion().setFinished(true); t1->completion().setFinishTime(t1->endTime()); d = p1->endTime().date(); ecm = p1->bcwpPrDay(); QCOMPARE(ecm.bcwpEffortOnDate(d), 80.0); QCOMPARE(ecm.bcwpCostOnDate(d), 40.0 + 0.5 + 0.25); // 100% progress // check with ECCT_EffortWork d = p1->startTime().date(); ecm = p1->bcwpPrDay(CURRENTSCHEDULE, ECCT_EffortWork); QCOMPARE(ecm.totalEffort(), Duration(40.0, Duration::Unit_h)); QCOMPARE(ecm.hoursOnDate(d), 8.0); // hours from r1 QCOMPARE(ecm.costOnDate(d), 8.0 + 0.5); // cost from r1 (1.0) + r2 (0.0) + startup cost (0.5) QCOMPARE(ecm.bcwpEffortOnDate(d), 4.0); // 10% progress QCOMPARE(ecm.bcwpCostOnDate(d), 4.0 + 0.5); // 10% progress d = p1->endTime().date(); ecm = p1->bcwpPrDay(CURRENTSCHEDULE, ECCT_EffortWork); QCOMPARE(ecm.bcwpEffortOnDate(d), 40.0); // hours from r1 QCOMPARE(ecm.bcwpCostOnDate(d), 40.0 + 0.5 + 0.25); // 100% progress // add a new task with a material resource Task *tt = p1->createTask(); tt->setName("TT"); p1->addTask(tt, p1); tt->estimate()->setUnit(Duration::Unit_d); tt->estimate()->setExpectedEstimate(5.0); tt->estimate()->setType(Estimate::Type_Duration); tt->estimate()->setCalendar(p1->calendarAt(0)); r3->setNormalRate(1.0); ResourceGroupRequest *gr = new ResourceGroupRequest(r3->parentGroups().first()); tt->addRequest(gr); ResourceRequest *rr = new ResourceRequest(r3, 100); - gr->addResourceRequest(rr); + tt->requests().addResourceRequest(rr, gr); ScheduleManager *sm = p1->createScheduleManager(""); p1->addScheduleManager(sm); sm->createSchedules(); p1->calculate(*sm); QString s = " Material resource, no progress "; Debug::print(tt, s, true); d = tt->endTime().date(); ecm = tt->bcwpPrDay(sm->scheduleId(), ECCT_EffortWork); Debug::print(ecm, "BCWP: " + tt->name() + s); QCOMPARE(ecm.hoursOnDate(d), 0.0); QCOMPARE(ecm.costOnDate(d), 16.0); QCOMPARE(ecm.bcwpEffortOnDate(d), 0.0); QCOMPARE(ecm.bcwpCostOnDate(d), 0.0); // 0% progress d = p1->endTime().date(); ecm = p1->bcwpPrDay(sm->scheduleId(), ECCT_EffortWork); Debug::print(p1, s, true); Debug::print(ecm, "BCWP Project: " + p1->name() + s); QCOMPARE(ecm.bcwpEffortOnDate(d), 40.0); // hours from r1 QCOMPARE(ecm.bcwpCostOnDate(d), 40.0 + 0.5 + 0.25); } void PerformanceTester::acwpPrDayProject() { NamedCommand *cmd = new ModifyCompletionEntrymodeCmd(t1->completion(), Completion::EnterEffortPerResource); cmd->execute(); delete cmd; Completion::UsedEffort *ue = new Completion::UsedEffort(); t1->completion().addUsedEffort(r1, ue); QDate d = p1->startTime().date(); EffortCostMap ecb = p1->bcwpPrDay(); EffortCostMap eca = p1->acwp(); QCOMPARE(ecb.effortOnDate(d), Duration(0, 16, 0)); QCOMPARE(ecb.costOnDate(d), 8.0); QCOMPARE(ecb.bcwpEffortOnDate(d), 0.0); QCOMPARE(ecb.bcwpCostOnDate(d), 0.0); QCOMPARE(eca.hoursOnDate(d), 0.0); QCOMPARE(eca.costOnDate(d), 0.0); cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 10); cmd->execute(); delete cmd; cmd = new AddCompletionActualEffortCmd(t1, r1, d, Completion::UsedEffort::ActualEffort(Duration(0, 8, 0))); cmd->execute(); delete cmd; ecb = p1->bcwpPrDay(); eca = p1->acwp(); // Debug::print(eca, "ACWP Project"); QCOMPARE(ecb.bcwpEffortOnDate(d), 8.0); QCOMPARE(ecb.bcwpCostOnDate(d), 4.0); QCOMPARE(eca.hoursOnDate(d), 8.0); QCOMPARE(eca.costOnDate(d), 8.0); d = d.addDays(1); cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 20); cmd->execute(); delete cmd; cmd = new AddCompletionActualEffortCmd(t1, r1, d, Completion::UsedEffort::ActualEffort(Duration(0, 6, 0))); cmd->execute(); delete cmd; ecb = p1->bcwpPrDay(); eca = p1->acwp(); QCOMPARE(ecb.bcwpEffortOnDate(d), 16.0); QCOMPARE(ecb.bcwpCostOnDate(d), 8.0); QCOMPARE(eca.hoursOnDate(d), 6.0); QCOMPARE(eca.costOnDate(d), 6.0); d = d.addDays(1); cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 30); cmd->execute(); delete cmd; cmd = new AddCompletionActualEffortCmd(t1, r1, d, Completion::UsedEffort::ActualEffort(Duration(0, 8, 0))); cmd->execute(); delete cmd; ecb = p1->bcwpPrDay(); eca = p1->acwp(); QCOMPARE(ecb.bcwpEffortOnDate(d), 24.0); QCOMPARE(ecb.bcwpCostOnDate(d), 12.0); QCOMPARE(eca.hoursOnDate(d), 8.0); QCOMPARE(eca.costOnDate(d), 8.0); d = d.addDays(1); cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 50); cmd->execute(); delete cmd; cmd = new AddCompletionActualEffortCmd(t1, r1, d, Completion::UsedEffort::ActualEffort(Duration(0, 8, 0))); cmd->execute(); delete cmd; ecb = p1->bcwpPrDay(); eca = p1->acwp(); QCOMPARE(ecb.bcwpEffortOnDate(d), 40.0); QCOMPARE(ecb.bcwpCostOnDate(d), 20.0); QCOMPARE(eca.hoursOnDate(d), 8.0); QCOMPARE(eca.costOnDate(d), 8.0); d = d.addDays(1); cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 80); cmd->execute(); delete cmd; cmd = new AddCompletionActualEffortCmd(t1, r1, d, Completion::UsedEffort::ActualEffort(Duration(0, 12, 0))); cmd->execute(); delete cmd; ecb = p1->bcwpPrDay(); eca = p1->acwp(); QCOMPARE(ecb.bcwpEffortOnDate(d), 64.0); QCOMPARE(ecb.bcwpCostOnDate(d), 32.0); QCOMPARE(eca.hoursOnDate(d), 12.0); QCOMPARE(eca.costOnDate(d), 12.0); // add startup cost t1->completion().setStartTime(t1->startTime()); t1->completion().setStarted(true); t1->setStartupCost(0.5); d = p1->startTime().date(); eca = p1->acwp(); QCOMPARE(eca.hoursOnDate(d), 8.0); QCOMPARE(eca.costOnDate(d), 8.0 + 0.5); // add shutdown cost d = t1->endTime().date(); t1->setShutdownCost(0.25); cmd = new ModifyCompletionPercentFinishedCmd(t1->completion(), d, 100); cmd->execute(); delete cmd; t1->completion().setFinished(true); t1->completion().setFinishTime(t1->endTime()); d = p1->endTime().date(); eca = p1->acwp(); QCOMPARE(eca.hoursOnDate(d), 12.0); QCOMPARE(eca.costOnDate(d), 12.25); } } //namespace KPlato QTEST_GUILESS_MAIN(KPlato::PerformanceTester) diff --git a/src/libs/kernel/tests/ProjectTester.cpp b/src/libs/kernel/tests/ProjectTester.cpp index ab561346..bd502c7e 100644 --- a/src/libs/kernel/tests/ProjectTester.cpp +++ b/src/libs/kernel/tests/ProjectTester.cpp @@ -1,3286 +1,3286 @@ /* This file is part of the KDE project Copyright (C) 2007 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 "ProjectTester.h" #include "kptcommand.h" #include "kptcalendar.h" #include "kptdatetime.h" #include "kptresource.h" #include "kptnode.h" #include "kpttask.h" #include "kptschedule.h" #include #include "debug.cpp" namespace KPlato { void ProjectTester::initTestCase() { tz = "TZ=Europe/Copenhagen"; putenv(tz.data()); m_project = new Project(); m_project->setId(m_project->uniqueNodeId()); m_project->registerNodeId(m_project); m_project->setConstraintStartTime(QDateTime::fromString("2012-02-01T00:00", Qt::ISODate)); m_project->setConstraintEndTime(QDateTime::fromString("2013-02-01T00:00", Qt::ISODate)); // standard worktime defines 8 hour day as default QVERIFY(m_project->standardWorktime()); QCOMPARE(m_project->standardWorktime()->day(), 8.0); m_calendar = new Calendar(); m_calendar->setName("C1"); m_calendar->setDefault(true); QTime t1(9, 0, 0); QTime t2 (17, 0, 0); int length = t1.msecsTo(t2); for (int i=1; i <= 7; ++i) { CalendarDay *d = m_calendar->weekday(i); d->setState(CalendarDay::Working); d->addInterval(t1, length); } m_project->addCalendar(m_calendar); m_task = 0; } void ProjectTester::cleanupTestCase() { unsetenv("TZ"); delete m_project; } void ProjectTester::testAddTask() { m_task = m_project->createTask(); QVERIFY(m_project->addTask(m_task, m_project)); QVERIFY(m_task->parentNode() == m_project); QCOMPARE(m_project->findNode(m_task->id()), m_task); m_project->takeTask(m_task); delete m_task; m_task = 0; } void ProjectTester::testTakeTask() { m_task = m_project->createTask(); m_project->addTask(m_task, m_project); QCOMPARE(m_project->findNode(m_task->id()), m_task); m_project->takeTask(m_task); QVERIFY(m_project->findNode(m_task->id()) == 0); delete (m_task); m_task = 0; } void ProjectTester::testTaskAddCmd() { m_task = m_project->createTask(); SubtaskAddCmd *cmd = new SubtaskAddCmd(m_project, m_task, m_project); cmd->execute(); QVERIFY(m_task->parentNode() == m_project); QCOMPARE(m_project->findNode(m_task->id()), m_task); cmd->unexecute(); QVERIFY(m_project->findNode(m_task->id()) == 0); delete cmd; m_task = 0; } void ProjectTester::testTaskDeleteCmd() { m_task = m_project->createTask(); QVERIFY(m_project->addTask(m_task, m_project)); QVERIFY(m_task->parentNode() == m_project); NodeDeleteCmd *cmd = new NodeDeleteCmd(m_task); cmd->execute(); QVERIFY(m_project->findNode(m_task->id()) == 0); cmd->unexecute(); QCOMPARE(m_project->findNode(m_task->id()), m_task); cmd->execute(); delete cmd; m_task = 0; } void ProjectTester::schedule() { QDate today = QDate::fromString("2012-02-01", Qt::ISODate); QDate tomorrow = today.addDays(1); QDate yesterday = today.addDays(-1); QDate nextweek = today.addDays(7); QTime t1(9, 0, 0); QTime t2 (17, 0, 0); int length = t1.msecsTo(t2); Task *t = m_project->createTask(); t->setName("T1"); m_project->addTask(t, m_project); t->estimate()->setUnit(Duration::Unit_d); t->estimate()->setExpectedEstimate(1.0); t->estimate()->setType(Estimate::Type_Duration); QString s = "Calculate forward, Task: Duration -----------------------------------"; qDebug()<name()<id()<findNode(t->id()); qDebug()<nodeDict(); Debug::print(m_project, s, true); ScheduleManager *sm = m_project->createScheduleManager("Test Plan"); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); QCOMPARE(t->startTime(), m_project->startTime()); QCOMPARE(t->endTime(), DateTime(t->startTime().addDays(1))); s = "Calculate forward, Task: Duration w calendar -------------------------------"; qDebug()<estimate()->setCalendar(m_calendar); sm->createSchedules(); m_project->calculate(*sm); QCOMPARE(t->startTime(), m_calendar->firstAvailableAfter(m_project->startTime(), m_project->endTime())); QCOMPARE(t->endTime(), DateTime(t->startTime().addMSecs(length))); ResourceGroup *g = new ResourceGroup(); g->setName("G1"); m_project->addResourceGroup(g); Resource *r = new Resource(); r->setName("R1"); r->setAvailableFrom(QDateTime(yesterday, QTime(), Qt::LocalTime)); r->setCalendar(m_calendar); m_project->addResource(r); r->addParentGroup(g); ResourceGroupRequest *gr = new ResourceGroupRequest(g); t->addRequest(gr); ResourceRequest *rr = new ResourceRequest(r, 100); - gr->addResourceRequest(rr); + t->requests().addResourceRequest(rr, gr); t->estimate()->setType(Estimate::Type_Effort); s = "Calculate forward, Task: ASAP -----------------------------------"; qDebug()<setConstraintStartTime(DateTime(today, QTime())); sm = m_project->createScheduleManager("Test Plan"); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); Debug::print(m_project, t, s); // Debug::printSchedulingLog(*sm, s); QCOMPARE(t->earlyStart(), t->requests().workTimeAfter(m_project->startTime())); QVERIFY(t->lateStart() >= t->earlyStart()); QVERIFY(t->earlyFinish() <= t->endTime()); QVERIFY(t->lateFinish() >= t->endTime()); QCOMPARE(t->startTime(), DateTime(today, t1)); QCOMPARE(t->endTime(), t->startTime() + Duration(0, 8, 0)); QCOMPARE(t->plannedEffort().toHours(), 8.0); QVERIFY(t->schedulingError() == false); s = "Calculate forward, Task: ASAP, Resource 50% available -----------------"; qDebug()<setUnits(50); sm = m_project->createScheduleManager("Test Plan"); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); Debug::print(m_project, t, s); // Debug::printSchedulingLog(*sm, s); QCOMPARE(t->earlyStart(), t->requests().workTimeAfter(m_project->startTime())); QVERIFY(t->lateStart() >= t->earlyStart()); QVERIFY(t->earlyFinish() <= t->endTime()); QVERIFY(t->lateFinish() >= t->endTime()); QCOMPARE(t->startTime(), DateTime(today, t1)); QCOMPARE(t->endTime(), t->startTime() + Duration(1, 8, 0)); QCOMPARE(t->plannedEffort().toHours(), 8.0); QVERIFY(t->schedulingError() == false); s = "Calculate forward, Task: ASAP, Resource 50% available, Request 50% load ---------"; qDebug()<setUnits(50); rr->setUnits(50); sm = m_project->createScheduleManager("Test Plan"); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); Debug::print(m_project, s, true); // Debug::printSchedulingLog(*sm, s); QCOMPARE(t->earlyStart(), t->requests().workTimeAfter(m_project->startTime())); QVERIFY(t->lateStart() >= t->earlyStart()); QVERIFY(t->earlyFinish() <= t->endTime()); QVERIFY(t->lateFinish() >= t->endTime()); QCOMPARE(t->startTime(), DateTime(today, t1)); QCOMPARE(t->endTime(), t->startTime() + Duration(3, 8, 0)); QCOMPARE(t->plannedEffort().toHours(), 8.0); QVERIFY(t->schedulingError() == false); s = "Calculate forward, Task: ASAP, Resource 200% available, Request 50% load ---------"; qDebug()<setUnits(200); rr->setUnits(50); sm = m_project->createScheduleManager("Test Plan"); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); Debug::print(m_project, s, true); // Debug::printSchedulingLog(*sm, s); QCOMPARE(t->earlyStart(), t->requests().workTimeAfter(m_project->startTime())); QVERIFY(t->lateStart() >= t->earlyStart()); QVERIFY(t->earlyFinish() <= t->endTime()); QVERIFY(t->lateFinish() >= t->endTime()); QCOMPARE(t->startTime(), DateTime(today, t1)); QCOMPARE(t->endTime(), t->startTime() + Duration(0, 8, 0)); QCOMPARE(t->plannedEffort().toHours(), 8.0); QVERIFY(t->schedulingError() == false); s = "Calculate forward, Task: ASAP, Resource 200% available, Request 100% load ---------"; qDebug()<setUnits(200); rr->setUnits(100); sm = m_project->createScheduleManager("Test Plan"); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); Debug::print(m_project, s, true); // Debug::printSchedulingLog(*sm, s); QCOMPARE(t->earlyStart(), t->requests().workTimeAfter(m_project->startTime())); QVERIFY(t->lateStart() >= t->earlyStart()); QVERIFY(t->earlyFinish() <= t->endTime()); QVERIFY(t->lateFinish() >= t->endTime()); QCOMPARE(t->startTime(), DateTime(today, t1)); QCOMPARE(t->endTime(), t->startTime() + Duration(0, 4, 0)); QCOMPARE(t->plannedEffort().toHours(), 8.0); QVERIFY(t->schedulingError() == false); s = "Calculate forward, 2 tasks: Resource 200% available, Request 50% load each ---------"; qDebug()<setUnits(200); rr->setUnits(50); Task *task2 = m_project->createTask(*t); task2->setName("T2"); m_project->addTask(task2, t); ResourceGroupRequest *gr2 = new ResourceGroupRequest(g); task2->addRequest(gr2); ResourceRequest *rr2 = new ResourceRequest(r, 50); - gr2->addResourceRequest(rr2); + task2->requests().addResourceRequest(rr2, gr2); sm = m_project->createScheduleManager("Test Plan"); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); Debug::print(m_project, s, true); // Debug::printSchedulingLog(*sm, s); QCOMPARE(t->startTime(), DateTime(today, t1)); QCOMPARE(t->endTime(), t->startTime() + Duration(0, 8, 0)); QCOMPARE(t->plannedEffort().toHours(), 8.0); QCOMPARE(task2->startTime(), DateTime(today, t1)); QCOMPARE(task2->endTime(), task2->startTime() + Duration(0, 8, 0)); QCOMPARE(task2->plannedEffort().toHours(), 8.0); QVERIFY(task2->schedulingError() == false); m_project->takeTask(task2); delete task2; s = "Calculate forward, Task: ASAP, Resource available tomorrow --------"; qDebug()<setAvailableFrom(QDateTime(tomorrow, QTime(), Qt::LocalTime)); qDebug()<<"Tomorrow:"<availableFrom(); r->setUnits(100); rr->setUnits(100); sm = m_project->createScheduleManager("Test Plan"); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); // Debug::print(m_project, t, s); // Debug::printSchedulingLog(*sm, s); QCOMPARE(t->earlyStart(), t->requests().workTimeAfter(m_project->startTime())); QVERIFY(t->lateStart() >= t->earlyStart()); QVERIFY(t->earlyFinish() <= t->endTime()); QVERIFY(t->lateFinish() >= t->endTime()); QCOMPARE(t->startTime(), DateTime(r->availableFrom().date(), t1)); QCOMPARE(t->endTime(), t->startTime() + Duration(0, 8, 0)); QVERIFY(t->schedulingError() == false); s = "Calculate forward, Task: ALAP -----------------------------------"; qDebug()<setConstraintStartTime(DateTime(today, QTime(0,0,0))); t->setConstraint(Node::ALAP); r->setAvailableFrom(QDateTime(yesterday, QTime(), Qt::LocalTime)); sm = m_project->createScheduleManager("Test Plan"); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); Debug::print(m_project, s, true); // Debug::printSchedulingLog(*sm, s); QCOMPARE(t->earlyStart(), t->requests().workTimeAfter(m_project->startTime())); QVERIFY(t->lateStart() >= t->earlyStart()); QVERIFY(t->earlyFinish() <= t->endTime()); QVERIFY(t->lateFinish() >= t->endTime()); QCOMPARE(t->startTime(), DateTime(today, t1)); QCOMPARE(t->endTime(), t->startTime() + Duration(0, 8, 0)); QVERIFY(t->schedulingError() == false); s = "Calculate forward, Task: MustStartOn -----------------------------------"; qDebug()<setAvailableFrom(QDateTime(yesterday, QTime(), Qt::LocalTime)); t->setConstraint(Node::MustStartOn); t->setConstraintStartTime(DateTime(nextweek, t1)); sm = m_project->createScheduleManager("Test Plan"); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); QCOMPARE(t->startTime(), DateTime(t->constraintStartTime().date(), t1)); QCOMPARE(t->endTime(), t->startTime() + Duration(0, 8, 0)); QVERIFY(t->schedulingError() == false); // Calculate backwards s = "Calculate backward, Task: MustStartOn -----------------------------------"; qDebug()<setConstraintEndTime(DateTime(nextweek.addDays(1), QTime())); sm = m_project->createScheduleManager("Test Plan"); sm->setSchedulingDirection(true); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); Debug::print(m_project, t, s); // Debug::printSchedulingLog(*sm, s); QCOMPARE(t->startTime(), DateTime(t->constraintStartTime().date(), t1)); QCOMPARE(t->endTime(), t->startTime() + Duration(0, 8, 0)); QVERIFY(t->schedulingError() == false); // Calculate bacwords s = "Calculate backwards, Task: MustFinishOn -----------------------------------"; qDebug()<setConstraintStartTime(DateTime(today, QTime(0,0,0))); m_project->setConstraintEndTime(DateTime(nextweek.addDays(1), QTime())); t->setConstraint(Node::MustFinishOn); t->setConstraintEndTime(DateTime(nextweek.addDays(-2), t2)); sm = m_project->createScheduleManager("Test Plan"); sm->setSchedulingDirection(true); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); QCOMPARE(t->endTime(), t->constraintEndTime()); QCOMPARE(t->startTime(), t->endTime() - Duration(0, 8, 0)); QVERIFY(t->schedulingError() == false); // Calculate forward s = "Calculate forwards, Task: MustFinishOn -----------------------------------"; qDebug()<setConstraintStartTime(DateTime(today, QTime())); t->setConstraint(Node::MustFinishOn); t->setConstraintEndTime(DateTime(tomorrow, t2)); sm = m_project->createScheduleManager("Test Plan"); sm->setSchedulingDirection(false); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); Debug::print(m_project, t, s); // Debug::printSchedulingLog(*sm, s); QCOMPARE(t->endTime(), t->constraintEndTime()); QCOMPARE(t->startTime(), t->endTime() - Duration(0, 8, 0)); QVERIFY(t->schedulingError() == false); // Calculate forward s = "Calculate forwards, Task: StartNotEarlier -----------------------------------"; qDebug()<setConstraintStartTime(DateTime(today, QTime())); t->setConstraint(Node::StartNotEarlier); t->setConstraintStartTime(DateTime(today, t2)); sm = m_project->createScheduleManager("Test Plan"); sm->setSchedulingDirection(false); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); Debug::print(m_project, s, true); // Debug::printSchedulingLog(*sm, s); QCOMPARE(t->startTime(), DateTime(tomorrow, t1)); QCOMPARE(t->endTime(), t->startTime() + Duration(0, 8, 0)); QVERIFY(t->schedulingError() == false); // Calculate backward s = "Calculate backwards, Task: StartNotEarlier -----------------------------------"; qDebug()<setConstraintEndTime(DateTime(nextweek, QTime())); t->setConstraint(Node::StartNotEarlier); t->setConstraintStartTime(DateTime(today, t2)); sm = m_project->createScheduleManager("Test Plan"); sm->setSchedulingDirection(true); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); Debug::print(m_project, t, s); Debug::print(m_project->resourceList().first(), s); // Debug::printSchedulingLog(*sm, s); QVERIFY(t->lateStart() >= t->constraintStartTime()); QCOMPARE(t->earlyFinish(), t->endTime()); QVERIFY(t->lateFinish() <= m_project->constraintEndTime()); QVERIFY(t->endTime() <= t->lateFinish()); QCOMPARE(t->startTime(), t->endTime() - Duration(0, 8, 0)); QVERIFY(t->schedulingError() == false); // Calculate forward s = "Calculate forwards, Task: FinishNotLater -----------------------------------"; qDebug()<setConstraintStartTime(DateTime(today, QTime())); t->setConstraint(Node::FinishNotLater); t->setConstraintEndTime(DateTime(tomorrow.addDays(1), t2)); sm = m_project->createScheduleManager("Test Plan"); sm->setSchedulingDirection(false); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); QCOMPARE(t->earlyStart(), m_project->constraintStartTime()); QVERIFY(t->startTime() >= t->earlyStart()); QVERIFY(t->startTime() <= t->lateStart()); QVERIFY(t->startTime() >= m_project->startTime()); QVERIFY(t->endTime() >= t->earlyFinish()); QVERIFY(t->endTime() <= t->lateFinish()); QVERIFY(t->endTime() <= m_project->endTime()); QVERIFY(t->earlyFinish() <= t->constraintEndTime()); QVERIFY(t->lateFinish() <= m_project->constraintEndTime()); QVERIFY(t->schedulingError() == false); // Calculate backward s = "Calculate backwards, Task: FinishNotLater -----------------------------------"; qDebug()<setConstraintStartTime(DateTime(nextweek, QTime())); t->setConstraint(Node::FinishNotLater); t->setConstraintEndTime(DateTime(tomorrow, t2)); sm = m_project->createScheduleManager("Test Plan"); sm->setSchedulingDirection(true); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); //Debug::print(m_project, t, s); QCOMPARE(t->earlyStart(), m_project->startTime()); QCOMPARE(t->lateStart(), t->startTime()); QCOMPARE(t->earlyFinish(), t->constraintEndTime()); QVERIFY(t->lateFinish() <= m_project->constraintEndTime()); QCOMPARE(t->startTime(), m_project->startTime()); QCOMPARE(t->endTime(), t->startTime() + Duration(0, 8, 0)); QVERIFY(t->schedulingError() == false); // Calculate forward s = "Calculate forward, Task: FixedInterval -----------------------------------"; qDebug()<setConstraintStartTime(DateTime(today, QTime())); t->setConstraint(Node::FixedInterval); t->setConstraintStartTime(DateTime(tomorrow, t1)); t->setConstraintEndTime(DateTime(tomorrow, t2)); sm = m_project->createScheduleManager("Test Plan"); sm->setSchedulingDirection(false); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); //Debug::print(m_project, t, s); QCOMPARE(t->earlyStart(), m_project->constraintStartTime()); QCOMPARE(t->lateStart(), t->constraintStartTime()); QCOMPARE(t->earlyFinish(), t->constraintEndTime()); QCOMPARE(t->lateFinish(), t->constraintEndTime()); QCOMPARE(t->startTime(), t->constraintStartTime()); QCOMPARE(t->endTime(), t->constraintEndTime()); QVERIFY(t->schedulingError() == false); // Calculate forward s = "Calculate forwards, Task: FixedInterval -----------------------------------"; qDebug()<setConstraintStartTime(DateTime(today, QTime())); t->setConstraint(Node::FixedInterval); t->setConstraintStartTime(DateTime(tomorrow, QTime())); // outside working hours t->setConstraintEndTime(DateTime(tomorrow, t2)); sm = m_project->createScheduleManager("Test Plan"); sm->setSchedulingDirection(false); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); QCOMPARE(t->earlyStart(), m_project->constraintStartTime()); QCOMPARE(t->lateStart(), t->constraintStartTime()); QCOMPARE(t->earlyFinish(), t->constraintEndTime()); QCOMPARE(t->lateFinish(), t->constraintEndTime()); QCOMPARE(t->startTime(), t->constraintStartTime()); QCOMPARE(t->endTime(), t->constraintEndTime()); QVERIFY(t->schedulingError() == false); // Calculate forward s = "Calculate forwards, Task: Milestone, ASAP-------------------------"; qDebug()<setConstraintStartTime(DateTime(today, QTime())); t->setConstraint(Node::ASAP); t->estimate()->clear(); sm = m_project->createScheduleManager("Test Plan"); sm->setSchedulingDirection(false); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); //Debug::print(m_project, t, s); QCOMPARE(t->earlyStart(), m_project->constraintStartTime()); QCOMPARE(t->lateStart(), t->earlyStart()); QCOMPARE(t->earlyFinish(), t->earlyStart()); QCOMPARE(t->lateFinish(), t->earlyFinish()); QCOMPARE(t->startTime(), t->earlyStart()); QCOMPARE(t->endTime(), t->startTime()); QVERIFY(t->schedulingError() == false); // Calculate backward s = "Calculate backwards, Task: Milestone, ASAP-------------------------"; qDebug()<setConstraintEndTime(DateTime(today, QTime())); t->setConstraint(Node::ASAP); t->estimate()->clear(); sm = m_project->createScheduleManager("Test Plan"); sm->setSchedulingDirection(true); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); QCOMPARE(t->earlyStart(), m_project->constraintStartTime()); QCOMPARE(t->lateStart(), t->earlyStart()); QCOMPARE(t->earlyFinish(), t->earlyStart()); QCOMPARE(t->lateFinish(), t->earlyFinish()); QCOMPARE(t->startTime(), t->earlyStart()); QCOMPARE(t->endTime(), t->startTime()); QVERIFY(t->schedulingError() == false); // Calculate forward s = "Calculate forwards, Task: Milestone, ALAP-------------------------"; qDebug()<setConstraintStartTime(DateTime(today, QTime())); t->setConstraint(Node::ALAP); t->estimate()->clear(); sm = m_project->createScheduleManager("Test Plan"); sm->setSchedulingDirection(false); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); QCOMPARE(t->earlyStart(), m_project->constraintStartTime()); QCOMPARE(t->lateStart(), t->earlyStart()); QCOMPARE(t->earlyFinish(), t->earlyStart()); QCOMPARE(t->lateFinish(), t->earlyFinish()); QCOMPARE(t->startTime(), t->earlyStart()); QCOMPARE(t->endTime(), t->startTime()); QVERIFY(t->schedulingError() == false); // Calculate backward s = "Calculate backwards, Task: Milestone, ALAP-------------------------"; qDebug()<setConstraintEndTime(DateTime(today, QTime())); t->setConstraint(Node::ALAP); t->estimate()->clear(); sm = m_project->createScheduleManager("Test Plan"); sm->setSchedulingDirection(true); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); QCOMPARE(t->earlyStart(), m_project->constraintStartTime()); QCOMPARE(t->lateStart(), t->earlyStart()); QCOMPARE(t->earlyFinish(), t->earlyStart()); QCOMPARE(t->lateFinish(), t->earlyFinish()); QCOMPARE(t->startTime(), t->earlyStart()); QCOMPARE(t->endTime(), t->startTime()); QVERIFY(t->schedulingError() == false); // Calculate forward s = "Calculate forwards, Task: Milestone, MustStartOn ------------------"; qDebug()<setConstraintStartTime(DateTime(today, QTime())); t->setConstraint(Node::MustStartOn); t->setConstraintStartTime(DateTime(tomorrow, t1)); sm = m_project->createScheduleManager("Test Plan"); sm->setSchedulingDirection(false); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); QCOMPARE(t->earlyStart(), m_project->constraintStartTime()); QCOMPARE(t->lateStart(), t->constraintStartTime()); QCOMPARE(t->earlyFinish(), t->lateStart()); QCOMPARE(t->lateFinish(), t->earlyFinish()); QCOMPARE(t->startTime(), t->constraintStartTime()); QCOMPARE(t->endTime(), t->startTime()); QVERIFY(t->schedulingError() == false); // Calculate backward s = "Calculate backwards, Task: Milestone, MustStartOn ------------------"; qDebug()<setConstraintEndTime(DateTime(tomorrow, QTime())); t->setConstraint(Node::MustStartOn); t->setConstraintStartTime(DateTime(today, t1)); sm = m_project->createScheduleManager("Test Plan"); sm->setSchedulingDirection(true); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); QCOMPARE(t->earlyStart(), t->constraintStartTime()); QCOMPARE(t->lateStart(), t->earlyStart()); QCOMPARE(t->earlyFinish(), t->earlyStart()); QCOMPARE(t->lateFinish(), m_project->constraintEndTime()); QCOMPARE(t->startTime(), t->constraintStartTime()); QCOMPARE(t->endTime(), t->startTime()); QVERIFY(t->schedulingError() == false); // Calculate forward s = "Calculate forwards, Task: Milestone, MustFinishOn ------------------"; qDebug()<setConstraintStartTime(DateTime(today, QTime())); t->setConstraint(Node::MustFinishOn); t->setConstraintEndTime(DateTime(tomorrow, t1)); sm = m_project->createScheduleManager("Test Plan"); sm->setSchedulingDirection(false); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); QCOMPARE(t->earlyStart(), m_project->constraintStartTime()); QCOMPARE(t->lateStart(), t->constraintEndTime()); QCOMPARE(t->earlyFinish(), t->lateStart()); QCOMPARE(t->lateFinish(), t->earlyFinish()); QCOMPARE(t->startTime(), t->constraintEndTime()); QCOMPARE(t->endTime(), t->startTime()); QVERIFY(t->schedulingError() == false); QCOMPARE(m_project->endTime(), t->endTime()); // Calculate backward s = "Calculate backwards, Task: Milestone, MustFinishOn ------------------"; qDebug()<setConstraintEndTime(DateTime(tomorrow, QTime())); t->setConstraint(Node::MustFinishOn); t->setConstraintEndTime(DateTime(today, t1)); sm = m_project->createScheduleManager("Test Plan"); sm->setSchedulingDirection(true); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); QCOMPARE(t->earlyStart(), t->constraintEndTime()); QCOMPARE(t->lateStart(), t->constraintEndTime()); QCOMPARE(t->earlyFinish(), t->lateStart()); QCOMPARE(t->lateFinish(), m_project->constraintEndTime()); QCOMPARE(t->startTime(), t->constraintEndTime()); QCOMPARE(t->endTime(), t->startTime()); QVERIFY(t->schedulingError() == false); QCOMPARE(m_project->startTime(), t->startTime()); // Calculate forward s = "Calculate forwards, Task: Milestone, StartNotEarlier ---------------"; qDebug()<setConstraintStartTime(DateTime(today, QTime())); t->setConstraint(Node::StartNotEarlier); t->setConstraintEndTime(DateTime(tomorrow, t1)); sm = m_project->createScheduleManager("Test Plan"); sm->setSchedulingDirection(false); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); QCOMPARE(t->earlyStart(), m_project->constraintStartTime()); QCOMPARE(t->lateStart(), t->constraintStartTime()); QCOMPARE(t->earlyFinish(), t->lateStart()); QCOMPARE(t->lateFinish(), t->earlyFinish()); QCOMPARE(t->startTime(), t->constraintStartTime()); QCOMPARE(t->endTime(), t->startTime()); QVERIFY(t->schedulingError() == false); QCOMPARE(m_project->endTime(), t->endTime()); // Calculate backward s = "Calculate backwards, Task: Milestone, StartNotEarlier ---------------"; qDebug()<setConstraintEndTime(DateTime(tomorrow, QTime())); t->setConstraint(Node::StartNotEarlier); t->setConstraintStartTime(DateTime(today, t1)); sm = m_project->createScheduleManager("Test Plan"); sm->setSchedulingDirection(true); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); Debug::print(m_project, s, true); QVERIFY(t->earlyStart() >= t->constraintStartTime()); QVERIFY(t->lateStart() >= t->earlyStart()); QVERIFY(t->earlyFinish() <= t->lateFinish()); QVERIFY(t->lateFinish() >= t->constraintStartTime()); QVERIFY(t->startTime() >= t->constraintStartTime()); QCOMPARE(t->endTime(), t->startTime()); QVERIFY(t->schedulingError() == false); QCOMPARE(m_project->startTime(), t->startTime()); // Calculate forward s = "Calculate forwards, Task: Milestone, FinishNotLater ---------------"; qDebug()<setConstraintStartTime(DateTime(today, QTime())); t->setConstraint(Node::FinishNotLater); t->setConstraintEndTime(DateTime(tomorrow, t1)); sm = m_project->createScheduleManager("Test Plan"); sm->setSchedulingDirection(false); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); QVERIFY(t->earlyStart() <= t->constraintEndTime()); QVERIFY(t->lateStart() <= t->constraintEndTime()); QVERIFY(t->earlyFinish() >= t->earlyStart()); QVERIFY(t->lateFinish() >= t->earlyFinish()); QVERIFY(t->startTime() <= t->constraintEndTime()); QCOMPARE(t->endTime(), t->startTime()); QVERIFY(t->schedulingError() == false); QCOMPARE(m_project->endTime(), t->endTime()); // Calculate backward s = "Calculate backwards, Task: Milestone, FinishNotLater ---------------"; qDebug()<setConstraintEndTime(DateTime(tomorrow, QTime())); t->setConstraint(Node::FinishNotLater); t->setConstraintEndTime(DateTime(today, t1)); sm = m_project->createScheduleManager("Test Plan"); sm->setSchedulingDirection(true); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); QCOMPARE(t->earlyStart(), t->constraintStartTime()); QCOMPARE(t->lateStart(), t->earlyStart()); QCOMPARE(t->earlyFinish(), t->lateStart()); QCOMPARE(t->lateFinish(), m_project->constraintEndTime()); QCOMPARE(t->startTime(), t->constraintEndTime()); QCOMPARE(t->endTime(), t->startTime()); QVERIFY(t->schedulingError() == false); QCOMPARE(m_project->startTime(), t->startTime()); // Calculate forward s = "Calculate forward, 2 Tasks, no overbooking ----------"; qDebug()<setConstraintStartTime(DateTime(today, QTime())); m_project->setConstraintEndTime(DateTime(today, QTime()).addDays(4)); t->setConstraint(Node::ASAP); t->estimate()->setUnit(Duration::Unit_d); t->estimate()->setExpectedEstimate(2.0); Task *tsk2 = m_project->createTask(*t); tsk2->setName("T2"); m_project->addTask(tsk2, m_project); gr = new ResourceGroupRequest(g); tsk2->addRequest(gr); rr = new ResourceRequest(r, 100); - gr->addResourceRequest(rr); + tsk2->requests().addResourceRequest(rr, gr); sm = m_project->createScheduleManager("Test Plan"); sm->setAllowOverbooking(false); sm->setSchedulingDirection(false); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); Debug::print(m_project, t, s); Debug::print(m_project, tsk2, s); // Debug::printSchedulingLog(*sm, s); QCOMPARE(t->earlyStart(), t->requests().workTimeAfter(m_project->constraintStartTime())); QCOMPARE(t->lateStart(), tsk2->startTime()); QCOMPARE(t->earlyFinish(), DateTime(tomorrow, t2)); QCOMPARE(t->lateFinish(), t->lateFinish()); QCOMPARE(t->startTime(), DateTime(today, t1)); QCOMPARE(t->endTime(), t->earlyFinish()); QVERIFY(t->schedulingError() == false); QCOMPARE(tsk2->earlyStart(), t->earlyStart()); QCOMPARE(tsk2->lateStart(), t->earlyFinish() + Duration(0, 16, 0)); QCOMPARE(tsk2->earlyFinish(), DateTime(tomorrow, t2)); QCOMPARE(tsk2->lateFinish(), t->lateFinish()); QCOMPARE(tsk2->startTime(), DateTime(tomorrow.addDays(1), t1)); QCOMPARE(tsk2->endTime(), tsk2->lateFinish()); QVERIFY(tsk2->schedulingError() == false); QCOMPARE(m_project->endTime(), tsk2->endTime()); // Calculate forward s = "Calculate forward, 2 Tasks, relation ---------------"; qDebug()<setConstraintStartTime(DateTime(today, QTime())); t->setConstraint(Node::ASAP); t->estimate()->setUnit(Duration::Unit_d); t->estimate()->setExpectedEstimate(2.0); Relation *rel = new Relation(t, tsk2); bool relationAdded = m_project->addRelation(rel); QVERIFY(relationAdded); sm = m_project->createScheduleManager("Test Plan"); sm->setAllowOverbooking(true); sm->setSchedulingDirection(false); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); // Debug::print(m_project, t, s); // Debug::print(m_project, tsk2, s); // Debug::printSchedulingLog(*sm, s); QCOMPARE(t->earlyStart(), t->requests().workTimeAfter(m_project->constraintStartTime())); QCOMPARE(t->lateStart(), DateTime(today, t1)); QCOMPARE(t->earlyFinish(), DateTime(tomorrow, t2)); QCOMPARE(t->lateFinish(), t->lateFinish()); QCOMPARE(t->startTime(), DateTime(today, t1)); QCOMPARE(t->endTime(), t->earlyFinish()); QVERIFY(t->schedulingError() == false); QCOMPARE(tsk2->earlyStart(), tsk2->requests().workTimeAfter(t->earlyFinish())); QCOMPARE(tsk2->lateStart(), DateTime(tomorrow.addDays(1), t1)); QCOMPARE(tsk2->earlyFinish(), DateTime(tomorrow.addDays(2), t2)); QCOMPARE(tsk2->lateFinish(), tsk2->earlyFinish()); QCOMPARE(tsk2->startTime(), DateTime(tomorrow.addDays(1), t1)); QCOMPARE(tsk2->endTime(), tsk2->earlyFinish()); QVERIFY(tsk2->schedulingError() == false); QCOMPARE(m_project->endTime(), tsk2->endTime()); } void ProjectTester::scheduleFullday() { QString s = "Full day, 1 resource works 24 hours a day -------------"; qDebug()<setConstraintStartTime(QDateTime::fromString("2011-09-01T00:00:00", Qt::ISODate)); m_project->setConstraintEndTime(QDateTime::fromString("2011-09-16T00:00:00", Qt::ISODate)); qDebug()<constraintStartTime()<constraintEndTime(); Calendar *c = new Calendar("Test"); QTime t1(0,0,0); int length = 24*60*60*1000; for (int i = 1; i <= 7; ++i) { CalendarDay *wd1 = c->weekday(i); wd1->setState(CalendarDay::Working); wd1->addInterval(TimeInterval(t1, length)); } m_project->addCalendar(c); ResourceGroup *g = new ResourceGroup(); g->setName("G1"); m_project->addResourceGroup(g); Resource *r = new Resource(); r->setName("R1"); r->setCalendar(c); r->setAvailableFrom(m_project->constraintStartTime()); r->setAvailableUntil(r->availableFrom().addDays(21)); m_project->addResource(r); r->addParentGroup(g); Task *t = m_project->createTask(); t->setName("T1"); t->setId(m_project->uniqueNodeId()); m_project->addTask(t, m_project); t->estimate()->setUnit(Duration::Unit_d); t->estimate()->setExpectedEstimate(3 * 14.0); t->estimate()->setType(Estimate::Type_Effort); ResourceGroupRequest *gr = new ResourceGroupRequest(g); - gr->addResourceRequest(new ResourceRequest(r, 100)); t->addRequest(gr); + t->requests().addResourceRequest(new ResourceRequest(r, 100), gr); ScheduleManager *sm = m_project->createScheduleManager("Test Plan"); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); // Debug::print(c, s); // Debug::print(m_project, t, s); // Debug::print(r, s, true); // Debug::printSchedulingLog(*sm, s); QCOMPARE(t->startTime(), m_project->startTime()); QCOMPARE(t->endTime(), DateTime(t->startTime().addDays(14))); s = "Full day, 8 hour shifts, 3 resources ---------------"; qDebug()<weekday(i); wd1->setState(CalendarDay::Working); wd1->addInterval(TimeInterval(QTime(6, 0, 0), 8*hour)); } m_project->addCalendar(c1); Calendar *c2 = new Calendar("Test 2"); for (int i = 1; i <= 7; ++i) { CalendarDay *wd1 = c2->weekday(i); wd1->setState(CalendarDay::Working); wd1->addInterval(TimeInterval(QTime(14, 0, 0), 8*hour)); } m_project->addCalendar(c2); Calendar *c3 = new Calendar("Test 3"); for (int i = 1; i <= 7; ++i) { CalendarDay *wd1 = c3->weekday(i); wd1->setState(CalendarDay::Working); wd1->addInterval(TimeInterval(QTime(0, 0, 0), 6*hour)); wd1->addInterval(TimeInterval(QTime(22, 0, 0), 2*hour)); } m_project->addCalendar(c3); r->setCalendar(c1); r = new Resource(); r->setName("R2"); r->setCalendar(c2); r->setAvailableFrom(m_project->constraintStartTime()); r->setAvailableUntil(r->availableFrom().addDays(21)); m_project->addResource(r); r->addParentGroup(g); - gr->addResourceRequest(new ResourceRequest(r, 100)); + t->requests().addResourceRequest(new ResourceRequest(r, 100), gr); r = new Resource(); r->setName("R3"); r->setCalendar(c3); r->setAvailableFrom(m_project->constraintStartTime()); r->setAvailableUntil(r->availableFrom().addDays(21)); m_project->addResource(r); r->addParentGroup(g); - gr->addResourceRequest(new ResourceRequest(r, 100)); + t->requests().addResourceRequest(new ResourceRequest(r, 100), gr); sm->createSchedules(); m_project->calculate(*sm); // Debug::print(m_project, t, s); // Debug::printSchedulingLog(*sm, s); QCOMPARE(t->startTime(), m_project->startTime()); QCOMPARE(t->endTime(), DateTime(t->startTime().addDays(14))); } void ProjectTester::scheduleFulldayDstSpring() { QString s = "Daylight saving time - Spring, 1 resource works 24 hours a day -------------"; qDebug()<weekday(i); wd1->setState(CalendarDay::Working); wd1->addInterval(TimeInterval(t1, length)); } project.addCalendar(c); ResourceGroup *g = new ResourceGroup(); g->setName("G1"); project.addResourceGroup(g); Resource *r = new Resource(); r->setName("R1"); r->setCalendar(c); r->setAvailableFrom(project.constraintStartTime().addDays(-1)); r->setAvailableUntil(r->availableFrom().addDays(21)); project.addResource(r); r->addParentGroup(g); Task *t = project.createTask(); t->setName("T1"); t->setId(project.uniqueNodeId()); project.addTask(t, &project); t->estimate()->setUnit(Duration::Unit_d); t->estimate()->setExpectedEstimate(3 * 4.0); t->estimate()->setType(Estimate::Type_Effort); ResourceGroupRequest *gr = new ResourceGroupRequest(g); - gr->addResourceRequest(new ResourceRequest(r, 100)); t->addRequest(gr); + t->requests().addResourceRequest(new ResourceRequest(r, 100), gr); ScheduleManager *sm = project.createScheduleManager("Test Plan"); project.addScheduleManager(sm); sm->createSchedules(); project.calculate(*sm); // Debug::print(c, s); Debug::print(&project, t, s); // Debug::print(r, s, true); // Debug::printSchedulingLog(*sm, s); QCOMPARE(t->startTime(), project.constraintStartTime()); QCOMPARE(t->endTime(), t->startTime() + Duration(4, 0, 0)); s = "Daylight saving time - Spring, Backward: 1 resource works 24 hours a day -------------"; qDebug()<setSchedulingDirection(true); sm->createSchedules(); project.calculate(*sm); // Debug::print(c, s); Debug::print(&project, t, s); // Debug::print(r, s, true); // Debug::printSchedulingLog(*sm, s); QCOMPARE(t->endTime(), project.constraintEndTime()); QCOMPARE(t->startTime(), t->endTime() - Duration(4, 0, 0)); s = "Daylight saving time - Spring, 8 hour shifts, 3 resources ---------------"; qDebug()<weekday(i); wd1->setState(CalendarDay::Working); wd1->addInterval(TimeInterval(QTime(6, 0, 0), 8*hour)); } project.addCalendar(c1); Calendar *c2 = new Calendar("Test 2"); for (int i = 1; i <= 7; ++i) { CalendarDay *wd1 = c2->weekday(i); wd1->setState(CalendarDay::Working); wd1->addInterval(TimeInterval(QTime(14, 0, 0), 8*hour)); } project.addCalendar(c2); Calendar *c3 = new Calendar("Test 3"); for (int i = 1; i <= 7; ++i) { CalendarDay *wd1 = c3->weekday(i); wd1->setState(CalendarDay::Working); wd1->addInterval(TimeInterval(QTime(0, 0, 0), 6*hour)); wd1->addInterval(TimeInterval(QTime(22, 0, 0), 2*hour)); } project.addCalendar(c3); r->setCalendar(c1); r = new Resource(); r->setName("R2"); r->setCalendar(c2); r->setAvailableFrom(project.constraintStartTime().addDays(-1)); r->setAvailableUntil(r->availableFrom().addDays(21)); project.addResource(r); r->addParentGroup(g); - gr->addResourceRequest(new ResourceRequest(r, 100)); + t->requests().addResourceRequest(new ResourceRequest(r, 100), gr); r = new Resource(); r->setName("R3"); r->setCalendar(c3); r->setAvailableFrom(project.constraintStartTime().addDays(-1)); r->setAvailableUntil(r->availableFrom().addDays(21)); project.addResource(r); r->addParentGroup(g); - gr->addResourceRequest(new ResourceRequest(r, 100)); + t->requests().addResourceRequest(new ResourceRequest(r, 100), gr); project.setConstraintStartTime(DateTime(QDate::fromString("2011-03-25", Qt::ISODate))); sm = project.createScheduleManager("Test Foreward"); project.addScheduleManager(sm); sm->createSchedules(); project.calculate(*sm); // Debug::print(&project, t, s); // Debug::printSchedulingLog(*sm, s); QCOMPARE(t->startTime().toUTC(), project.constraintStartTime().toUTC()); QCOMPARE(t->endTime(), t->startTime() + Duration(4, 0, 0)); s = "Daylight saving time - Spring, Backward: 8 hour shifts, 3 resources ---------------"; qDebug()<setSchedulingDirection(true); sm->createSchedules(); project.calculate(*sm); Debug::print(&project, t, s); // Debug::printSchedulingLog(*sm, s); QCOMPARE(t->endTime(), project.constraintEndTime()); QCOMPARE(t->startTime(), t->endTime() - Duration(4, 0, 0)); } void ProjectTester::scheduleFulldayDstFall() { QString s = "Daylight saving time - Fall, 1 resource works 24 hours a day -------------"; qDebug()<weekday(i); wd1->setState(CalendarDay::Working); wd1->addInterval(TimeInterval(t1, length)); } project.addCalendar(c); ResourceGroup *g = new ResourceGroup(); g->setName("G1"); project.addResourceGroup(g); Resource *r = new Resource(); r->setName("R1"); r->setCalendar(c); r->setAvailableFrom(project.constraintStartTime().addDays(-1)); r->setAvailableUntil(r->availableFrom().addDays(21)); project.addResource(r); r->addParentGroup(g); Task *t = project.createTask(); t->setName("T1"); t->setId(project.uniqueNodeId()); project.addTask(t, &project); t->estimate()->setUnit(Duration::Unit_d); t->estimate()->setExpectedEstimate(3 * 4.0); t->estimate()->setType(Estimate::Type_Effort); ResourceGroupRequest *gr = new ResourceGroupRequest(g); - gr->addResourceRequest(new ResourceRequest(r, 100)); t->addRequest(gr); + t->requests().addResourceRequest(new ResourceRequest(r, 100), gr); ScheduleManager *sm = project.createScheduleManager("Test Plan"); project.addScheduleManager(sm); sm->createSchedules(); project.calculate(*sm); // Debug::print(c, s); Debug::print(&project, t, s); // Debug::print(r, s, true); // Debug::printSchedulingLog(*sm, s); QCOMPARE(t->startTime(), project.constraintStartTime()); QCOMPARE(t->endTime(), t->startTime() + Duration(4, 0, 0)); s = "Daylight saving time - Fall, Backward: 1 resource works 24 hours a day -------------"; qDebug()<setSchedulingDirection(true); sm->createSchedules(); project.calculate(*sm); // Debug::print(c, s); Debug::print(&project, t, s); // Debug::print(r, s, true); // Debug::printSchedulingLog(*sm, s); QCOMPARE(t->endTime(), project.constraintEndTime()); QCOMPARE(t->startTime(), t->endTime() - Duration(4, 0, 0)); s = "Daylight saving time - Fall, 8 hour shifts, 3 resources ---------------"; qDebug()<weekday(i); wd1->setState(CalendarDay::Working); wd1->addInterval(TimeInterval(QTime(6, 0, 0), 8*hour)); } project.addCalendar(c1); Calendar *c2 = new Calendar("Test 2"); for (int i = 1; i <= 7; ++i) { CalendarDay *wd1 = c2->weekday(i); wd1->setState(CalendarDay::Working); wd1->addInterval(TimeInterval(QTime(14, 0, 0), 8*hour)); } project.addCalendar(c2); Calendar *c3 = new Calendar("Test 3"); for (int i = 1; i <= 7; ++i) { CalendarDay *wd1 = c3->weekday(i); wd1->setState(CalendarDay::Working); wd1->addInterval(TimeInterval(QTime(0, 0, 0), 6*hour)); wd1->addInterval(TimeInterval(QTime(22, 0, 0), 2*hour)); } project.addCalendar(c3); r->setCalendar(c1); r = new Resource(); r->setName("R2"); r->setCalendar(c2); r->setAvailableFrom(project.constraintStartTime().addDays(-1)); r->setAvailableUntil(r->availableFrom().addDays(21)); project.addResource(r); r->addParentGroup(g); - gr->addResourceRequest(new ResourceRequest(r, 100)); + t->requests().addResourceRequest(new ResourceRequest(r, 100), gr); r = new Resource(); r->setName("R3"); r->setCalendar(c3); r->setAvailableFrom(project.constraintStartTime().addDays(-1)); r->setAvailableUntil(r->availableFrom().addDays(21)); project.addResource(r); r->addParentGroup(g); - gr->addResourceRequest(new ResourceRequest(r, 100)); + t->requests().addResourceRequest(new ResourceRequest(r, 100), gr); sm = project.createScheduleManager("Test Foreward"); project.addScheduleManager(sm); sm->createSchedules(); project.calculate(*sm); // Debug::print(&project, t, s); // Debug::printSchedulingLog(*sm, s); QCOMPARE(t->startTime(), project.constraintStartTime()); QCOMPARE(t->endTime(), t->startTime() + Duration(4, 0, 0)); s = "Daylight saving time - Fall, Backward: 8 hour shifts, 3 resources ---------------"; qDebug()<setSchedulingDirection(true); sm->createSchedules(); project.calculate(*sm); QCOMPARE(t->endTime(), project.constraintEndTime()); QCOMPARE(t->startTime(), t->endTime() - Duration(4, 0, 0)); } void ProjectTester::scheduleWithExternalAppointments() { Project project; project.setName("P1"); project.setId(project.uniqueNodeId()); project.registerNodeId(&project); DateTime targetstart = DateTime(QDate::fromString("2012-02-01", Qt::ISODate), QTime(0,0,0)); DateTime targetend = DateTime(targetstart.addDays(3)); project.setConstraintStartTime(targetstart); project.setConstraintEndTime(targetend); Calendar c("Test"); QTime t1(0,0,0); int length = 24*60*60*1000; for (int i = 1; i <= 7; ++i) { CalendarDay *wd1 = c.weekday(i); wd1->setState(CalendarDay::Working); wd1->addInterval(TimeInterval(t1, length)); } ResourceGroup *g = new ResourceGroup(); g->setName("G1"); project.addResourceGroup(g); Resource *r = new Resource(); r->setName("R1"); r->setCalendar(&c); project.addResource(r); r->addParentGroup(g); r->addExternalAppointment("Ext-1", "External project 1", targetstart, targetstart.addDays(1), 100); r->addExternalAppointment("Ext-1", "External project 1", targetend.addDays(-1), targetend, 100); Task *t = project.createTask(); t->setName("T1"); project.addTask(t, &project); t->estimate()->setUnit(Duration::Unit_h); t->estimate()->setExpectedEstimate(8.0); t->estimate()->setType(Estimate::Type_Effort); ResourceGroupRequest *gr = new ResourceGroupRequest(g); - gr->addResourceRequest(new ResourceRequest(r, 100)); t->addRequest(gr); + t->requests().addResourceRequest(new ResourceRequest(r, 100), gr); ScheduleManager *sm = project.createScheduleManager("Test Plan"); project.addScheduleManager(sm); sm->createSchedules(); project.calculate(*sm); QString s = "Schedule with external appointments ----------"; qDebug()<startTime(), targetstart + Duration(1, 0, 0)); QCOMPARE(t->endTime(), t->startTime() + Duration(0, 8, 0)); sm->setAllowOverbooking(true); sm->createSchedules(); project.calculate(*sm); // Debug::printSchedulingLog(*sm); QCOMPARE(t->startTime(), targetstart); QCOMPARE(t->endTime(), t->startTime() + Duration(0, 8, 0)); sm->setAllowOverbooking(false); sm->setSchedulingDirection(true); // backwards sm->createSchedules(); project.calculate(*sm); Debug::print(&project, s, true); Debug::print(r, "", true); // Debug::printSchedulingLog(*sm, s); QCOMPARE(t->startTime(), targetend - Duration(1, 8, 0)); QCOMPARE(t->endTime(), t->startTime() + Duration(0, 8, 0)); sm->setAllowOverbooking(true); sm->createSchedules(); project.calculate(*sm); // Debug::printSchedulingLog(*sm); QCOMPARE(t->startTime(), targetend - Duration(0, 8, 0)); QCOMPARE(t->endTime(), targetend); sm->setAllowOverbooking(false); r->clearExternalAppointments(); sm->createSchedules(); project.calculate(*sm); // Debug::printSchedulingLog(*sm); QCOMPARE(t->endTime(), targetend); QCOMPARE(t->startTime(), t->endTime() - Duration(0, 8, 0)); } void ProjectTester::reschedule() { Project project; project.setName("P1"); project.setId(project.uniqueNodeId()); project.registerNodeId(&project); DateTime targetstart = DateTime(QDate::fromString("2012-02-01", Qt::ISODate), QTime(0,0,0)); DateTime targetend = DateTime(targetstart.addDays(7)); project.setConstraintStartTime(targetstart); project.setConstraintEndTime(targetend); Calendar *c = new Calendar("Test"); QTime t1(8,0,0); int length = 8*60*60*1000; // 8 hours for (int i = 1; i <= 7; ++i) { CalendarDay *wd1 = c->weekday(i); wd1->setState(CalendarDay::Working); wd1->addInterval(TimeInterval(t1, length)); } project.addCalendar(c); ResourceGroup *g = new ResourceGroup(); g->setName("G1"); project.addResourceGroup(g); Resource *r = new Resource(); r->setName("R1"); r->setCalendar(c); project.addResource(r); r->addParentGroup(g); QString s = "Re-schedule; schedule tasks T1, T2, T3 ---------------"; qDebug()<setName("T1"); project.addTask(task1, &project); task1->estimate()->setUnit(Duration::Unit_h); task1->estimate()->setExpectedEstimate(8.0); task1->estimate()->setType(Estimate::Type_Effort); ResourceGroupRequest *gr = new ResourceGroupRequest(g); - gr->addResourceRequest(new ResourceRequest(r, 100)); task1->addRequest(gr); + task1->requests().addResourceRequest(new ResourceRequest(r, 100), gr); Task *task2 = project.createTask(); task2->setName("T2"); project.addTask(task2, &project); task2->estimate()->setUnit(Duration::Unit_h); task2->estimate()->setExpectedEstimate(8.0); task2->estimate()->setType(Estimate::Type_Effort); gr = new ResourceGroupRequest(g); - gr->addResourceRequest(new ResourceRequest(r, 100)); task2->addRequest(gr); + task2->requests().addResourceRequest(new ResourceRequest(r, 100), gr); Task *task3 = project.createTask(); task3->setName("T3"); project.addTask(task3, &project); task3->estimate()->setUnit(Duration::Unit_h); task3->estimate()->setExpectedEstimate(8.0); task3->estimate()->setType(Estimate::Type_Effort); gr = new ResourceGroupRequest(g); - gr->addResourceRequest(new ResourceRequest(r, 100)); task3->addRequest(gr); + task3->requests().addResourceRequest(new ResourceRequest(r, 100), gr); Relation *rel = new Relation(task1, task2); project.addRelation(rel); rel = new Relation(task1, task3); project.addRelation(rel); ScheduleManager *sm = project.createScheduleManager("Plan"); project.addScheduleManager(sm); sm->createSchedules(); project.calculate(*sm); // Debug::print(&project, task1, s, true); // Debug::print(&project, task2, s, true); // Debug::print(&project, task3, s, true); // Debug::print(r, s); // Debug::printSchedulingLog(*sm); QVERIFY(task1->startTime() >= c->firstAvailableAfter(targetstart, targetend)); QVERIFY(task1->startTime() <= c->firstAvailableBefore(targetend, targetstart)); QCOMPARE(task1->endTime(), task1->startTime() + Duration(0, 8, 0)); QVERIFY(task2->startTime() >= c->firstAvailableAfter(targetstart, targetend)); QVERIFY(task2->startTime() <= c->firstAvailableBefore(targetend, targetstart)); QCOMPARE(task2->endTime(), task2->startTime() + Duration(0, 8, 0)); QVERIFY(task3->startTime() >= c->firstAvailableAfter(targetstart, targetend)); QVERIFY(task3->startTime() <= c->firstAvailableBefore(targetend, targetstart)); QCOMPARE(task3->endTime(), task3->startTime() + Duration(0, 8, 0)); DateTime restart = task1->endTime(); s = QString("Re-schedule; re-schedule from %1 - tasks T1 (finished), T2, T3 ------").arg(restart.toString()); qDebug()<completion().setStarted(true); task1->completion().setPercentFinished(task1->endTime().date(), 100); task1->completion().setFinished(true); ScheduleManager *child = project.createScheduleManager("Plan.1"); project.addScheduleManager(child, sm); child->setRecalculate(true); child->setRecalculateFrom(restart); child->createSchedules(); project.calculate(*child); // Debug::print(&project, task1, s, true); // Debug::print(&project, task2, s, true); // Debug::print(&project, task3, s, true); // Debug::printSchedulingLog(*child, s); QCOMPARE(task1->startTime(), c->firstAvailableAfter(targetstart, targetend)); QCOMPARE(task1->endTime(), task1->startTime() + Duration(0, 8, 0)); // either task2 or task3 may be scheduled first if (task2->startTime() < task3->startTime()) { QCOMPARE(task2->startTime(), c->firstAvailableAfter(qMax(task1->endTime(), restart), targetend)); QCOMPARE(task2->endTime(), task2->startTime() + Duration(0, 8, 0)); QCOMPARE(task3->startTime(), c->firstAvailableAfter(task2->endTime(), targetend)); QCOMPARE(task3->endTime(), task3->startTime() + Duration(0, 8, 0)); } else { QCOMPARE(task3->startTime(), c->firstAvailableAfter(qMax(task1->endTime(), restart), targetend)); QCOMPARE(task3->endTime(), task3->startTime() + Duration(0, 8, 0)); QCOMPARE(task2->startTime(), c->firstAvailableAfter(task3->endTime(), targetend)); QCOMPARE(task2->endTime(), task2->startTime() + Duration(0, 8, 0)); } } void ProjectTester::materialResource() { Project project; project.setName("P1"); project.setId(project.uniqueNodeId()); project.registerNodeId(&project); DateTime targetstart = DateTime(QDate::fromString("2012-02-01", Qt::ISODate), QTime(0,0,0)); DateTime targetend = DateTime(targetstart.addDays(7)); project.setConstraintStartTime(targetstart); project.setConstraintEndTime(targetend); Calendar *c = new Calendar("Test"); QTime t1(8,0,0); int length = 8*60*60*1000; // 8 hours for (int i = 1; i <= 7; ++i) { CalendarDay *wd1 = c->weekday(i); wd1->setState(CalendarDay::Working); wd1->addInterval(TimeInterval(t1, length)); } project.addCalendar(c); Task *task1 = project.createTask(); task1->setName("T1"); project.addTask(task1, &project); task1->estimate()->setUnit(Duration::Unit_h); task1->estimate()->setExpectedEstimate(8.0); task1->estimate()->setType(Estimate::Type_Effort); QString s = "Calculate forward, Task: ASAP, Working + material resource --------"; qDebug()<setName("Work"); r->setAvailableFrom(targetstart); r->setCalendar(c); project.addResource(r); r->addParentGroup(g); ResourceGroup *mg = new ResourceGroup(); mg->setType(ResourceGroup::Type_Material); project.addResourceGroup(mg); Resource *mr = new Resource(); mr->setType(Resource::Type_Material); mr->setName("Material"); mr->setAvailableFrom(targetstart); mr->setCalendar(c); project.addResource(mr); mr->addParentGroup(mg); ResourceGroupRequest *gr = new ResourceGroupRequest(g); task1->addRequest(gr); ResourceRequest *rr = new ResourceRequest(r, 100); - gr->addResourceRequest(rr); + task1->requests().addResourceRequest(rr, gr); ResourceGroupRequest *mgr = new ResourceGroupRequest(mg); task1->addRequest(mgr); ResourceRequest *mrr = new ResourceRequest(mr, 100); - mgr->addResourceRequest(mrr); + task1->requests().addResourceRequest(mrr, mgr); ScheduleManager *sm = project.createScheduleManager("Test Plan"); project.addScheduleManager(sm); sm->createSchedules(); project.calculate(*sm); // Debug::print(r, s); // Debug::print(mr, s); // Debug::print(&project, task1, s); // Debug::printSchedulingLog(*sm, s); QCOMPARE(task1->earlyStart(), task1->requests().workTimeAfter(targetstart)); QVERIFY(task1->lateStart() >= task1->earlyStart()); QVERIFY(task1->earlyFinish() <= task1->endTime()); QVERIFY(task1->lateFinish() >= task1->endTime()); QCOMPARE(task1->startTime(), DateTime(r->availableFrom().date(), t1)); QCOMPARE(task1->endTime(), task1->startTime() + Duration(0, 8, 0)); QVERIFY(task1->schedulingError() == false); } void ProjectTester::requiredResource() { Project project; project.setName("P1"); project.setId(project.uniqueNodeId()); project.registerNodeId(&project); DateTime targetstart = DateTime(QDate::fromString("2012-02-01", Qt::ISODate), QTime(0,0,0)); DateTime targetend = DateTime(targetstart.addDays(7)); project.setConstraintStartTime(targetstart); project.setConstraintEndTime(targetend); Calendar *c = new Calendar("Test"); QTime t1(8,0,0); int length = 8*60*60*1000; // 8 hours for (int i = 1; i <= 7; ++i) { CalendarDay *wd1 = c->weekday(i); wd1->setState(CalendarDay::Working); wd1->addInterval(TimeInterval(t1, length)); } project.addCalendar(c); Task *task1 = project.createTask(); task1->setName("T1"); project.addTask(task1, &project); task1->estimate()->setUnit(Duration::Unit_h); task1->estimate()->setExpectedEstimate(8.0); task1->estimate()->setType(Estimate::Type_Effort); QString s = "Required resource: Working + required material resource --------"; qDebug()<setName("Work"); r->setAvailableFrom(targetstart); r->setCalendar(c); project.addResource(r); r->addParentGroup(g); ResourceGroup *mg = new ResourceGroup(); mg->setType(ResourceGroup::Type_Material); mg->setName("MG"); project.addResourceGroup(mg); Resource *mr = new Resource(); mr->setType(Resource::Type_Material); mr->setName("Material"); mr->setAvailableFrom(targetstart); mr->setCalendar(c); project.addResource(mr); mr->addParentGroup(mg); ResourceGroupRequest *gr = new ResourceGroupRequest(g); task1->addRequest(gr); ResourceRequest *rr = new ResourceRequest(r, 100); - gr->addResourceRequest(rr); + task1->requests().addResourceRequest(rr, gr); QList lst; lst << mr; rr->setRequiredResources(lst); ScheduleManager *sm = project.createScheduleManager("Test Plan"); project.addScheduleManager(sm); sm->createSchedules(); project.calculate(*sm); // Debug::print(r, s); // Debug::print(mr, s); // Debug::print(&project, task1, s); // Debug::printSchedulingLog(*sm, s); QCOMPARE(task1->earlyStart(), task1->requests().workTimeAfter(targetstart)); QVERIFY(task1->lateStart() >= task1->earlyStart()); QVERIFY(task1->earlyFinish() <= task1->endTime()); QVERIFY(task1->lateFinish() >= task1->endTime()); QCOMPARE(task1->startTime(), DateTime(r->availableFrom().date(), t1)); QCOMPARE(task1->endTime(), task1->startTime() + Duration(0, 8, 0)); QVERIFY(task1->schedulingError() == false); QList apps = r->appointments(sm->scheduleId()); QVERIFY(apps.count() == 1); QCOMPARE(task1->startTime(), apps.first()->startTime()); QCOMPARE(task1->endTime(), apps.last()->endTime()); apps = mr->appointments(sm->scheduleId()); QVERIFY(apps.count() == 1); QCOMPARE(task1->startTime(), apps.first()->startTime()); QCOMPARE(task1->endTime(), apps.last()->endTime()); s = "Required resource limits availability --------"; qDebug()<setAvailableFrom(tomorrow); sm->createSchedules(); project.calculate(*sm); // Debug::print(r, s); // Debug::print(mr, s); // Debug::print(&project, task1, s); // Debug::printSchedulingLog(*sm, s); QCOMPARE(task1->earlyStart(), task1->requests().workTimeAfter(targetstart)); QVERIFY(task1->lateStart() >= task1->earlyStart()); QVERIFY(task1->earlyFinish() <= task1->endTime()); QVERIFY(task1->lateFinish() >= task1->endTime()); QCOMPARE(task1->startTime(), DateTime(mr->availableFrom().date(), t1)); QCOMPARE(task1->endTime(), task1->startTime() + Duration(0, 8, 0)); QVERIFY(task1->schedulingError() == false); apps = r->appointments(sm->scheduleId()); QVERIFY(apps.count() == 1); QCOMPARE(task1->startTime(), apps.first()->startTime()); QCOMPARE(task1->endTime(), apps.last()->endTime()); apps = mr->appointments(sm->scheduleId()); QVERIFY(apps.count() == 1); QCOMPARE(task1->startTime(), apps.first()->startTime()); QCOMPARE(task1->endTime(), apps.last()->endTime()); } void ProjectTester::resourceWithLimitedAvailability() { Project project; project.setName("P1"); project.setId(project.uniqueNodeId()); project.registerNodeId(&project); DateTime targetstart = DateTime(QDate(2010, 5, 1), QTime(0,0,0)); DateTime targetend = DateTime(targetstart.addDays(7)); project.setConstraintStartTime(targetstart); project.setConstraintEndTime(targetend); DateTime expectedEndTime(QDate(2010, 5, 3), QTime(16, 0, 0)); Calendar *c = new Calendar("Test"); QTime t1(8,0,0); int length = 8*60*60*1000; // 8 hours for (int i = 1; i <= 7; ++i) { CalendarDay *wd1 = c->weekday(i); wd1->setState(CalendarDay::Working); wd1->addInterval(TimeInterval(t1, length)); } project.addCalendar(c); Task *task1 = project.createTask(); task1->setName("T1"); project.addTask(task1, &project); task1->estimate()->setUnit(Duration::Unit_d); task1->estimate()->setExpectedEstimate(4.0); task1->estimate()->setType(Estimate::Type_Effort); QString s = "Two resources: One with available until < resulting task length --------"; qDebug()<setName("R1"); r1->setAvailableFrom(targetstart); r1->setCalendar(c); project.addResource(r1); r1->addParentGroup(g); Resource *r2 = new Resource(); r2->setName("R2"); r2->setAvailableFrom(targetstart); r2->setAvailableUntil(targetstart.addDays(1)); r2->setCalendar(c); project.addResource(r2); r2->addParentGroup(g); ResourceGroupRequest *gr = new ResourceGroupRequest(g); task1->addRequest(gr); ResourceRequest *rr1 = new ResourceRequest(r1, 100); - gr->addResourceRequest(rr1); + task1->requests().addResourceRequest(rr1, gr); ResourceRequest *rr2 = new ResourceRequest(r2, 100); - gr->addResourceRequest(rr2); + task1->requests().addResourceRequest(rr2, gr); ScheduleManager *sm = project.createScheduleManager("Test Plan"); project.addScheduleManager(sm); sm->createSchedules(); project.calculate(*sm); // Debug::print(r1, s); // Debug::print(r2, s); // Debug::print(&project, task1, s); // Debug::printSchedulingLog(*sm, s); QCOMPARE(task1->endTime(), expectedEndTime); } void ProjectTester::unavailableResource() { Project project; project.setName("P1"); project.setId(project.uniqueNodeId()); project.registerNodeId(&project); DateTime targetstart = DateTime(QDate(2010, 5, 1), QTime(0,0,0)); DateTime targetend = DateTime(targetstart.addDays(7)); project.setConstraintStartTime(targetstart); project.setConstraintEndTime(targetend); Calendar *c = new Calendar("Test"); QTime t1(8,0,0); int length = 8*60*60*1000; // 8 hours for (int i = 1; i <= 7; ++i) { CalendarDay *wd1 = c->weekday(i); wd1->setState(CalendarDay::Working); wd1->addInterval(TimeInterval(t1, length)); } project.addCalendar(c); Task *task1 = project.createTask(); task1->setName("T1"); project.addTask(task1, &project); task1->estimate()->setUnit(Duration::Unit_d); task1->estimate()->setExpectedEstimate(2.0); task1->estimate()->setType(Estimate::Type_Effort); QString s = "One available resource --------"; qDebug()<setName("R1"); r1->setCalendar(c); project.addResource(r1); r1->addParentGroup(g); Resource *r2 = new Resource(); r2->setName("Unavailable"); r2->setCalendar(c); project.addResource(r2); r2->addParentGroup(g); ResourceGroupRequest *gr = new ResourceGroupRequest(g); task1->addRequest(gr); - gr->addResourceRequest(new ResourceRequest(r1, 100)); + task1->requests().addResourceRequest(new ResourceRequest(r1, 100), gr); ScheduleManager *sm = project.createScheduleManager("Plan R1"); project.addScheduleManager(sm); sm->createSchedules(); project.calculate(*sm); // Debug::print(r1, s); // Debug::print(r2, s); Debug::print(&project, task1, s); // Debug::printSchedulingLog(*sm, s); DateTime expectedEndTime = targetstart + Duration(1, 16, 0); QCOMPARE(task1->endTime(), expectedEndTime); s = "One available resource + one unavailable resource --------"; qDebug()<setAvailableFrom(targetend); r2->setAvailableUntil(targetend.addDays(1)); - gr->addResourceRequest(new ResourceRequest(r2, 100)); + task1->requests().addResourceRequest(new ResourceRequest(r2, 100), gr); sm = project.createScheduleManager("Team + Resource"); project.addScheduleManager(sm); sm->createSchedules(); project.calculate(*sm); // Debug::print(r1, s); // Debug::print(r2, s); Debug::print(&project, task1, s); // Debug::printSchedulingLog(*sm, s); QCOMPARE(task1->endTime(), expectedEndTime); } void ProjectTester::team() { Project project; project.setName("P1"); project.setId(project.uniqueNodeId()); project.registerNodeId(&project); DateTime targetstart = DateTime(QDate(2010, 5, 1), QTime(0,0,0)); DateTime targetend = DateTime(targetstart.addDays(7)); project.setConstraintStartTime(targetstart); project.setConstraintEndTime(targetend); Calendar *c = new Calendar("Test"); QTime t1(8,0,0); int length = 8*60*60*1000; // 8 hours for (int i = 1; i <= 7; ++i) { CalendarDay *wd1 = c->weekday(i); wd1->setState(CalendarDay::Working); wd1->addInterval(TimeInterval(t1, length)); } project.addCalendar(c); Task *task1 = project.createTask(); task1->setName("T1"); project.addTask(task1, &project); task1->estimate()->setUnit(Duration::Unit_d); task1->estimate()->setExpectedEstimate(2.0); task1->estimate()->setType(Estimate::Type_Effort); QString s = "One team with one resource --------"; qDebug()<setName("R1"); r1->setCalendar(c); project.addResource(r1); r1->addParentGroup(g); Resource *r2 = new Resource(); r2->setName("Team member"); r2->setCalendar(c); project.addResource(r2); r2->addParentGroup(g); Resource *team = new Resource(); team->setType(Resource::Type_Team); team->setName("Team"); project.addResource(team); team->addParentGroup(g); team->addTeamMemberId(r2->id()); ResourceGroupRequest *gr = new ResourceGroupRequest(g); task1->addRequest(gr); ResourceRequest *tr = new ResourceRequest(team, 100); - gr->addResourceRequest(tr); + task1->requests().addResourceRequest(tr, gr); ScheduleManager *sm = project.createScheduleManager("Team"); project.addScheduleManager(sm); sm->createSchedules(); project.calculate(*sm); // Debug::print(r1, s); // Debug::print(r2, s); Debug::print(team, s, false); Debug::print(&project, task1, s); // Debug::printSchedulingLog(*sm, s); DateTime expectedEndTime = targetstart + Duration(1, 16, 0); QCOMPARE(task1->endTime(), expectedEndTime); s = "One team with one resource + one resource --------"; qDebug()<addResourceRequest(rr1); + task1->requests().addResourceRequest(rr1, gr); sm = project.createScheduleManager("Team + Resource"); project.addScheduleManager(sm); sm->createSchedules(); project.calculate(*sm); // Debug::print(r1, s); // Debug::print(r2, s); Debug::print(team, s, false); Debug::print(&project, task1, s); // Debug::printSchedulingLog(*sm, s); expectedEndTime = targetstart + Duration(0, 16, 0); QCOMPARE(task1->endTime(), expectedEndTime); s = "One team with one resource + one resource, resource available too late --------"; qDebug()<setAvailableFrom(targetend); r1->setAvailableUntil(targetend.addDays(7)); sm = project.createScheduleManager("Team + Resource not available"); project.addScheduleManager(sm); sm->createSchedules(); project.calculate(*sm); Debug::print(r1, s); // Debug::print(r2, s); Debug::print(team, s, false); Debug::print(&project, task1, s); // Debug::printSchedulingLog(*sm, s); expectedEndTime = targetstart + Duration(1, 16, 0); QCOMPARE(task1->endTime(), expectedEndTime); s = "One team with two resources --------"; qDebug()<removeRequests(); team->addTeamMemberId(r1->id()); r1->setAvailableFrom(targetstart); r1->setAvailableUntil(targetend); sm = project.createScheduleManager("Team with 2 resources"); project.addScheduleManager(sm); sm->createSchedules(); project.calculate(*sm); // Debug::print(r1, s); // Debug::print(r2, s); Debug::print(team, s, false); Debug::print(&project, task1, s); // Debug::printSchedulingLog(*sm, s); expectedEndTime = targetstart + Duration(0, 16, 0); QCOMPARE(task1->endTime(), expectedEndTime); s = "One team with two resources, one resource unavailable --------"; qDebug()<setAvailableFrom(targetend); r1->setAvailableUntil(targetend.addDays(2)); sm = project.createScheduleManager("Team, one unavailable resource"); project.addScheduleManager(sm); sm->createSchedules(); project.calculate(*sm); Debug::print(r1, s); // Debug::print(r2, s); Debug::print(team, s, false); Debug::print(&project, task1, s); // Debug::printSchedulingLog(*sm, s); expectedEndTime = targetstart + Duration(1, 16, 0); QCOMPARE(task1->endTime(), expectedEndTime); gr->takeResourceRequest(tr); task1->takeRequest(gr); project.takeResource(team); team->removeTeamMemberId(r2->id()); } void ProjectTester::inWBSOrder() { Project p; p.setName("WBS Order"); p.setId(p.uniqueNodeId()); p.registerNodeId(&p); DateTime st = QDateTime::fromString("2012-02-01", Qt::ISODate); st = DateTime(st.addDays(1)); st.setTime(QTime (0, 0, 0)); p.setConstraintStartTime(st); p.setConstraintEndTime(st.addDays(5)); Calendar *c = new Calendar("Test"); QTime t1(8,0,0); int length = 8*60*60*1000; // 8 hours for (int i = 1; i <= 7; ++i) { CalendarDay *wd1 = c->weekday(i); wd1->setState(CalendarDay::Working); wd1->addInterval(TimeInterval(t1, length)); } p.addCalendar(c); p.setDefaultCalendar(c); ResourceGroup *g = new ResourceGroup(); p.addResourceGroup(g); Resource *r1 = new Resource(); r1->setName("R1"); p.addResource(r1); r1->addParentGroup(g); Task *t = p.createTask(); t->setName("T1"); p.addSubTask(t, &p); t->estimate()->setUnit(Duration::Unit_d); t->estimate()->setExpectedEstimate(1.0); t->estimate()->setType(Estimate::Type_Effort); ResourceGroupRequest *gr = new ResourceGroupRequest(g); t->addRequest(gr); ResourceRequest *tr = new ResourceRequest(r1, 100); - gr->addResourceRequest(tr); + t->requests().addResourceRequest(tr, gr); t = p.createTask(); t->setName("T2"); p.addSubTask(t, &p); t->estimate()->setUnit(Duration::Unit_d); t->estimate()->setExpectedEstimate(1.0); t->estimate()->setType(Estimate::Type_Effort); gr = new ResourceGroupRequest(g); t->addRequest(gr); tr = new ResourceRequest(r1, 100); - gr->addResourceRequest(tr); + t->requests().addResourceRequest(tr, gr); t = p.createTask(); t->setName("T3"); p.addSubTask(t, &p); t->estimate()->setUnit(Duration::Unit_d); t->estimate()->setExpectedEstimate(1.0); t->estimate()->setType(Estimate::Type_Effort); gr = new ResourceGroupRequest(g); t->addRequest(gr); tr = new ResourceRequest(r1, 100); - gr->addResourceRequest(tr); + t->requests().addResourceRequest(tr, gr); t = p.createTask(); t->setName("T4"); p.addSubTask(t, &p); t->estimate()->setUnit(Duration::Unit_d); t->estimate()->setExpectedEstimate(1.0); t->estimate()->setType(Estimate::Type_Effort); gr = new ResourceGroupRequest(g); t->addRequest(gr); tr = new ResourceRequest(r1, 100); - gr->addResourceRequest(tr); + t->requests().addResourceRequest(tr, gr); ScheduleManager *sm = p.createScheduleManager("WBS Order, forward"); p.addScheduleManager(sm); sm->createSchedules(); p.calculate(*sm); QString s = "Schedule 4 tasks forward in wbs order -------"; // NOTE: It's not *mandatory* to schedule in wbs order but users expect it, so we'll try // This test can be removed if for some important reason this isn't possible. // Debug::print (c, s); // Debug::print(r1, s); // Debug::print(&p, s, true); // Debug::printSchedulingLog(*sm, s); QCOMPARE(p.allTasks().count(), 4); QCOMPARE(p.allTasks().at(0)->startTime(), st + Duration(0, 8, 0)); QCOMPARE(p.allTasks().at(1)->startTime(), st + Duration(1, 8, 0)); QCOMPARE(p.allTasks().at(2)->startTime(), st + Duration(2, 8, 0)); QCOMPARE(p.allTasks().at(3)->startTime(), st + Duration(3, 8, 0)); } void ProjectTester::resourceConflictALAP() { Project p; p.setName("resourceConflictALAP"); p.setId(p.uniqueNodeId()); p.registerNodeId(&p); DateTime st = QDateTime::fromString("2012-02-01", Qt::ISODate); st = DateTime(st.addDays(1)); st.setTime(QTime (0, 0, 0)); p.setConstraintStartTime(st); p.setConstraintEndTime(st.addDays(5)); Calendar *c = new Calendar("Test"); QTime t1(8,0,0); int length = 8*60*60*1000; // 8 hours for (int i = 1; i <= 7; ++i) { CalendarDay *wd1 = c->weekday(i); wd1->setState(CalendarDay::Working); wd1->addInterval(TimeInterval(t1, length)); } p.addCalendar(c); p.setDefaultCalendar(c); ResourceGroup *g = new ResourceGroup(); p.addResourceGroup(g); Resource *r1 = new Resource(); r1->setName("R1"); p.addResource(r1); r1->addParentGroup(g); Task *t = p.createTask(); t->setName("T1"); t->setConstraint(Node::ALAP); p.addSubTask(t, &p); t->estimate()->setUnit(Duration::Unit_d); t->estimate()->setExpectedEstimate(1.0); t->estimate()->setType(Estimate::Type_Effort); ResourceGroupRequest *gr = new ResourceGroupRequest(g); t->addRequest(gr); ResourceRequest *tr = new ResourceRequest(r1, 100); - gr->addResourceRequest(tr); + t->requests().addResourceRequest(tr, gr); t = p.createTask(); t->setName("T2"); p.addSubTask(t, &p); t->estimate()->setUnit(Duration::Unit_d); t->estimate()->setExpectedEstimate(1.0); t->estimate()->setType(Estimate::Type_Effort); gr = new ResourceGroupRequest(g); t->addRequest(gr); tr = new ResourceRequest(r1, 100); - gr->addResourceRequest(tr); + t->requests().addResourceRequest(tr, gr); t = p.createTask(); t->setName("T3"); p.addSubTask(t, &p); t->estimate()->setUnit(Duration::Unit_d); t->estimate()->setExpectedEstimate(1.0); t->estimate()->setType(Estimate::Type_Effort); gr = new ResourceGroupRequest(g); t->addRequest(gr); tr = new ResourceRequest(r1, 100); - gr->addResourceRequest(tr); + t->requests().addResourceRequest(tr, gr); t = p.createTask(); t->setName("T4"); p.addSubTask(t, &p); t->estimate()->setUnit(Duration::Unit_d); t->estimate()->setExpectedEstimate(1.0); t->estimate()->setType(Estimate::Type_Effort); gr = new ResourceGroupRequest(g); t->addRequest(gr); tr = new ResourceRequest(r1, 100); - gr->addResourceRequest(tr); + t->requests().addResourceRequest(tr, gr); ScheduleManager *sm = p.createScheduleManager("T1 ALAP"); p.addScheduleManager(sm); sm->createSchedules(); p.calculate(*sm); QString s = "Schedule T1 ALAP -------"; // Debug::print (c, s); // Debug::print(r1, s); // Debug::print(&p, s, true); // Debug::printSchedulingLog(*sm, s); QCOMPARE(p.allTasks().count(), 4); QCOMPARE(p.allTasks().at(0)->startTime(), st + Duration(3, 8, 0)); QCOMPARE(p.allTasks().at(0)->endTime(), st + Duration(3, 8, 0) + Duration(0, 8, 0)); QCOMPARE(p.allTasks().at(1)->startTime(), st + Duration(0, 8, 0)); QCOMPARE(p.allTasks().at(1)->endTime(), st + Duration(0, 8, 0) + Duration(0, 8, 0)); QCOMPARE(p.allTasks().at(2)->startTime(), st + Duration(1, 8, 0)); QCOMPARE(p.allTasks().at(2)->endTime(), st + Duration(1, 8, 0) + Duration(0, 8, 0)); QCOMPARE(p.allTasks().at(3)->startTime(), st + Duration(2, 8, 0)); QCOMPARE(p.allTasks().at(3)->endTime(), st + Duration(2, 8, 0) + Duration(0, 8, 0)); s = "Schedule T1, T2 ALAP -------"; p.allTasks().at(1)->setConstraint(Node::ALAP); sm = p.createScheduleManager("T1, T2 ALAP"); p.addScheduleManager(sm); sm->createSchedules(); p.calculate(*sm); // Debug::print (c, s); // Debug::print(r1, s); // Debug::print(&p, s, true); // Debug::printSchedulingLog(*sm, s); QCOMPARE(p.allTasks().count(), 4); QCOMPARE(p.allTasks().at(0)->startTime(), st + Duration(3, 8, 0)); QCOMPARE(p.allTasks().at(0)->endTime(), st + Duration(3, 8, 0) + Duration(0, 8, 0)); QCOMPARE(p.allTasks().at(1)->startTime(), st + Duration(2, 8, 0)); QCOMPARE(p.allTasks().at(1)->endTime(), st + Duration(2, 8, 0) + Duration(0, 8, 0)); QCOMPARE(p.allTasks().at(2)->startTime(), st + Duration(0, 8, 0)); QCOMPARE(p.allTasks().at(2)->endTime(), st + Duration(0, 8, 0) + Duration(0, 8, 0)); QCOMPARE(p.allTasks().at(3)->startTime(), st + Duration(1, 8, 0)); QCOMPARE(p.allTasks().at(3)->endTime(), st + Duration(1, 8, 0) + Duration(0, 8, 0)); s = "Schedule T1, T2, T3 ALAP -------"; p.allTasks().at(2)->setConstraint(Node::ALAP); sm = p.createScheduleManager("T1, T2, T3 ALAP"); p.addScheduleManager(sm); sm->createSchedules(); p.calculate(*sm); // Debug::print (c, s); // Debug::print(r1, s); // Debug::print(&p, s, true); // Debug::printSchedulingLog(*sm, s); QCOMPARE(p.allTasks().count(), 4); QCOMPARE(p.allTasks().at(0)->startTime(), st + Duration(3, 8, 0)); QCOMPARE(p.allTasks().at(0)->endTime(), st + Duration(3, 8, 0) + Duration(0, 8, 0)); QCOMPARE(p.allTasks().at(1)->startTime(), st + Duration(2, 8, 0)); QCOMPARE(p.allTasks().at(1)->endTime(), st + Duration(2, 8, 0) + Duration(0, 8, 0)); QCOMPARE(p.allTasks().at(2)->startTime(), st + Duration(1, 8, 0)); QCOMPARE(p.allTasks().at(2)->endTime(), st + Duration(1, 8, 0) + Duration(0, 8, 0)); QCOMPARE(p.allTasks().at(3)->startTime(), st + Duration(0, 8, 0)); QCOMPARE(p.allTasks().at(3)->endTime(), st + Duration(0, 8, 0) + Duration(0, 8, 0)); s = "Schedule T1, T2, T3, T4 ALAP -------"; p.allTasks().at(3)->setConstraint(Node::ALAP); sm = p.createScheduleManager("T1, T2, T3, T4 ALAP"); p.addScheduleManager(sm); sm->createSchedules(); p.calculate(*sm); // Debug::print (c, s); // Debug::print(r1, s); // Debug::print(&p, s, true); // Debug::printSchedulingLog(*sm, s); QCOMPARE(p.allTasks().count(), 4); QCOMPARE(p.allTasks().at(0)->startTime(), st + Duration(3, 8, 0)); QCOMPARE(p.allTasks().at(0)->endTime(), st + Duration(3, 8, 0) + Duration(0, 8, 0)); QCOMPARE(p.allTasks().at(1)->startTime(), st + Duration(2, 8, 0)); QCOMPARE(p.allTasks().at(1)->endTime(), st + Duration(2, 8, 0) + Duration(0, 8, 0)); QCOMPARE(p.allTasks().at(2)->startTime(), st + Duration(1, 8, 0)); QCOMPARE(p.allTasks().at(2)->endTime(), st + Duration(1, 8, 0) + Duration(0, 8, 0)); QCOMPARE(p.allTasks().at(3)->startTime(), st + Duration(0, 8, 0)); QCOMPARE(p.allTasks().at(3)->endTime(), st + Duration(0, 8, 0) + Duration(0, 8, 0)); } void ProjectTester::resourceConflictMustStartOn() { Project p; p.setName("resourceConflictMustStartOn"); p.setId(p.uniqueNodeId()); p.registerNodeId(&p); DateTime st = QDateTime::fromString("2012-02-01T00:00:00", Qt::ISODate); st = DateTime(st.addDays(1)); st.setTime(QTime (0, 0, 0)); p.setConstraintStartTime(st); p.setConstraintEndTime(st.addDays(5)); Calendar *c = new Calendar("Test"); QTime t1(8,0,0); int length = 8*60*60*1000; // 8 hours for (int i = 1; i <= 7; ++i) { CalendarDay *wd1 = c->weekday(i); wd1->setState(CalendarDay::Working); wd1->addInterval(TimeInterval(t1, length)); } p.addCalendar(c); p.setDefaultCalendar(c); ResourceGroup *g = new ResourceGroup(); p.addResourceGroup(g); Resource *r1 = new Resource(); r1->setName("R1"); p.addResource(r1); r1->addParentGroup(g); Task *t = p.createTask(); t->setName("T1"); t->setConstraint(Node::MustStartOn); t->setConstraintStartTime(st + Duration(1, 8, 0)); p.addSubTask(t, &p); t->estimate()->setUnit(Duration::Unit_d); t->estimate()->setExpectedEstimate(1.0); t->estimate()->setType(Estimate::Type_Effort); ResourceGroupRequest *gr = new ResourceGroupRequest(g); t->addRequest(gr); ResourceRequest *tr = new ResourceRequest(r1, 100); - gr->addResourceRequest(tr); + t->requests().addResourceRequest(tr, gr); t = p.createTask(); t->setName("T2"); p.addSubTask(t, &p); t->estimate()->setUnit(Duration::Unit_d); t->estimate()->setExpectedEstimate(1.0); t->estimate()->setType(Estimate::Type_Effort); gr = new ResourceGroupRequest(g); t->addRequest(gr); tr = new ResourceRequest(r1, 100); - gr->addResourceRequest(tr); + t->requests().addResourceRequest(tr, gr); t = p.createTask(); t->setName("T3"); p.addSubTask(t, &p); t->estimate()->setUnit(Duration::Unit_d); t->estimate()->setExpectedEstimate(1.0); t->estimate()->setType(Estimate::Type_Effort); gr = new ResourceGroupRequest(g); t->addRequest(gr); tr = new ResourceRequest(r1, 100); - gr->addResourceRequest(tr); + t->requests().addResourceRequest(tr, gr); t = p.createTask(); t->setName("T4"); p.addSubTask(t, &p); t->estimate()->setUnit(Duration::Unit_d); t->estimate()->setExpectedEstimate(1.0); t->estimate()->setType(Estimate::Type_Effort); gr = new ResourceGroupRequest(g); t->addRequest(gr); tr = new ResourceRequest(r1, 100); - gr->addResourceRequest(tr); + t->requests().addResourceRequest(tr, gr); ScheduleManager *sm = p.createScheduleManager("T1 MustStartOn"); p.addScheduleManager(sm); sm->createSchedules(); p.calculate(*sm); QString s = "Schedule T1 MustStartOn -------"; // Debug::print (c, s); // Debug::print(r1, s); Debug::print(&p, s, true); // Debug::printSchedulingLog(*sm, s); QCOMPARE(p.allTasks().count(), 4); QCOMPARE(p.allTasks().at(0)->startTime(), st + Duration(1, 8, 0)); QCOMPARE(p.allTasks().at(0)->endTime(), st + Duration(1, 8, 0) + Duration(0, 8, 0)); QCOMPARE(p.allTasks().at(1)->startTime(), st + Duration(0, 8, 0)); QCOMPARE(p.allTasks().at(1)->endTime(), st + Duration(0, 8, 0) + Duration(0, 8, 0)); QCOMPARE(p.allTasks().at(2)->startTime(), st + Duration(2, 8, 0)); QCOMPARE(p.allTasks().at(2)->endTime(), st + Duration(2, 8, 0) + Duration(0, 8, 0)); QCOMPARE(p.allTasks().at(3)->startTime(), st + Duration(3, 8, 0)); QCOMPARE(p.allTasks().at(3)->endTime(), st + Duration(3, 8, 0) + Duration(0, 8, 0)); s = "Schedule T1, T2 MustStartOn -------"; p.allTasks().at(1)->setConstraint(Node::MustStartOn); p.allTasks().at(1)->setConstraintStartTime(st + Duration(2, 8, 0)); sm = p.createScheduleManager("T1, T2 MustStartOn"); p.addScheduleManager(sm); sm->createSchedules(); p.calculate(*sm); // Debug::print (c, s); // Debug::print(r1, s); Debug::print(&p, s, true); // Debug::printSchedulingLog(*sm, s); QCOMPARE(p.allTasks().count(), 4); QCOMPARE(p.allTasks().at(0)->startTime(), st + Duration(1, 8, 0)); QCOMPARE(p.allTasks().at(0)->endTime(), st + Duration(1, 8, 0) + Duration(0, 8, 0)); QCOMPARE(p.allTasks().at(1)->startTime(), st + Duration(2, 8, 0)); QCOMPARE(p.allTasks().at(1)->endTime(), st + Duration(2, 8, 0) + Duration(0, 8, 0)); QCOMPARE(p.allTasks().at(2)->startTime(), st + Duration(0, 8, 0)); QCOMPARE(p.allTasks().at(2)->endTime(), st + Duration(0, 8, 0) + Duration(0, 8, 0)); QCOMPARE(p.allTasks().at(3)->startTime(), st + Duration(3, 8, 0)); QCOMPARE(p.allTasks().at(3)->endTime(), st + Duration(3, 8, 0) + Duration(0, 8, 0)); s = "Schedule T1, T2, T3 MustStartOn -------"; p.allTasks().at(2)->setConstraint(Node::MustStartOn); p.allTasks().at(2)->setConstraintStartTime(st + Duration(3, 8, 0)); sm = p.createScheduleManager("T1, T2, T3 MustStartOn"); p.addScheduleManager(sm); sm->createSchedules(); p.calculate(*sm); // Debug::print (c, s); // Debug::print(r1, s); // Debug::print(&p, s, true); // Debug::printSchedulingLog(*sm, s); QCOMPARE(p.allTasks().count(), 4); QCOMPARE(p.allTasks().at(0)->startTime(), st + Duration(1, 8, 0)); QCOMPARE(p.allTasks().at(0)->endTime(), st + Duration(1, 8, 0) + Duration(0, 8, 0)); QCOMPARE(p.allTasks().at(1)->startTime(), st + Duration(2, 8, 0)); QCOMPARE(p.allTasks().at(1)->endTime(), st + Duration(2, 8, 0) + Duration(0, 8, 0)); QCOMPARE(p.allTasks().at(2)->startTime(), st + Duration(3, 8, 0)); QCOMPARE(p.allTasks().at(2)->endTime(), st + Duration(3, 8, 0) + Duration(0, 8, 0)); QCOMPARE(p.allTasks().at(3)->startTime(), st + Duration(0, 8, 0)); QCOMPARE(p.allTasks().at(3)->endTime(), st + Duration(0, 8, 0) + Duration(0, 8, 0)); s = "Schedule backwards, T1 MustStartOn, T2 ASAP -------"; p.takeTask(p.allTasks().at(3), false); p.takeTask(p.allTasks().at(2), false); Task *task1 = p.allTasks().at(0); Task *task2 = p.allTasks().at(1); DateTime et = p.constraintEndTime(); task1->setConstraint(Node::MustStartOn); task1->setConstraintStartTime(et - Duration(1, 16, 0)); task2->setConstraint(Node::ASAP); sm = p.createScheduleManager("T1 MustStartOn, T2 ASAP"); p.addScheduleManager(sm); sm->createSchedules(); sm->setSchedulingDirection(true); p.calculate(*sm); Debug::print(&p, s, true); // Debug::printSchedulingLog(*sm, s); QCOMPARE(task1->startTime(), task1->mustStartOn()); QCOMPARE(task2->startTime(), et - Duration(0, 16, 0)); s = "Schedule backwards, T1 MustStartOn, T2 StartNotEarlier -------"; task2->setConstraint(Node::StartNotEarlier); task2->setConstraintStartTime(task1->mustStartOn().addDays(-1)); sm = p.createScheduleManager("T1 MustStartOn, T2 StartNotEarlier"); p.addScheduleManager(sm); sm->createSchedules(); sm->setSchedulingDirection(true); p.calculate(*sm); Debug::print(&p, s, true); // Debug::printSchedulingLog(*sm, s); QCOMPARE(task1->startTime(), task1->mustStartOn()); QCOMPARE(task2->startTime(), et - Duration(0, 16, 0)); task2->setConstraintStartTime(task1->mustStartOn()); sm->createSchedules(); p.calculate(*sm); Debug::print(&p, s, true); // Debug::printSchedulingLog(*sm, s); QCOMPARE(task1->startTime(), task1->mustStartOn()); QCOMPARE(task2->startTime(), et - Duration(0, 16, 0)); task2->setConstraintStartTime(task1->mustStartOn().addDays(1)); sm->createSchedules(); p.calculate(*sm); Debug::print(&p, s, true); // Debug::printSchedulingLog(*sm, s); QCOMPARE(task1->startTime(), task1->mustStartOn()); QCOMPARE(task2->startTime(), et - Duration(0, 16, 0)); } void ProjectTester::resourceConflictMustFinishOn() { Project p; p.setName("P1"); p.setId(p.uniqueNodeId()); p.registerNodeId(&p); DateTime st = QDateTime::fromString("2012-02-01", Qt::ISODate); st = DateTime(st.addDays(1)); st.setTime(QTime (0, 0, 0)); p.setConstraintStartTime(st); p.setConstraintEndTime(st.addDays(5)); Calendar *c = new Calendar("Test"); QTime t1(8,0,0); int length = 8*60*60*1000; // 8 hours for (int i = 1; i <= 7; ++i) { CalendarDay *wd1 = c->weekday(i); wd1->setState(CalendarDay::Working); wd1->addInterval(TimeInterval(t1, length)); } p.addCalendar(c); p.setDefaultCalendar(c); ResourceGroup *g = new ResourceGroup(); p.addResourceGroup(g); Resource *r1 = new Resource(); r1->setName("R1"); p.addResource(r1); r1->addParentGroup(g); Task *task1 = p.createTask(); task1->setName("T1"); task1->setConstraint(Node::MustFinishOn); task1->setConstraintEndTime(st + Duration(1, 16, 0)); p.addSubTask(task1, &p); task1->estimate()->setUnit(Duration::Unit_d); task1->estimate()->setExpectedEstimate(1.0); task1->estimate()->setType(Estimate::Type_Effort); ResourceGroupRequest *gr = new ResourceGroupRequest(g); task1->addRequest(gr); ResourceRequest *tr = new ResourceRequest(r1, 100); - gr->addResourceRequest(tr); + task1->requests().addResourceRequest(tr, gr); Task *task2 = p.createTask(); task2->setName("T2"); p.addSubTask(task2, &p); task2->estimate()->setUnit(Duration::Unit_d); task2->estimate()->setExpectedEstimate(1.0); task2->estimate()->setType(Estimate::Type_Effort); gr = new ResourceGroupRequest(g); task2->addRequest(gr); tr = new ResourceRequest(r1, 100); - gr->addResourceRequest(tr); + task2->requests().addResourceRequest(tr, gr); QString s = "Schedule T1 MustFinishOn, T2 ASAP -------"; qDebug()<createSchedules(); p.calculate(*sm); Debug::print(&p, s, true); QCOMPARE(task1->endTime(), task1->mustFinishOn()); QCOMPARE(task2->startTime(), p.constraintStartTime() + Duration(0, 8, 0)); s = "Schedule T1 MustFinishOn, T2 ALAP -------"; qDebug()<setConstraint(Node::ALAP); sm->createSchedules(); p.calculate(*sm); Debug::print(&p, s, true); QCOMPARE(task1->endTime(), task1->mustFinishOn()); QCOMPARE(task2->startTime(), p.constraintStartTime() + Duration(0, 8, 0)); s = "Schedule T1 MustFinishOn, T2 StartNotEarlier -------"; qDebug()<setConstraint(Node::StartNotEarlier); task2->setConstraintStartTime(p.constraintStartTime()); sm->createSchedules(); p.calculate(*sm); Debug::print(&p, s, true); QCOMPARE(task1->endTime(), task1->mustFinishOn()); QCOMPARE(task2->startTime(), p.constraintStartTime() + Duration(0, 8, 0)); s = "Schedule T1 MustFinishOn, T2 StartNotEarlier -------"; qDebug()<setConstraintStartTime(task1->mustFinishOn() - Duration(0, 8, 0)); sm->createSchedules(); p.calculate(*sm); Debug::print(&p, s, true); QCOMPARE(task1->endTime(), task1->mustFinishOn()); QCOMPARE(task2->startTime(), task2->constraintStartTime() + Duration(1, 0, 0)); s = "Schedule T1 MustFinishOn, T2 StartNotEarlier -------"; qDebug()<setConstraintStartTime(task1->mustFinishOn()); sm->createSchedules(); p.calculate(*sm); Debug::print(&p, s, true); QCOMPARE(task1->endTime(), task1->mustFinishOn()); QCOMPARE(task2->startTime(), task2->constraintStartTime() + Duration(0, 16, 0)); s = "Schedule backwards, T1 MustFinishOn, T2 ASAP -------"; qDebug()<setConstraint(Node::ASAP); sm->createSchedules(); sm->setSchedulingDirection(true); p.calculate(*sm); Debug::print(&p, s, true); QCOMPARE(task1->endTime(), task1->mustFinishOn()); QCOMPARE(task2->startTime(), task1->mustFinishOn() + Duration(0, 16, 0)); s = "Schedule backwards, T1 MustFinishOn, T2 ALAP -------"; qDebug()<setConstraint(Node::ALAP); sm->createSchedules(); p.calculate(*sm); Debug::print(&p, s, true); // Debug::printSchedulingLog(*sm, s); QCOMPARE(task1->endTime(), task1->mustFinishOn()); QCOMPARE(task2->endTime(), et - Duration(0, 8, 0)); s = "Schedule backwards, T1 MustFinishOn, T2 StartNotEarlier -------"; qDebug()<setConstraint(Node::StartNotEarlier); task2->setConstraintStartTime(task1->mustFinishOn()); sm->createSchedules(); p.calculate(*sm); Debug::print(&p, s, true); // Debug::printSchedulingLog(*sm, s); QCOMPARE(task1->endTime(), task1->mustFinishOn()); QCOMPARE(task2->endTime(), et - Duration(0, 8, 0)); task2->setConstraintStartTime(p.constraintStartTime()); sm->createSchedules(); p.calculate(*sm); Debug::print(&p, s, true); // Debug::printSchedulingLog(*sm, s); QCOMPARE(task1->endTime(), task1->mustFinishOn()); QCOMPARE(task2->endTime(), et - Duration(0, 8, 0)); s = "Schedule backwards, T1 MustFinishOn, T2 FinishNotLater -------"; qDebug()<setConstraint(Node::FinishNotLater); task2->setConstraintEndTime(task1->mustFinishOn()); sm->createSchedules(); p.calculate(*sm); Debug::print(&p, s, true); // Debug::printSchedulingLog(*sm, s); QCOMPARE(task1->endTime(), task1->mustFinishOn()); QCOMPARE(task2->endTime(), task1->startTime() - Duration(0, 16, 0)); task2->setConstraintEndTime(task1->mustFinishOn().addDays(2)); sm->createSchedules(); p.calculate(*sm); Debug::print(&p, s, true); // Debug::printSchedulingLog(*sm, s); QCOMPARE(task1->endTime(), task1->mustFinishOn()); QCOMPARE(task2->endTime(), task2->finishNotLater()); } void ProjectTester::fixedInterval() { Project p; p.setName("P1"); p.setId(p.uniqueNodeId()); p.registerNodeId(&p); DateTime st = QDateTime::fromString("2010-10-20T08:00", Qt::ISODate); p.setConstraintStartTime(st); p.setConstraintEndTime(st.addDays(5)); Calendar *c = new Calendar("Test"); QTime t1(8,0,0); int length = 8*60*60*1000; // 8 hours for (int i = 1; i <= 7; ++i) { CalendarDay *wd1 = c->weekday(i); wd1->setState(CalendarDay::Working); wd1->addInterval(TimeInterval(t1, length)); } p.addCalendar(c); p.setDefaultCalendar(c); ResourceGroup *g = new ResourceGroup(); p.addResourceGroup(g); Resource *r1 = new Resource(); r1->setName("R1"); p.addResource(r1); r1->addParentGroup(g); Task *task1 = p.createTask(); task1->setName("T1"); task1->setConstraint(Node::FixedInterval); task1->setConstraintStartTime(st); task1->setConstraintEndTime(st.addDays(1)); p.addTask(task1, &p); QString s = "Schedule T1 Fixed interval -------"; qDebug()<createSchedules(); p.calculate(*sm); Debug::print(&p, s, true); QCOMPARE(task1->startTime(), task1->constraintStartTime()); QCOMPARE(task1->endTime(), task1->constraintEndTime()); s = "Schedule backward: T1 Fixed interval -------"; qDebug()<setSchedulingDirection(true); sm->createSchedules(); p.calculate(*sm); Debug::print(&p, s, true); // Debug::printSchedulingLog(*sm, s); QCOMPARE(task1->startTime(), task1->constraintStartTime()); QCOMPARE(task1->endTime(), task1->constraintEndTime()); } void ProjectTester::estimateDuration() { Project p; p.setName("P1"); p.setId(p.uniqueNodeId()); p.registerNodeId(&p); DateTime st = QDateTime::fromString("2010-10-20 08:00", Qt::TextDate); p.setConstraintStartTime(st); p.setConstraintEndTime(st.addDays(5)); Calendar *c = new Calendar("Test"); QTime t1(8,0,0); int length = 8*60*60*1000; // 8 hours for (int i = 1; i <= 7; ++i) { CalendarDay *wd1 = c->weekday(i); wd1->setState(CalendarDay::Working); wd1->addInterval(TimeInterval(t1, length)); } p.addCalendar(c); p.setDefaultCalendar(c); ResourceGroup *g = new ResourceGroup(); p.addResourceGroup(g); Resource *r1 = new Resource(); r1->setName("R1"); p.addResource(r1); r1->addParentGroup(g); Task *task1 = p.createTask(); task1->setName("T1"); task1->setConstraint(Node::ASAP); p.addTask(task1, &p); task1->estimate()->setType(Estimate::Type_Duration); task1->estimate()->setUnit(Duration::Unit_h); task1->estimate()->setExpectedEstimate(10); QString s = "Schedule T1 Estimate type Duration -------"; qDebug()<createSchedules(); p.calculate(*sm); Debug::print(&p, s, true); QCOMPARE(task1->startTime(), p.constraintStartTime()); QCOMPARE(task1->endTime(), task1->startTime() + Duration(0, 10, 0)); s = "Schedule backward: T1 Estimate type Duration -------"; qDebug()<setSchedulingDirection(true); sm->createSchedules(); p.calculate(*sm); Debug::print(&p, s, true); QCOMPARE(task1->endTime(), p.constraintEndTime()); QCOMPARE(task1->startTime(), task1->endTime() - Duration(0, 10, 0)); } void ProjectTester::startStart() { Project p; p.setName("P1"); p.setId(p.uniqueNodeId()); p.registerNodeId(&p); DateTime st = QDateTime::fromString("2010-10-20T00:00:00", Qt::ISODate); p.setConstraintStartTime(st); p.setConstraintEndTime(st.addDays(5)); Calendar *c = new Calendar("Test"); QTime t1(8,0,0); int length = 8*60*60*1000; // 8 hours for (int i = 1; i <= 7; ++i) { CalendarDay *wd1 = c->weekday(i); wd1->setState(CalendarDay::Working); wd1->addInterval(TimeInterval(t1, length)); } p.addCalendar(c); p.setDefaultCalendar(c); ResourceGroup *g = new ResourceGroup(); p.addResourceGroup(g); Resource *r1 = new Resource(); r1->setName("R1"); p.addResource(r1); r1->addParentGroup(g); Resource *r2 = new Resource(); r2->setName("R2"); p.addResource(r2); r2->addParentGroup(g); Task *task1 = p.createTask(); task1->setName("T1"); task1->setConstraint(Node::ASAP); p.addTask(task1, &p); task1->estimate()->setType(Estimate::Type_Duration); task1->estimate()->setUnit(Duration::Unit_h); task1->estimate()->setExpectedEstimate(2); Task *task2 = p.createTask(); task2->setName("T2"); task2->setConstraint(Node::ASAP); p.addTask(task2, &p); task2->estimate()->setType(Estimate::Type_Duration); task2->estimate()->setUnit(Duration::Unit_h); task2->estimate()->setExpectedEstimate(2); task1->addDependChildNode(task2, Relation::StartStart); QString s = "Schedule T1 Lag = 0 -------"; qDebug()<createSchedules(); p.calculate(*sm); Debug::print(&p, s, true); QCOMPARE(task1->startTime(), p.constraintStartTime()); QCOMPARE(task1->lateStart(), task2->lateStart()); QCOMPARE(task1->startTime(), task2->startTime()); s = "Schedule backward T1 Lag = 0 -------"; qDebug()<createSchedules(); sm->setSchedulingDirection(true); p.calculate(*sm); // Debug::printSchedulingLog(*sm, s); Debug::print(&p, s, true); qDebug()<<"predeccessors:"<dependParentNodes(); QCOMPARE(task2->endTime(), p.constraintEndTime()); QCOMPARE(task1->lateStart(), task2->lateStart()); QCOMPARE(task1->startTime(), task2->startTime()); s = "Schedule T1 calendar -------"; qDebug()<estimate()->setCalendar(c); sm = p.createScheduleManager("Lag = 0"); p.addScheduleManager(sm); sm->createSchedules(); p.calculate(*sm); Debug::print(&p, s, true); QCOMPARE(task1->startTime(), p.constraintStartTime() + Duration(0, 8, 0)); QCOMPARE(task1->lateStart(), task2->lateStart()); QCOMPARE(task1->startTime(), task2->startTime()); s = "Schedule backward T1 calendar -------"; qDebug()<estimate()->setCalendar(0); task2->estimate()->setCalendar(c); sm = p.createScheduleManager("Backward, calendar, Lag = 0"); p.addScheduleManager(sm); sm->createSchedules(); sm->setSchedulingDirection(true); p.calculate(*sm); Debug::print(&p, s, true); QCOMPARE(task2->endTime(), p.constraintEndTime() - Duration(0, 8, 0)); QCOMPARE(task1->lateStart(), task2->lateStart()); QCOMPARE(task1->startTime(), task2->startTime()); s = "Schedule Lag = 1 hour -------"; qDebug()<estimate()->setCalendar(c); task2->estimate()->setCalendar(0); task1->dependChildNodes().at(0)->setLag(Duration(0, 1, 0)); sm = p.createScheduleManager("Lag = 1 hour"); p.addScheduleManager(sm); sm->createSchedules(); p.calculate(*sm); Debug::print(&p, s, true); QCOMPARE(task1->startTime(), p.constraintStartTime() + Duration(0, 8, 0)); QCOMPARE(task2->lateStart(), task1->lateStart() + task1->dependChildNodes().at(0)->lag()); QCOMPARE(task2->startTime(), task1->startTime() + task1->dependChildNodes().at(0)->lag()); s = "Schedule backward Lag = 1 hour -------"; qDebug()<estimate()->setCalendar(0); task2->estimate()->setCalendar(c); sm = p.createScheduleManager("Backward, Lag = 1 hour"); p.addScheduleManager(sm); sm->createSchedules(); sm->setSchedulingDirection(true); p.calculate(*sm); Debug::print(&p, s, true); QCOMPARE(task2->endTime(), p.constraintEndTime() - Duration(0, 8, 0)); QCOMPARE(task2->lateStart(), task1->lateStart() + task1->dependChildNodes().at(0)->lag()); QCOMPARE(task2->startTime(), task1->startTime() + task1->dependChildNodes().at(0)->lag()); s = "Schedule resources Lag = 1 hour -------"; qDebug()<estimate()->setCalendar(0); task2->estimate()->setCalendar(0); ResourceGroupRequest *gr1 = new ResourceGroupRequest(g); task1->addRequest(gr1); ResourceRequest *rr1 = new ResourceRequest(r1, 100); - gr1->addResourceRequest(rr1); + task1->requests().addResourceRequest(rr1, gr1); task1->estimate()->setType(Estimate::Type_Effort); ResourceGroupRequest *gr2 = new ResourceGroupRequest(g); task2->addRequest(gr2); ResourceRequest *rr2 = new ResourceRequest(r2, 100); - gr2->addResourceRequest(rr2); + task2->requests().addResourceRequest(rr2, gr2); task2->estimate()->setType(Estimate::Type_Effort); sm = p.createScheduleManager("Resources, Lag = 1 hour"); p.addScheduleManager(sm); sm->createSchedules(); sm->setSchedulingDirection(false); p.calculate(*sm); Debug::print(&p, s, true); QCOMPARE(task1->startTime(), p.constraintStartTime() + Duration(0, 8, 0)); QCOMPARE(task2->lateStart(), task1->lateStart() + task1->dependChildNodes().at(0)->lag()); QCOMPARE(task2->startTime(), task1->startTime() + task1->dependChildNodes().at(0)->lag()); s = "Schedule backward resources Lag = 1 hour -------"; qDebug()<createSchedules(); sm->setSchedulingDirection(true); p.calculate(*sm); Debug::print(&p, s, true); QCOMPARE(task2->endTime(), p.constraintEndTime() - Duration(0, 8, 0)); QCOMPARE(task2->lateStart(), task1->lateStart() + task1->dependChildNodes().at(0)->lag()); QCOMPARE(task2->startTime(), task1->startTime() + task1->dependChildNodes().at(0)->lag()); s = "Schedule resources w limited availability, Lag = 1 hour -------"; qDebug()<setAvailableFrom(p.constraintStartTime() + Duration(0, 9, 0)); r1->setAvailableUntil(p.constraintEndTime() - Duration(0, 12, 0)); sm = p.createScheduleManager("Resources, Lag = 1 hour"); p.addScheduleManager(sm); sm->createSchedules(); sm->setSchedulingDirection(false); p.calculate(*sm); Debug::print(&p, s, true); QCOMPARE(task1->startTime(), p.constraintStartTime() + Duration(0, 9, 0)); QCOMPARE(task2->lateStart(), task1->lateStart() + task1->dependChildNodes().at(0)->lag()); QCOMPARE(task2->startTime(), task1->startTime() + task1->dependChildNodes().at(0)->lag()); s = "Schedule backward resources w limited availability Lag = 1 hour -------"; qDebug()<createSchedules(); sm->setSchedulingDirection(true); p.calculate(*sm); Debug::print(&p, s, true); QVERIFY(task2->lateStart() >= task1->lateStart() + task1->dependChildNodes().at(0)->lag()); QVERIFY(task2->startTime() >= task1->startTime() + task1->dependChildNodes().at(0)->lag()); } void ProjectTester::scheduleTimeZone() { QByteArray tz("TZ=Europe/Copenhagen"); putenv(tz.data()); qDebug()<<"Local timezone: "<day(), 8.0); Calendar *calendar = new Calendar(); calendar->setName("LocalTime, Copenhagen"); calendar->setDefault(true); QCOMPARE(calendar->timeZone(), QTimeZone::systemTimeZone()); QTime time1(9, 0, 0); QTime time2 (17, 0, 0); int length = time1.msecsTo(time2); for (int i=1; i <= 7; ++i) { CalendarDay *d = calendar->weekday(i); d->setState(CalendarDay::Working); d->addInterval(time1, length); } project.addCalendar(calendar); Calendar *cal2 = new Calendar(); cal2->setName("Helsinki"); cal2->setTimeZone(QTimeZone("Europe/Helsinki")); QVERIFY(cal2->timeZone().isValid()); for (int i=1; i <= 7; ++i) { CalendarDay *d = cal2->weekday(i); d->setState(CalendarDay::Working); d->addInterval(time1, length); } project.addCalendar(cal2); QDate today = project.constraintStartTime().date(); Task *t = project.createTask(); t->setName("T1"); project.addTask(t, &project); t->estimate()->setUnit(Duration::Unit_d); t->estimate()->setExpectedEstimate(1.0); t->estimate()->setType(Estimate::Type_Effort); Task *t2 = project.createTask(); t2->setName("T2"); project.addTask(t2, &project); t2->estimate()->setUnit(Duration::Unit_d); t2->estimate()->setExpectedEstimate(1.0); t2->estimate()->setType(Estimate::Type_Effort); ResourceGroup *g = new ResourceGroup(); g->setName("G1"); project.addResourceGroup(g); Resource *r = new Resource(); r->setName("R1"); r->setCalendar(calendar); project.addResource(r); r->addParentGroup(g); Resource *r2 = new Resource(); r2->setName("R2"); r2->setCalendar(cal2); project.addResource(r2); r2->addParentGroup(g); ResourceGroupRequest *gr = new ResourceGroupRequest(g); t->addRequest(gr); ResourceRequest *rr = new ResourceRequest(r, 100); - gr->addResourceRequest(rr); + t->requests().addResourceRequest(rr, gr); ResourceGroupRequest *gr2 = new ResourceGroupRequest(g); t2->addRequest(gr2); ResourceRequest *rr2 = new ResourceRequest(r2, 100); - gr2->addResourceRequest(rr2); + t2->requests().addResourceRequest(rr2, gr2); QString s = "Calculate forward, Task: ASAP -----------------------------------"; qDebug()<createSchedules(); project.calculate(*sm); Debug::print(&project, t, s); // Debug::printSchedulingLog(*sm, s); QCOMPARE(t->startTime(), DateTime(today, time1)); QCOMPARE(t->endTime(), t->startTime() + Duration(0, 8, 0)); QCOMPARE(t->plannedEffort().toHours(), 8.0); QVERIFY(t->schedulingError() == false); QCOMPARE(t2->startTime(), DateTime(today, time1.addSecs(-3600))); QCOMPARE(t2->endTime(), t2->startTime() + Duration(0, 8, 0)); QCOMPARE(t2->plannedEffort().toHours(), 8.0); QVERIFY(t2->schedulingError() == false); unsetenv("TZ"); } } //namespace KPlato QTEST_GUILESS_MAIN(KPlato::ProjectTester) diff --git a/src/libs/kernel/tests/debug.cpp b/src/libs/kernel/tests/debug.cpp index acfacec9..4e6817a4 100644 --- a/src/libs/kernel/tests/debug.cpp +++ b/src/libs/kernel/tests/debug.cpp @@ -1,415 +1,418 @@ /* This file is part of the KDE project - Copyright (C) 2009, 2010 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. -*/ + * Copyright (C) 2009, 2010 Dag Andersen + * Copyright (C) 2016 Dag Andersen + * Copyright (C) 2019 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 "kptappointment.h" #include "kptcalendar.h" #include "kptdatetime.h" #include "kptproject.h" #include "kptresource.h" #include "kptnode.h" #include "kpttask.h" #include "kptschedule.h" #include #include #include namespace QTest { template<> char *toString(const KPlato::DateTime &dt) { QString s; switch (dt.timeSpec()) { case Qt::LocalTime: s = " LocalTime"; break; case Qt::UTC: s = " UTC"; break; case Qt::OffsetFromUTC: s = " OffsetFromUTC"; break; case Qt::TimeZone: s = " TimeZone (" + dt.timeZone().id() + ')'; break; } return toString(QString("%1T%2 %3").arg(dt.date().toString(Qt::ISODate), dt.time().toString("hh:mm:ss.zzz"), s)); } template<> char *toString(const KPlato::Duration &d) { return toString(d.toString()); } } namespace KPlato { class Debug { public: Debug() {} static void print(Calendar *c, const QString &str, bool full = true) { Q_UNUSED(full); QTimeZone tz = c->timeZone(); QString s = tz.isValid() ? QString::fromLatin1(tz.id()) : QStringLiteral("LocalTime"); qDebug()<<"Debug info: Calendar"<name()<weekday(wd); qDebug()<<" "<stateToString(d->state()); foreach (TimeInterval *t, d->timeIntervals()) { qDebug()<<" interval:"<first<second<<'('<second)).toString()<<')'; } } foreach (const CalendarDay *d, c->days()) { qDebug()<<" "<date()<<':'; foreach (TimeInterval *t, d->timeIntervals()) { qDebug()<<" interval:"<first<second; } } } static QString printAvailable(Resource *r, const QString &lead = QString()) { QStringList sl; sl<availableFrom().isValid() ? r->availableFrom().toString() : (r->project() ? ('('+r->project()->constraintStartTime().toString()+')') : QString())) <<(r->availableUntil().isValid() ? r->availableUntil().toString() : (r->project() ? ('('+r->project()->constraintEndTime().toString()+')') : QString())) <units())<<"%" <<"cost: normal"<normalRate())<<" overtime"<overtimeRate()); return sl.join(" "); } static void print(ResourceGroup *g, const QString &str, bool full = true) { qDebug()<<"Debug info: Group"<name()<numResources()<<"id:"<id()<resources()) { qDebug()<<" "<name()<id(); } } static void print(Resource *r, const QString &str, bool full = true) { qDebug()<<"Debug info: Resource"<name()<<"id:"<id()<<(void*)r<parentGroups().count(); for (ResourceGroup *g : r->parentGroups()) { qDebug()<<" "<name() + " Type: " + g->typeToString() << "id:"<id(); } qDebug()<<" Available:" <<(r->availableFrom().isValid() ? r->availableFrom().toString() : (r->project() ? ('('+r->project()->constraintStartTime().toString()+')') : QString())) <<(r->availableUntil().isValid() ? r->availableUntil().toString() : (r->project() ? ('('+r->project()->constraintEndTime().toString()+')') : QString())) <units()<<'%'; qDebug()<<" Type:"<typeToString(); if (r->type() == Resource::Type_Team) { qDebug()<<" Team members:"<teamMembers().count(); foreach (Resource *tm, r->teamMembers()) { qDebug()<<" "<name()<<"Available:" <<(r->availableFrom().isValid() ? r->availableFrom().toString() : (r->project() ? ('('+r->project()->constraintStartTime().toString()+')') : QString())) <<(r->availableUntil().isValid() ? r->availableUntil().toString() : (r->project() ? ('('+r->project()->constraintEndTime().toString()+')') : QString())) <units()<<'%'; } } else { Calendar *cal = r->calendar(true); QString s; if (cal) { s = cal->name(); } else { cal = r->calendar(false); if (cal) { s = cal->name() + " (Default)"; } else { s = " No calendar"; } } qDebug()<<" Calendar:"<requiredResources().isEmpty()) { qDebug()<<" No required resources"; } else { qDebug()<<" Required resources:"<requiredResources().count()<<'('<requiredIds().count()<<')'; for (Resource *req : r->requiredResources()) { qDebug()<<" "<name()<type()<id()<<(void*)req<requiredIds().contains(req->id()); } } if (! full) return; qDebug()<<" External appointments:"<numExternalAppointments(); foreach (Appointment *a, r->externalAppointmentList()) { qDebug()<<" appointment:"<startTime().toString()<endTime().toString(); foreach(const AppointmentInterval &i, a->intervals().map()) { qDebug()<<" "<name()<constraintStartTime())); qDebug()<<"project target end :"<constraintEndTime())); if (p->isScheduled()) { qDebug()<<" project start time:"<startTime())); qDebug()<<" project end time :"<endTime())); } else { qDebug()<<" Not scheduled"; } - + qDebug()<<" Default calendar:"<<(p->defaultCalendar()?p->defaultCalendar()->name():QString("None")); if (! all) { return; } + if (p->resourceGroups().isEmpty()) { qDebug()<<" No resource groups"; } else { for (ResourceGroup *g : p->resourceGroups()) { qDebug(); print(g, "", true); } } if (p->resourceList().isEmpty()) { qDebug()<<" No resources"; } else { for (Resource *r : p->resourceList()) { qDebug(); print(r, "", true); } } for (int i = 0; i < p->numChildren(); ++i) { qDebug(); print(static_cast(p->childNode(i)), true); } } static void print(Project *p, Task *t, const QString &str, bool full = true) { Q_UNUSED(full); print(p, str); print(t); } static void print(Task *t, const QString &str, bool full = true) { qDebug()<<"Debug info: Task"<name()<level() > 0) { pad = QString("%1").arg("", t->level()*2, ' '); } - qDebug()<wbsCode()<name()<typeToString()<constraintToString(); + qDebug()<wbsCode()<name()<typeToString()<constraintToString()<<(void*)t; if (t->isScheduled()) { qDebug()<earlyStart())); qDebug()<lateStart())); qDebug()<earlyFinish())); qDebug()<lateFinish())); qDebug()<startTime())); qDebug()<endTime())); } else { qDebug()<constraint()) { case Node::MustStartOn: case Node::StartNotEarlier: qDebug()<constraintStartTime())); break; case Node::FixedInterval: qDebug()<constraintStartTime())); case Node::MustFinishOn: case Node::FinishNotLater: qDebug()<constraintEndTime())); break; default: break; } qDebug()<estimate()->expectedEstimate()<estimate()->unit()) <estimate()->typeToString() <<(t->estimate()->type() == Estimate::Type_Duration ? (t->estimate()->calendar()?t->estimate()->calendar()->name():"Fixed") : QString("%1 h").arg(t->estimate()->expectedValue().toDouble(Duration::Unit_h))); + qDebug()<requests().requests().count()<<"resources:"<requests().resourceRequests().count(); foreach (ResourceGroupRequest *gr, t->requests().requests()) { - qDebug()<group()->name()<units(); + qDebug()<group()->name()<units()<<':'<<(void*)gr; foreach (ResourceRequest *rr, gr->resourceRequests()) { - qDebug()<resource(), " " + rr->resource()->name())<<"id:"<resource()->id()<<(void*)rr->resource(); + qDebug()<resource(), " " + rr->resource()->name())<<"id:"<resource()->id()<<(void*)rr->resource()<<':'<<(void*)rr; } } if (t->isStartNode()) { qDebug()<dependChildNodes()) { QString type; switch(r->type()) { case Relation::StartStart: type = "SS"; break; case Relation::FinishFinish: type = "FF"; break; default: type = "FS"; break; } rel << QString("(%1) -> %2, %3 %4").arg(r->parent()->name()).arg(r->child()->name()).arg(type).arg(r->lag() == 0?QString():r->lag().toString(Duration::Format_HourFraction)); } if (!rel.isEmpty()) { qDebug()<isEndNode()) { qDebug()<dependParentNodes()) { QString type; switch(r->type()) { case Relation::StartStart: type = "SS"; break; case Relation::FinishFinish: type = "FF"; break; default: type = "FS"; break; } rel << QString("%1 -> (%2), %3 %4").arg(r->parent()->name()).arg(r->child()->name()).arg(type).arg(r->lag() == 0?QString():r->lag().toString(Duration::Format_HourFraction)); } if (!rel.isEmpty()) { qDebug()<currentSchedule(); if (s) { qDebug()<appointments().count(); foreach (Appointment *a, s->appointments()) { qDebug()<resource()->resource()->name()<<"booked:"<startTime()))<endTime()))<<"effort:"<effort(a->startTime(), a->endTime()).toDouble(Duration::Unit_h)<<'h'; if (! full) { continue; } foreach(const AppointmentInterval &i, a->intervals().map()) { qDebug()<runningAccount()) { qDebug()<runningAccount()->name(); } if (t->startupAccount()) { qDebug()<startupAccount()->name()<<" cost:"<startupCost(); } if (t->shutdownAccount()) { qDebug()<shutdownAccount()->name()<<" cost:"<shutdownCost(); } if (full) { for (int i = 0; i < t->numChildren(); ++i) { qDebug()<(t->childNode(i)), full); } } } static void print(const Completion &c, const QString &name, const QString &s = QString()) { qDebug()<<"Completion:"<actualEffortMap().keys()) { // clazy:exclude=container-anti-pattern qDebug()<<" "<name()<<':'; qDebug()<<" "<logMessages()) { qDebug()<name()<name()<<(a->isDefaultAccount() ? "Default" : ""); EffortCostMap ec = a->plannedCost(id); qDebug()<<"Planned cost:"<isElement()) { foreach (Account *c, a->accountList()) { print(c); } return; } qDebug()<<"Cost places:"; foreach (Account::CostPlace *cp, a->costPlaces()) { qDebug()<<" Node:"<<(cp->node() ? cp->node()->name() : ""); qDebug()<<" running:"<running(); qDebug()<<" startup:"<startup(); qDebug()<<" shutdown:"<shutdown(); } } static void print(const AppointmentInterval &i, const QString &indent = QString()) { QString s = indent + "Interval:"; if (! i.isValid()) { qDebug()< * * 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 "InsertProjectXmlCommand.h" #include "kptaccount.h" #include "kptappointment.h" #include "kptproject.h" #include "kpttask.h" #include "kptcalendar.h" #include "kptrelation.h" #include "kptresource.h" #include "kptdocuments.h" #include "kptlocale.h" #include "kptdebug.h" #include const QLoggingCategory &PLANCMDINSPROJECT_LOG() { static const QLoggingCategory category("calligra.plan.command.insertProjectXml"); return category; } #define debugPlanInsertProjectXml qCDebug(PLANCMDINSPROJECT_LOG) #define warnPlanInsertProjectXml qCWarning(PLANCMDINSPROJECT_LOG) #define errorPlanInsertProjectXml qCCritical(PLANCMDINSPROJECT_LOG) using namespace KPlato; AddTaskCommand::AddTaskCommand(Project *project, Node *parent, Node *node, Node *after, const KUndo2MagicString& name) : NamedCommand(name) , m_project(project) , m_parent(parent) , m_node(node) , m_after(after) , m_added(false) { } AddTaskCommand::~AddTaskCommand() { if (!m_added) delete m_node; } void AddTaskCommand::execute() { m_project->addSubTask(m_node, m_parent->indexOf(m_after), m_parent, true); m_added = true; } void AddTaskCommand::unexecute() { m_project->takeTask(m_node); m_added = false; } InsertProjectXmlCommand::InsertProjectXmlCommand(Project *project, const QByteArray &data, Node *parent, Node *position, const KUndo2MagicString& name) : MacroCommand(name) , m_project(project) , m_data(data) , m_parent(parent) , m_position(position) , m_first(true) { - //debugPlan<name(); + //debugPlanInsertProjectXml<name(); Q_ASSERT(project != 0); m_context.setProject(project); m_context.setProjectTimeZone(project->timeZone()); // from xml doc? m_context.setLoadTaskChildren(false); } InsertProjectXmlCommand::~InsertProjectXmlCommand() { } void InsertProjectXmlCommand::execute() { if (m_first) { // create and execute commands KoXmlDocument doc; doc.setContent(m_data); m_context.setVersion(doc.documentElement().attribute("plan-version", PLAN_FILE_SYNTAX_VERSION)); KoXmlElement projectElement = doc.documentElement().namedItem("project").toElement(); createCmdAccounts(projectElement); createCmdCalendars(projectElement); createCmdResources(projectElement); createCmdTasks(projectElement); createCmdRelations(projectElement); + createCmdRequests(projectElement); m_first = false; m_data.clear(); } else { MacroCommand::execute(); } } void InsertProjectXmlCommand::unexecute() { MacroCommand::unexecute(); } void InsertProjectXmlCommand::createCmdAccounts(const KoXmlElement &projectElement) { if (projectElement.isNull()) { return; } } void InsertProjectXmlCommand::createCmdCalendars(const KoXmlElement &projectElement) { if (projectElement.isNull()) { return; } } void InsertProjectXmlCommand::createCmdResources(const KoXmlElement &projectElement) { if (projectElement.isNull()) { return; } } +void InsertProjectXmlCommand::createCmdRequests(const KoXmlElement &projectElement) +{ + if (projectElement.isNull()) { + return; + } + debugPlanInsertProjectXml<(m_oldIds.value(ge.attribute("task-id"))); + ResourceGroup *group = m_project->findResourceGroup(ge.attribute("group-id")); + if (task && group) { + int units = ge.attribute("units", "0").toInt(); + int requestId = ge.attribute("request-id").toInt(); + ResourceGroupRequest *request = new ResourceGroupRequest(group, units); + request->setId(requestId); + KUndo2Command *cmd = new AddResourceGroupRequestCmd(*task, request); + cmd->redo(); + addCommand(cmd); + debugPlanInsertProjectXml<<"added grouprequest:"<(m_oldIds.value(re.attribute("task-id"))); + if (!task) { + warnPlanInsertProjectXml<requests().groupRequest(re.attribute("request-id").toInt()); + if (!group) { + warnPlanInsertProjectXml<findResource(re.attribute("resource-id")); + Q_ASSERT(resource); + Q_ASSERT(task); + if (resource && task) { + int units = re.attribute("units", "100").toInt(); + ResourceRequest *request = new ResourceRequest(resource, units); + int requestId = re.attribute("request-id").toInt(); + Q_ASSERT(requestId > 0); + request->setId(requestId); + KUndo2Command *cmd = new AddResourceRequestCmd(&task->requests(), request, group); + cmd->redo(); + addCommand(cmd); + debugPlanInsertProjectXml<<"added resourcerequest:"<(m_oldIds.value(re.attribute("task-id"))); + Q_ASSERT(task); + if (!task) { + warnPlanInsertProjectXml<requests().resourceRequest(re.attribute("request-id").toInt()); + Resource *required = m_project->findResource(re.attribute("required-id")); + QList lst; + if (required && request->resource() != required) { + lst << required; + } + KUndo2Command *cmd = new ModifyResourceRequestRequiredCmd(request, lst); + cmd->redo(); + addCommand(cmd); + debugPlanInsertProjectXml<<"added requiredrequest:"<createTask(); QString id = task->id(); task->load(taskElement, m_context); m_oldIds.insert(task->id(), task); task->setId(id); NamedCommand *cmd = new AddTaskCommand(m_project, parent, task, position); cmd->execute(); addCommand(cmd); createCmdTask(taskElement, task); // add children } } void InsertProjectXmlCommand::createCmdRelations(const KoXmlElement &projectElement) { if (projectElement.isNull()) { return; } KoXmlElement relationElement; forEachElement(relationElement, projectElement) { if (relationElement.tagName() != "relation") { continue; } Node *parent = m_oldIds.value(relationElement.attribute("parent-id")); Node *child = m_oldIds.value(relationElement.attribute("child-id")); if (parent && child) { Relation *relation = new Relation(parent, child); relation->setType(relationElement.attribute("type")); relation->setLag(Duration::fromString(relationElement.attribute("lag"))); AddRelationCmd *cmd = new AddRelationCmd(*m_project, relation); cmd->execute(); addCommand(cmd); } } } diff --git a/src/libs/models/commands/InsertProjectXmlCommand.h b/src/libs/models/commands/InsertProjectXmlCommand.h index 762973f4..8bf9f600 100644 --- a/src/libs/models/commands/InsertProjectXmlCommand.h +++ b/src/libs/models/commands/InsertProjectXmlCommand.h @@ -1,86 +1,87 @@ /* This file is part of the KDE project * Copyright (C) 2019 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 INSERTPROJECTXMLCOMMAND_H #define INSERTPROJECTXMLCOMMAND_H #include "planmodels_export.h" #include "kptcommand.h" #include "kptxmlloaderobject.h" #include "KoXmlReader.h" #include class QString; /// The main namespace namespace KPlato { class Project; class Node; class PLANMODELS_EXPORT AddTaskCommand : public NamedCommand { public: AddTaskCommand(Project *project, Node *parent, Node *node, Node *after, const KUndo2MagicString& name = KUndo2MagicString()); ~AddTaskCommand() override; void execute() override; void unexecute() override; private: Project *m_project; Node *m_parent; Node *m_node; Node *m_after; bool m_added; }; class PLANMODELS_EXPORT InsertProjectXmlCommand : public MacroCommand { public: InsertProjectXmlCommand(Project *project, const QByteArray &data, Node *parent, Node *position, const KUndo2MagicString& name = KUndo2MagicString()); ~InsertProjectXmlCommand() override; void execute() override; void unexecute() override; private: void createCmdAccounts(const KoXmlElement &projectElement); void createCmdCalendars(const KoXmlElement &projectElement); void createCmdResources(const KoXmlElement &projectElement); void createCmdTasks(const KoXmlElement &projectElement); void createCmdTask(const KoXmlElement &parentElement, Node *parent, Node *position = nullptr); void createCmdRelations(const KoXmlElement &projectElement); + void createCmdRequests(const KoXmlElement &projectElement); private: Project *m_project; QByteArray m_data; Node *m_parent; Node *m_position; bool m_first; XMLLoaderObject m_context; QHash m_oldIds; // QHash }; } //KPlato namespace #endif diff --git a/src/libs/models/commands/InsertTaskModuleCommand.cpp b/src/libs/models/commands/InsertTaskModuleCommand.cpp index d4e8813c..6a0a988f 100644 --- a/src/libs/models/commands/InsertTaskModuleCommand.cpp +++ b/src/libs/models/commands/InsertTaskModuleCommand.cpp @@ -1,220 +1,304 @@ /* This file is part of the KDE project * Copyright (C) 2019 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 "InsertTaskModuleCommand.h" #include "kptaccount.h" #include "kptappointment.h" #include "kptproject.h" #include "kpttask.h" #include "kptcalendar.h" #include "kptrelation.h" #include "kptresource.h" #include "kptdocuments.h" #include "kptlocale.h" #include "kptdebug.h" #include const QLoggingCategory &PLANCMDINSTASKMODULE_LOG() { static const QLoggingCategory category("calligra.plan.command.inserttaskmodule"); return category; } #define debugPlanInsertTaskModule qCDebug(PLANCMDINSTASKMODULE_LOG) #define warnPlanInsertTaskModule qCWarning(PLANCMDINSTASKMODULE_LOG) #define errorPlanInsertTaskModule qCCritical(PLANCMDINSTASKMODULE_LOG) using namespace KPlato; class AddTaskCommand : public NamedCommand { public: AddTaskCommand(Project *project, Node *parent, Node *node, Node *after, const KUndo2MagicString& name = KUndo2MagicString()); ~AddTaskCommand() override; void execute() override; void unexecute() override; private: Project *m_project; Node *m_parent; Node *m_node; Node *m_after; bool m_added; }; AddTaskCommand::AddTaskCommand(Project *project, Node *parent, Node *node, Node *after, const KUndo2MagicString& name) : NamedCommand(name) , m_project(project) , m_parent(parent) , m_node(node) , m_after(after) , m_added(false) { } AddTaskCommand::~AddTaskCommand() { if (!m_added) delete m_node; } void AddTaskCommand::execute() { m_project->addSubTask(m_node, m_parent->indexOf(m_after), m_parent, true); m_added = true; } void AddTaskCommand::unexecute() { m_project->takeTask(m_node); m_added = false; } InsertTaskModuleCommand::InsertTaskModuleCommand(Project *project, const QByteArray &data, Node *parent, Node *position, const QMap substitute, const KUndo2MagicString& name) : MacroCommand(name) , m_project(project) , m_data(data) , m_parent(parent) , m_position(position) , m_substitute(substitute) , m_first(true) { //debugPlan<name(); Q_ASSERT(project != 0); m_context.setProject(project); m_context.setProjectTimeZone(project->timeZone()); // from xml doc? m_context.setLoadTaskChildren(false); } InsertTaskModuleCommand::~InsertTaskModuleCommand() { } void InsertTaskModuleCommand::execute() { if (m_first) { // create and execute commands KoXmlDocument doc; doc.setContent(m_data); m_context.setVersion(doc.documentElement().attribute("plan-version", PLAN_FILE_SYNTAX_VERSION)); KoXmlElement projectElement = doc.documentElement().namedItem("project").toElement(); createCmdAccounts(projectElement); createCmdCalendars(projectElement); createCmdResources(projectElement); createCmdTasks(projectElement); createCmdRelations(projectElement); + createCmdRequests(projectElement); m_first = false; m_data.clear(); } else { MacroCommand::execute(); } } void InsertTaskModuleCommand::unexecute() { MacroCommand::unexecute(); } void InsertTaskModuleCommand::createCmdAccounts(const KoXmlElement &projectElement) { if (projectElement.isNull()) { return; } } void InsertTaskModuleCommand::createCmdCalendars(const KoXmlElement &projectElement) { if (projectElement.isNull()) { return; } } void InsertTaskModuleCommand::createCmdResources(const KoXmlElement &projectElement) { if (projectElement.isNull()) { return; } } +void InsertTaskModuleCommand::createCmdRequests(const KoXmlElement &projectElement) +{ + if (projectElement.isNull()) { + return; + } + debugPlanXml<(m_oldIds.value(ge.attribute("task-id"))); + ResourceGroup *group = m_project->findResourceGroup(ge.attribute("group-id")); + if (task && group) { + int units = ge.attribute("units", "0").toInt(); + int requestId = ge.attribute("request-id").toInt(); + ResourceGroupRequest *request = new ResourceGroupRequest(group, units); + request->setId(requestId); + KUndo2Command *cmd = new AddResourceGroupRequestCmd(*task, request); + cmd->redo(); + addCommand(cmd); + } else { + warnPlanXml<<"Failed to find group or task"<(m_oldIds.value(ge.attribute("task-id"))); + if (!task) { + warnPlanXml<requests().groupRequest(re.attribute("request-id").toInt()); + if (!group) { + warnPlanXml<findResource(re.attribute("resource-id")); + Q_ASSERT(resource); + Q_ASSERT(task); + if (resource && task) { + int units = re.attribute("units", "100").toInt(); + ResourceRequest *request = new ResourceRequest(resource, units); + int requestId = re.attribute("request-id").toInt(); + Q_ASSERT(requestId > 0); + request->setId(requestId); + KUndo2Command *cmd = new AddResourceRequestCmd(&task->requests(), request); + cmd->redo(); + addCommand(cmd); + } else { + warnPlanXml<(m_oldIds.value(ge.attribute("task-id"))); + Q_ASSERT(task); + if (!task) { + warnPlanXml<requests().resourceRequest(re.attribute("request-id").toInt()); + Resource *required = m_project->findResource(re.attribute("required-id")); + QList lst; + if (required && request->resource() != required) { + lst << required; + } + KUndo2Command *cmd = new ModifyResourceRequestRequiredCmd(request, lst); + cmd->redo(); + addCommand(cmd); + } +} + void InsertTaskModuleCommand::createCmdTasks(const KoXmlElement &projectElement) { if (projectElement.isNull()) { return; } createCmdTask(projectElement, m_parent, m_position); } void InsertTaskModuleCommand::createCmdTask(const KoXmlElement &parentElement, Node *parent, Node *position) { KoXmlElement taskElement; forEachElement(taskElement, parentElement) { if (taskElement.tagName() != "task") { continue; } Task *task = m_project->createTask(); QString id = task->id(); task->load(taskElement, m_context); m_oldIds.insert(task->id(), task); task->setId(id); if (!m_substitute.isEmpty()) { substitute(task->name()); } NamedCommand *cmd = new AddTaskCommand(m_project, parent, task, position); cmd->execute(); addCommand(cmd); createCmdTask(taskElement, task); // add children } } void InsertTaskModuleCommand::createCmdRelations(const KoXmlElement &projectElement) { if (projectElement.isNull()) { return; } KoXmlElement relationElement; forEachElement(relationElement, projectElement) { if (relationElement.tagName() != "relation") { continue; } Node *parent = m_oldIds.value(relationElement.attribute("parent-id")); Node *child = m_oldIds.value(relationElement.attribute("child-id")); if (parent && child) { Relation *relation = new Relation(parent, child); relation->setType(relationElement.attribute("type")); relation->setLag(Duration::fromString(relationElement.attribute("lag"))); AddRelationCmd *cmd = new AddRelationCmd(*m_project, relation); cmd->execute(); addCommand(cmd); } } } void InsertTaskModuleCommand::substitute(QString &text) { QMap::const_iterator it = m_substitute.constBegin(); for (; it != m_substitute.constEnd(); ++it) { text.replace("[[" + it.key() + "]]", it.value()); } } diff --git a/src/libs/models/commands/InsertTaskModuleCommand.h b/src/libs/models/commands/InsertTaskModuleCommand.h index 082d6776..e4ee4bde 100644 --- a/src/libs/models/commands/InsertTaskModuleCommand.h +++ b/src/libs/models/commands/InsertTaskModuleCommand.h @@ -1,74 +1,75 @@ /* This file is part of the KDE project * Copyright (C) 2019 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 INSERTTASKMODULECOMMAND_H #define INSERTTASKMODULECOMMAND_H #include "planmodels_export.h" #include "kptcommand.h" #include "kptxmlloaderobject.h" #include "KoXmlReader.h" #include class QString; /// The main namespace namespace KPlato { class Project; class Node; class PLANMODELS_EXPORT InsertTaskModuleCommand : public MacroCommand { public: InsertTaskModuleCommand(Project *project, const QByteArray &data, Node *parent, Node *position, const QMap substitute, const KUndo2MagicString& name = KUndo2MagicString()); ~InsertTaskModuleCommand() override; void execute() override; void unexecute() override; private: void createCmdAccounts(const KoXmlElement &projectElement); void createCmdCalendars(const KoXmlElement &projectElement); void createCmdResources(const KoXmlElement &projectElement); void createCmdTasks(const KoXmlElement &projectElement); void createCmdTask(const KoXmlElement &parentElement, Node *parent, Node *position = nullptr); void createCmdRelations(const KoXmlElement &projectElement); + void createCmdRequests(const KoXmlElement &projectElement); void substitute(QString &text); private: Project *m_project; QByteArray m_data; Node *m_parent; Node *m_position; QMap m_substitute; bool m_first; XMLLoaderObject m_context; QHash m_oldIds; // QHash }; } //KPlato namespace #endif diff --git a/src/libs/models/kptnodeitemmodel.cpp b/src/libs/models/kptnodeitemmodel.cpp index 2bdceb26..b20fb0c7 100644 --- a/src/libs/models/kptnodeitemmodel.cpp +++ b/src/libs/models/kptnodeitemmodel.cpp @@ -1,5354 +1,5354 @@ /* This file is part of the KDE project * Copyright (C) 2007 - 2009, 2012 Dag Andersen * Copyright (C) 2016 Dag Andersen * Copyright (C) 2019 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 "kptnodeitemmodel.h" #include "kptglobal.h" #include "kptlocale.h" #include "kptcommonstrings.h" #include "kptcommand.h" #include #include "kptduration.h" #include "kptproject.h" #include "kptnode.h" #include "kpttaskcompletedelegate.h" #include "kptxmlloaderobject.h" #include "XmlSaveContext.h" #include "InsertProjectXmlCommand.h" #include "InsertTaskModuleCommand.h" #include "kptdebug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace KPlato { //-------------------------------------- NodeModel::NodeModel() : QObject(), m_project(0), m_manager(0), m_now(QDate::currentDate()), m_prec(1) { } const QMetaEnum NodeModel::columnMap() const { return metaObject()->enumerator(metaObject()->indexOfEnumerator("Properties")); } void NodeModel::setProject(Project *project) { debugPlan<"<"<name(); case Qt::ToolTipRole: { QTextEdit w(node->description(), nullptr); QString description = w.toPlainText(); if (description.length() > 200) { description = description.left(200) + " ..."; description.replace('\n', "
"); } else { description = node->description(); } w.setHtml(i18n("

%1: %2

%3

", node->wbsCode(), node->name(), description)); return w.toHtml(); } case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); case Qt::DecorationRole: if (node->isBaselined()) { return koIcon("view-time-schedule-baselined"); } break; case Role::Foreground: { if (! m_project) { break; } switch (node->type()) { case Node::Type_Task: return static_cast(node)->completion().isFinished() ? m_project->config().taskFinishedColor() : m_project->config().taskNormalColor(); case Node::Type_Milestone: return static_cast(node)->completion().isFinished() ? m_project->config().milestoneFinishedColor() : m_project->config().milestoneNormalColor(); case Node::Type_Summarytask: return m_project->config().summaryTaskLevelColor(node->level()); default: break; } break; } } return QVariant(); } QVariant NodeModel::leader(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: case Qt::EditRole: case Qt::ToolTipRole: return node->leader(); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::allocation(const Node *node, int role) const { if (node->type() == Node::Type_Task) { switch (role) { case Qt::DisplayRole: case Qt::EditRole: return node->requests().requestNameList().join(","); case Qt::ToolTipRole: { QMap lst; foreach (ResourceRequest *rr, node->requests().resourceRequests(false)) { QStringList sl; foreach(Resource *r, rr->requiredResources()) { sl << r->name(); } lst.insert(rr->resource()->name(), sl); } if (lst.isEmpty()) { return xi18nc("@info:tooltip", "No resources has been allocated"); } QStringList sl; for (QMap::ConstIterator it = lst.constBegin(); it != lst.constEnd(); ++it) { if (it.value().isEmpty()) { sl << it.key(); } else { sl << xi18nc("@info:tooltip 1=resource name, 2=list of required resources", "%1 (%2)", it.key(), it.value().join(", ")); } } if (sl.count() == 1) { return xi18nc("@info:tooltip 1=resource name", "Allocated resource:%1", sl.first()); } KLocalizedString ks = kxi18nc("@info:tooltip 1=list of resources", "Allocated resources:%1"); // Hack to get around ks escaping '<' and '>' QString s = ks.subs(sl.join("#¤#")).toString(); return s.replace("#¤#", "
"); } case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } } return QVariant(); } QVariant NodeModel::description(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: { KRichTextWidget w(node->description(), 0); w.switchToPlainText(); QString s = w.textOrHtml(); s.remove('\r'); return s.replace('\n', ' '); } case Qt::ToolTipRole: { KRichTextWidget w(node->description(), 0); w.switchToPlainText(); if (w.textOrHtml().isEmpty()) { return QVariant(); } return node->description(); } case Qt::EditRole: return node->description(); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::type(const Node *node, int role) const { //debugPlan<name()<<", "<typeToString(true); case Qt::EditRole: return node->type(); case Qt::TextAlignmentRole: return (int)(Qt::AlignLeft|Qt::AlignVCenter); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::constraint(const Node *node, int role) const { if (node->type() == Node::Type_Project) { switch (role) { case Qt::DisplayRole: return i18n("Target times"); case Qt::ToolTipRole: return xi18nc("@info:tooltip", "Earliest start and latest finish"); case Role::EnumList: case Qt::EditRole: case Role::EnumListValue: return QVariant(); case Qt::TextAlignmentRole: return Qt::AlignCenter; case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } } else if (node->type() != Node::Type_Summarytask) { switch (role) { case Qt::DisplayRole: case Qt::ToolTipRole: return node->constraintToString(true); case Role::EnumList: return Node::constraintList(true); case Qt::EditRole: return node->constraint(); case Role::EnumListValue: return (int)node->constraint(); case Qt::TextAlignmentRole: return Qt::AlignCenter; case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } } return QVariant(); } QVariant NodeModel::constraintStartTime(const Node *node, int role) const { if (node->type() == Node::Type_Project) { switch (role) { case Qt::DisplayRole: { return QLocale().toString(node->constraintStartTime(), QLocale::ShortFormat); } case Qt::ToolTipRole: { return QLocale().toString(node->constraintStartTime(), QLocale::LongFormat); } case Qt::EditRole: return node->constraintStartTime(); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } else if (node->type() != Node::Type_Summarytask) { switch (role) { case Qt::DisplayRole: { QString s = QLocale().toString(node->constraintStartTime(), QLocale::ShortFormat); switch (node->constraint()) { case Node::StartNotEarlier: case Node::MustStartOn: case Node::FixedInterval: return s; default: break; } return QString("(%1)").arg(s); } case Qt::ToolTipRole: { int c = node->constraint(); if (c == Node::MustStartOn || c == Node::StartNotEarlier || c == Node::FixedInterval) { return QLocale().toString(node->constraintStartTime(), QLocale::LongFormat); } break; } case Qt::EditRole: return node->constraintStartTime(); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } } return QVariant(); } QVariant NodeModel::constraintEndTime(const Node *node, int role) const { if (node->type() == Node::Type_Project) { switch (role) { case Qt::DisplayRole: { return QLocale().toString(node->constraintEndTime(), QLocale::ShortFormat); } case Qt::ToolTipRole: { return QLocale().toString(node->constraintEndTime(), QLocale::LongFormat); } case Qt::EditRole: return node->constraintEndTime(); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } else if (node->type() != Node::Type_Summarytask) { switch (role) { case Qt::DisplayRole: { QString s = QLocale().toString(node->constraintEndTime(), QLocale::ShortFormat); switch (node->constraint()) { case Node::FinishNotLater: case Node::MustFinishOn: case Node::FixedInterval: return s; default: break; } return QString("(%1)").arg(s); } case Qt::ToolTipRole: { int c = node->constraint(); if (c == Node::FinishNotLater || c == Node::MustFinishOn || c == Node::FixedInterval) { return QLocale().toString(node->constraintEndTime(), QLocale::LongFormat); } break; } case Qt::EditRole: return node->constraintEndTime(); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } } return QVariant(); } QVariant NodeModel::estimateType(const Node *node, int role) const { if (node->estimate() == 0) { return QVariant(); } switch (role) { case Qt::DisplayRole: case Qt::ToolTipRole: if (node->type() == Node::Type_Task) { return node->estimate()->typeToString(true); } return QString(); case Role::EnumList: return Estimate::typeToStringList(true); case Qt::EditRole: if (node->type() == Node::Type_Task) { return node->estimate()->typeToString(); } return QString(); case Role::EnumListValue: return (int)node->estimate()->type(); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::estimateCalendar(const Node *node, int role) const { if (node->estimate() == 0) { return QVariant(); } switch (role) { case Qt::DisplayRole: if (node->type() == Node::Type_Task) { if (node->estimate()->calendar()) { return node->estimate()->calendar()->name(); } return i18n("None"); } return QString(); case Qt::ToolTipRole: if (node->type() == Node::Type_Task) { if (node->estimate()->type() == Estimate::Type_Effort) { return xi18nc("@info:tooltip", "Not applicable, estimate type is Effort"); } if (node->estimate()->calendar()) { return node->estimate()->calendar()->name(); } return QVariant(); } return QString(); case Role::EnumList: { QStringList lst; lst << i18n("None"); const Node *n = const_cast(node)->projectNode(); if (n) { lst += static_cast(n)->calendarNames(); } return lst; } case Qt::EditRole: if (node->type() == Node::Type_Task) { if (node->estimate()->calendar() == 0) { return i18n("None"); } return node->estimate()->calendar()->name(); } return QString(); case Role::EnumListValue: { if (node->estimate()->calendar() == 0) { return 0; } QStringList lst; const Node *n = const_cast(node)->projectNode(); if (n) { lst = static_cast(n)->calendarNames(); } return lst.indexOf(node->estimate()->calendar()->name()) + 1; } case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::estimate(const Node *node, int role) const { if (node->estimate() == 0) { return QVariant(); } switch (role) { case Qt::DisplayRole: if (node->type() == Node::Type_Task || node->type() == Node::Type_Milestone) { Duration::Unit unit = node->estimate()->unit(); QString s = QLocale().toString(node->estimate()->expectedEstimate(), 'f', m_prec) + Duration::unitToString(unit, true); if (node->constraint() == Node::FixedInterval && node->estimate()->type() == Estimate::Type_Duration) { s = '(' + s + ')'; } return s; } break; case Qt::ToolTipRole: if (node->type() == Node::Type_Task) { Duration::Unit unit = node->estimate()->unit(); QString s = QLocale().toString(node->estimate()->expectedEstimate(), 'f', m_prec) + Duration::unitToString(unit, true); Estimate::Type t = node->estimate()->type(); if (node->constraint() == Node::FixedInterval && t == Estimate::Type_Duration) { s = xi18nc("@info:tooltip", "Not applicable, constraint is Fixed Interval"); } else if (t == Estimate::Type_Effort) { s = xi18nc("@info:tooltip", "Estimated effort: %1", s); } else { s = xi18nc("@info:tooltip", "Estimated duration: %1", s); } return s; } break; case Qt::EditRole: return node->estimate()->expectedEstimate(); case Role::DurationUnit: return static_cast(node->estimate()->unit()); case Role::Minimum: return m_project->config().minimumDurationUnit(); case Role::Maximum: return m_project->config().maximumDurationUnit(); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::optimisticRatio(const Node *node, int role) const { if (node->estimate() == 0 || node->type() == Node::Type_Summarytask || node->type() == Node::Type_Milestone) { return QVariant(); } switch (role) { case Qt::DisplayRole: if (node->type() == Node::Type_Task && node->constraint() == Node::FixedInterval && node->estimate()->type() == Estimate::Type_Duration) { QString s = QString::number(node->estimate()->optimisticRatio()); s = '(' + s + ')'; return s; } if (node->estimate()) { return node->estimate()->optimisticRatio(); } break; case Qt::EditRole: if (node->estimate()) { return node->estimate()->optimisticRatio(); } break; case Qt::ToolTipRole: if (node->type() == Node::Type_Task) { Duration::Unit unit = node->estimate()->unit(); QString s = QLocale().toString(node->estimate()->optimisticEstimate(), 'f', m_prec) + Duration::unitToString(unit, true); Estimate::Type t = node->estimate()->type(); if (node->constraint() == Node::FixedInterval && t == Estimate::Type_Duration) { s = xi18nc("@info:tooltip", "Not applicable, constraint is Fixed Interval"); } else if (t == Estimate::Type_Effort) { s = xi18nc("@info:tooltip", "Optimistic effort: %1", s); } else { s = xi18nc("@info:tooltip", "Optimistic duration: %1", s); } return s; } break; case Role::Minimum: return -99; case Role::Maximum: return 0; case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::pessimisticRatio(const Node *node, int role) const { if (node->estimate() == 0 || node->type() == Node::Type_Summarytask || node->type() == Node::Type_Milestone) { return QVariant(); } switch (role) { case Qt::DisplayRole: if (node->type() == Node::Type_Task && node->constraint() == Node::FixedInterval && node->estimate()->type() == Estimate::Type_Duration) { QString s = QString::number(node->estimate()->pessimisticRatio()); s = '(' + s + ')'; return s; } if (node->estimate()) { return node->estimate()->pessimisticRatio(); } break; case Qt::EditRole: if (node->estimate()) { return node->estimate()->pessimisticRatio(); } break; case Qt::ToolTipRole: if (node->type() == Node::Type_Task) { Duration::Unit unit = node->estimate()->unit(); QString s = QLocale().toString(node->estimate()->pessimisticEstimate(), 'f', m_prec) + Duration::unitToString(unit, true); Estimate::Type t = node->estimate()->type(); if (node->constraint() == Node::FixedInterval && t == Estimate::Type_Duration) { s = xi18nc("@info:tooltip", "Not applicable, constraint is Fixed Interval"); } else if (t == Estimate::Type_Effort) { s = xi18nc("@info:tooltip", "Pessimistic effort: %1", s); } else { s = xi18nc("@info:tooltip", "Pessimistic duration: %1", s); } return s; } break; case Role::Minimum: return 0; case Role::Maximum: return INT_MAX; case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::riskType(const Node *node, int role) const { if (node->estimate() == 0) { return QVariant(); } switch (role) { case Qt::DisplayRole: case Qt::ToolTipRole: if (node->type() == Node::Type_Task) { return node->estimate()->risktypeToString(true); } return QString(); case Role::EnumList: return Estimate::risktypeToStringList(true); case Qt::EditRole: if (node->type() == Node::Type_Task) { return node->estimate()->risktypeToString(); } return QString(); case Role::EnumListValue: return (int)node->estimate()->risktype(); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::priority(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: case Qt::EditRole: return node->priority(); case Qt::ToolTipRole: break; case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::runningAccount(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: if (node->type() == Node::Type_Task) { Account *a = node->runningAccount(); return a == 0 ? i18n("None") : a->name(); } break; case Qt::ToolTipRole: if (node->type() == Node::Type_Task) { Account *a = node->runningAccount(); return a ? xi18nc("@info:tooltip", "Account for resource cost: %1", a->name()) : xi18nc("@info:tooltip", "Account for resource cost"); } break; case Role::EnumListValue: case Qt::EditRole: { Account *a = node->runningAccount(); return a == 0 ? 0 : (m_project->accounts().costElements().indexOf(a->name()) + 1); } case Role::EnumList: { QStringList lst; lst << i18n("None"); lst += m_project->accounts().costElements(); return lst; } case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::startupAccount(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: if (node->type() == Node::Type_Task || node->type() == Node::Type_Milestone) { Account *a = node->startupAccount(); //debugPlan<name()<<": "<name(); } break; case Qt::ToolTipRole: if (node->type() == Node::Type_Task || node->type() == Node::Type_Milestone) { Account *a = node->startupAccount(); //debugPlan<name()<<": "<name()) : xi18nc("@info:tooltip", "Account for task startup cost"); } break; case Role::EnumListValue: case Qt::EditRole: { Account *a = node->startupAccount(); return a == 0 ? 0 : (m_project->accounts().costElements().indexOf(a->name()) + 1); } case Role::EnumList: { QStringList lst; lst << i18n("None"); lst += m_project->accounts().costElements(); return lst; } case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::startupCost(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: case Qt::ToolTipRole: if (node->type() == Node::Type_Task || node->type() == Node::Type_Milestone) { return m_project->locale()->formatMoney(node->startupCost()); } break; case Qt::EditRole: return node->startupCost(); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::shutdownAccount(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: if (node->type() == Node::Type_Task || node->type() == Node::Type_Milestone) { Account *a = node->shutdownAccount(); return a == 0 ? i18n("None") : a->name(); } break; case Qt::ToolTipRole: if (node->type() == Node::Type_Task || node->type() == Node::Type_Milestone) { Account *a = node->shutdownAccount(); return a ? xi18nc("@info:tooltip", "Account for task shutdown cost: %1", a->name()) : xi18nc("@info:tooltip", "Account for task shutdown cost"); } break; case Role::EnumListValue: case Qt::EditRole: { Account *a = node->shutdownAccount(); return a == 0 ? 0 : (m_project->accounts().costElements().indexOf(a->name()) + 1); } case Role::EnumList: { QStringList lst; lst << i18n("None"); lst += m_project->accounts().costElements(); return lst; } case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::shutdownCost(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: case Qt::ToolTipRole: if (node->type() == Node::Type_Task || node->type() == Node::Type_Milestone) { return m_project->locale()->formatMoney(node->shutdownCost()); } break; case Qt::EditRole: return node->shutdownCost(); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::startTime(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: return QLocale().toString(node->startTime(id()), QLocale::ShortFormat); case Qt::ToolTipRole: //debugPlan<name()<<", "<startTime(id()), QLocale::LongFormat)); case Qt::EditRole: return node->startTime(id()); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::endTime(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: return QLocale().toString(node->endTime(id()), QLocale::ShortFormat); case Qt::ToolTipRole: //debugPlan<name()<<", "<endTime(id()), QLocale::LongFormat)); case Qt::EditRole: return node->endTime(id()); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::duration(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: if (node->type() == Node::Type_Task) { Duration::Unit unit = node->estimate()->unit(); double v = node->duration(id()).toDouble(unit); return QVariant(QLocale().toString(v, 'f', m_prec) + Duration::unitToString(unit, true)); } else if (node->type() == Node::Type_Project) { Duration::Unit unit = Duration::Unit_d; double v = node->duration(id()).toDouble(unit); return QVariant(QLocale().toString(v, 'f', m_prec) + Duration::unitToString(unit, true)); } break; case Qt::ToolTipRole: if (node->type() == Node::Type_Task) { Duration::Unit unit = node->estimate()->unit(); double v = node->duration(id()).toDouble(unit); return xi18nc("@info:tooltip", "Scheduled duration: %1", QLocale().toString(v, 'f', m_prec) + Duration::unitToString(unit, true)); } else if (node->type() == Node::Type_Project) { Duration::Unit unit = Duration::Unit_d; double v = node->duration(id()).toDouble(unit); return xi18nc("@info:tooltip", "Scheduled duration: %1", QLocale().toString(v, 'f', m_prec) + Duration::unitToString(unit, true)); } break; case Qt::EditRole: { return node->duration(id()).toDouble(Duration::Unit_h); } case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::varianceDuration(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: if (node->type() == Node::Type_Task) { Duration::Unit unit = node->estimate()->unit(); double v = node->variance(id(), unit); return QLocale().toString(v, 'f', 2); } break; case Qt::EditRole: if (node->type() == Node::Type_Task) { Duration::Unit unit = node->estimate()->unit(); return node->variance(id(), unit); } return 0.0; case Qt::ToolTipRole: if (node->type() == Node::Type_Task) { Duration::Unit unit = node->estimate()->unit(); double v = node->variance(id(), unit); return xi18nc("@info:tooltip", "PERT duration variance: %1", QLocale().toString(v ,'f', 2)); } break; case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::varianceEstimate(const Estimate *est, int role) const { switch (role) { case Qt::DisplayRole: { if (est == 0) { return QVariant(); } Duration::Unit unit = est->unit(); double v = est->variance(unit); //debugPlan<name()<<": "<variance(est->unit()); } case Qt::ToolTipRole: { if (est == 0) { return QVariant(); } Duration::Unit unit = est->unit(); double v = est->variance(unit); return xi18nc("@info:tooltip", "PERT estimate variance: %1", QLocale().toString(v, 'f', 2) + Duration::unitToString(unit, true)); } case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::optimisticDuration(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: { if (node->type() != Node::Type_Task) { return QVariant(); } Duration d = node->duration(id()); d = (d * (100 + node->estimate()->optimisticRatio())) / 100; Duration::Unit unit = node->estimate()->unit(); double v = d.toDouble(unit); //debugPlan<name()<<": "<type() != Node::Type_Task) { return 0.0; } Duration d = node->duration(id()); d = (d * (100 + node->estimate()->optimisticRatio())) / 100; Duration::Unit unit = node->estimate()->unit(); return d.toDouble(unit); } case Qt::ToolTipRole: { if (node->type() != Node::Type_Task) { return QVariant(); } Duration d = node->duration(id()); d = (d * (100 + node->estimate()->optimisticRatio())) / 100; Duration::Unit unit = node->estimate()->unit(); double v = d.toDouble(unit); //debugPlan<name()<<": "<unit(); return QVariant(QLocale().toString(est->optimisticEstimate(), 'f', m_prec) + Duration::unitToString(unit, true)); break; } case Qt::EditRole: { if (est == 0) { return 0.0; } return est->optimisticEstimate(); } case Qt::ToolTipRole: { if (est == 0) { return QVariant(); } Duration::Unit unit = est->unit(); return xi18nc("@info:tooltip", "Optimistic estimate: %1", QLocale().toString(est->optimisticEstimate(), 'f', m_prec) + Duration::unitToString(unit, true)); break; } case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::pertExpected(const Estimate *est, int role) const { switch (role) { case Qt::DisplayRole: { if (est == 0) { return QVariant(); } Duration::Unit unit = est->unit(); double v = Estimate::scale(est->pertExpected(), unit, est->scales()); return QVariant(QLocale().toString(v, 'f', m_prec) + Duration::unitToString(unit, true)); } case Qt::EditRole: { if (est == 0) { return 0.0; } return Estimate::scale(est->pertExpected(), est->unit(), est->scales()); } case Qt::ToolTipRole: { if (est == 0) { return QVariant(); } Duration::Unit unit = est->unit(); double v = Estimate::scale(est->pertExpected(), unit, est->scales()); return xi18nc("@info:tooltip", "PERT expected estimate: %1", QLocale().toString(v, 'f', m_prec) + Duration::unitToString(unit, true)); } case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::pessimisticDuration(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: { if (node->type() != Node::Type_Task) { return QVariant(); } Duration d = node->duration(id()); d = (d * (100 + node->estimate()->pessimisticRatio())) / 100; Duration::Unit unit = node->estimate()->unit(); double v = d.toDouble(unit); //debugPlan<name()<<": "<type() != Node::Type_Task) { return 0.0; } Duration d = node->duration(id()); d = (d * (100 + node->estimate()->pessimisticRatio())) / 100; return d.toDouble(node->estimate()->unit()); } case Qt::ToolTipRole: { if (node->type() != Node::Type_Task) { return QVariant(); } Duration d = node->duration(id()); d = (d * (100 + node->estimate()->pessimisticRatio())) / 100; Duration::Unit unit = node->estimate()->unit(); double v = d.toDouble(unit); //debugPlan<name()<<": "<unit(); return QVariant(QLocale().toString(est->pessimisticEstimate(), 'f', m_prec) + Duration::unitToString(unit, true)); break; } case Qt::EditRole: { if (est == 0) { return 0.0; } return est->pessimisticEstimate(); } case Qt::ToolTipRole: { if (est == 0) { return QVariant(); } Duration::Unit unit = est->unit(); return xi18nc("@info:tooltip", "Pessimistic estimate: %1", QLocale().toString(est->pessimisticEstimate(), 'f', m_prec) + Duration::unitToString(unit, true)); break; } case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::earlyStart(const Node *node, int role) const { if (! (node->type() == Node::Type_Task || node->type() == Node::Type_Milestone)) { return QVariant(); } const Task *t = static_cast(node); switch (role) { case Qt::DisplayRole: return QLocale().toString(t->earlyStart(id()), QLocale::ShortFormat); case Qt::ToolTipRole: return QLocale().toString(t->earlyStart(id()).date(), QLocale::ShortFormat); case Qt::EditRole: return t->earlyStart(id()); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::earlyFinish(const Node *node, int role) const { if (! (node->type() == Node::Type_Task || node->type() == Node::Type_Milestone)) { return QVariant(); } const Task *t = static_cast(node); switch (role) { case Qt::DisplayRole: return QLocale().toString(t->earlyFinish(id()), QLocale::ShortFormat); case Qt::ToolTipRole: return QLocale().toString(t->earlyFinish(id()).date(), QLocale::ShortFormat); case Qt::EditRole: return t->earlyFinish(id()); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::lateStart(const Node *node, int role) const { if (! (node->type() == Node::Type_Task || node->type() == Node::Type_Milestone)) { return QVariant(); } const Task *t = static_cast(node); switch (role) { case Qt::DisplayRole: return QLocale().toString(t->lateStart(id()), QLocale::ShortFormat); case Qt::ToolTipRole: return QLocale().toString(t->lateStart(id()).date(), QLocale::ShortFormat); case Qt::EditRole: return t->lateStart(id()); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::lateFinish(const Node *node, int role) const { if (! (node->type() == Node::Type_Task || node->type() == Node::Type_Milestone)) { return QVariant(); } const Task *t = static_cast(node); switch (role) { case Qt::DisplayRole: return QLocale().toString(t->lateFinish(id()), QLocale::ShortFormat); case Qt::ToolTipRole: return QLocale().toString(t->lateFinish(id()).date(), QLocale::ShortFormat); case Qt::EditRole: return t->lateFinish(id()); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::positiveFloat(const Node *node, int role) const { if (! (node->type() == Node::Type_Task || node->type() == Node::Type_Milestone)) { return QVariant(); } const Task *t = static_cast(node); switch (role) { case Qt::DisplayRole: return t->positiveFloat(id()).toString(Duration::Format_i18nHourFraction); case Qt::ToolTipRole: return t->positiveFloat(id()).toString(Duration::Format_i18nDayTime); case Qt::EditRole: return t->positiveFloat(id()).toDouble(Duration::Unit_h); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::freeFloat(const Node *node, int role) const { if (! (node->type() == Node::Type_Task || node->type() == Node::Type_Milestone)) { return QVariant(); } const Task *t = static_cast(node); switch (role) { case Qt::DisplayRole: return t->freeFloat(id()).toString(Duration::Format_i18nHourFraction); case Qt::ToolTipRole: return t->freeFloat(id()).toString(Duration::Format_i18nDayTime); case Qt::EditRole: return t->freeFloat(id()).toDouble(Duration::Unit_h); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::negativeFloat(const Node *node, int role) const { if (! (node->type() == Node::Type_Task || node->type() == Node::Type_Milestone)) { return QVariant(); } const Task *t = static_cast(node); switch (role) { case Qt::DisplayRole: return t->negativeFloat(id()).toString(Duration::Format_i18nHourFraction); case Qt::ToolTipRole: return t->negativeFloat(id()).toString(Duration::Format_i18nDayTime); case Qt::EditRole: return t->negativeFloat(id()).toDouble(Duration::Unit_h); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::startFloat(const Node *node, int role) const { if (! (node->type() == Node::Type_Task || node->type() == Node::Type_Milestone)) { return QVariant(); } const Task *t = static_cast(node); switch (role) { case Qt::DisplayRole: return t->startFloat(id()).toString(Duration::Format_i18nHourFraction); case Qt::ToolTipRole: return t->startFloat(id()).toString(Duration::Format_i18nDayTime); case Qt::EditRole: return t->startFloat(id()).toDouble(Duration::Unit_h); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::finishFloat(const Node *node, int role) const { if (! (node->type() == Node::Type_Task || node->type() == Node::Type_Milestone)) { return QVariant(); } const Task *t = static_cast(node); switch (role) { case Qt::DisplayRole: return t->finishFloat(id()).toString(Duration::Format_i18nHourFraction); case Qt::ToolTipRole: return t->finishFloat(id()).toString(Duration::Format_i18nDayTime); case Qt::EditRole: return t->finishFloat(id()).toDouble(Duration::Unit_h); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::assignedResources(const Node *node, int role) const { if (node->type() != Node::Type_Task) { return QVariant(); } switch (role) { case Qt::DisplayRole: case Qt::EditRole: return node->assignedNameList(id()).join(","); case Qt::ToolTipRole: { QStringList lst = node->assignedNameList(id()); if (! lst.isEmpty()) { return xi18nc("@info:tooltip 1=list of resources", "Assigned resources:%1", node->assignedNameList(id()).join(", ")); } break; } case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::completed(const Node *node, int role) const { if (! (node->type() == Node::Type_Task || node->type() == Node::Type_Milestone)) { return QVariant(); } const Task *t = static_cast(node); switch (role) { case Qt::DisplayRole: return t->completion().percentFinished(); case Qt::EditRole: return t->completion().percentFinished(); case Qt::ToolTipRole: return xi18nc("@info:tooltip", "Task is %1% completed", t->completion().percentFinished()); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::status(const Node *node, int role) const { if (! (node->type() == Node::Type_Task || node->type() == Node::Type_Milestone)) { return QVariant(); } const Task *t = static_cast(node); switch (role) { case Qt::DisplayRole: { int st = t->state(id()); if (st & Node::State_NotScheduled) { return SchedulingState::notScheduled(); } if (st & Node::State_Finished) { if (st & Node::State_FinishedLate) { return i18n("Finished late"); } if (st & Node::State_FinishedEarly) { return i18n("Finished early"); } return i18n("Finished"); } if (st & Node::State_Running) { if (st & Node::State_Late) { return i18n("Running late"); } return i18n("Running"); } if (st & Node::State_Started) { if (st & Node::State_StartedLate) { return i18n("Started late"); } if (st & Node::State_StartedEarly) { return i18n("Started early"); } if (st & Node::State_Late) { return i18n("Running late"); } return i18n("Started"); } if (st & Node::State_ReadyToStart) { if (st & Node::State_Late) { return i18n("Not started"); } return i18n("Can start"); } if (st & Node::State_NotReadyToStart) { if (st & Node::State_Late) { return i18n("Delayed"); } return i18n("Cannot start"); } return i18n("Not started"); break; } case Qt::ToolTipRole: { int st = t->state(id()); if (st & Node::State_NotScheduled) { return SchedulingState::notScheduled(); } if (st & Node::State_Finished) { if (st & Node::State_FinishedLate) { Duration d = t->completion().finishTime() - t->endTime(id()); return xi18nc("@info:tooltip", "Finished %1 late", d.toString(Duration::Format_i18nDay)); } if (st & Node::State_FinishedEarly) { Duration d = t->endTime(id()) - t->completion().finishTime(); return xi18nc("@info:tooltip", "Finished %1 early", d.toString(Duration::Format_i18nDay)); } return xi18nc("@info:tooltip", "Finished"); } if (st & Node::State_Started) { if (st & Node::State_StartedLate) { Duration d = t->completion().startTime() - t->startTime(id()); return xi18nc("@info:tooltip", "Started %1 late", d.toString(Duration::Format_i18nDay)); } if (st & Node::State_StartedEarly) { Duration d = t->startTime(id()) - t->completion().startTime(); return xi18nc("@info:tooltip", "Started %1 early", d.toString(Duration::Format_i18nDay)); } return xi18nc("@info:tooltip", "Started"); } if (st & Node::State_Running) { return xi18nc("@info:tooltip", "Running"); } if (st & Node::State_ReadyToStart) { return xi18nc("@info:tooltip", "Can start"); } if (st & Node::State_NotReadyToStart) { return xi18nc("@info:tooltip", "Cannot start"); } return xi18nc("@info:tooltip", "Not started"); break; } case Qt::EditRole: return t->state(id()); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::startedTime(const Node *node, int role) const { if (! (node->type() == Node::Type_Task || node->type() == Node::Type_Milestone)) { return QVariant(); } const Task *t = static_cast(node); switch (role) { case Qt::DisplayRole: if (t->completion().isStarted()) { return QLocale().toString(t->completion().startTime(), QLocale::ShortFormat); } break; case Qt::ToolTipRole: if (t->completion().isStarted()) { return xi18nc("@info:tooltip", "Actual start: %1", QLocale().toString(t->completion().startTime().date(), QLocale::LongFormat)); } break; case Qt::EditRole: if (t->completion().isStarted()) { return t->completion().startTime(); } return QDateTime::currentDateTime(); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::isStarted(const Node *node, int role) const { if (! (node->type() == Node::Type_Task || node->type() == Node::Type_Milestone)) { return QVariant(); } const Task *t = static_cast(node); switch (role) { case Qt::DisplayRole: case Qt::EditRole: return t->completion().isStarted(); case Qt::ToolTipRole: if (t->completion().isStarted()) { return xi18nc("@info:tooltip", "The task started at: %1", QLocale().toString(t->completion().startTime().date(), QLocale::LongFormat)); } return xi18nc("@info:tooltip", "The task is not started"); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::finishedTime(const Node *node, int role) const { if (! (node->type() == Node::Type_Task || node->type() == Node::Type_Milestone)) { return QVariant(); } const Task *t = static_cast(node); switch (role) { case Qt::DisplayRole: if (t->completion().isFinished()) { return QLocale().toString(t->completion().finishTime(), QLocale::ShortFormat); } break; case Qt::ToolTipRole: if (t->completion().isFinished()) { return xi18nc("@info:tooltip", "Actual finish: %1", QLocale().toString(t->completion().finishTime(), QLocale::LongFormat)); } break; case Qt::EditRole: if (t->completion().isFinished()) { return t->completion().finishTime(); } break; case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::isFinished(const Node *node, int role) const { if (! (node->type() == Node::Type_Task || node->type() == Node::Type_Milestone)) { return QVariant(); } const Task *t = static_cast(node); switch (role) { case Qt::DisplayRole: case Qt::EditRole: return t->completion().isFinished(); case Qt::ToolTipRole: if (t->completion().isFinished()) { return xi18nc("@info:tooltip", "The task finished at: %1", QLocale().toString(t->completion().finishTime().date(), QLocale::LongFormat)); } return xi18nc("@info:tooltip", "The task is not finished"); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::plannedEffortTo(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: return node->plannedEffortTo(m_now, id()).format(); case Qt::ToolTipRole: return xi18nc("@info:tooltip", "Planned effort until %1: %2", QLocale().toString(m_now, QLocale::ShortFormat), node->plannedEffortTo(m_now, id()).toString(Duration::Format_i18nHour)); case Qt::EditRole: return node->plannedEffortTo(m_now, id()).toDouble(Duration::Unit_h); case Role::DurationUnit: return static_cast(Duration::Unit_h); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::actualEffortTo(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: return node->actualEffortTo(m_now).format(); case Qt::ToolTipRole: //debugPlan<actualEffortTo(m_now).toString(Duration::Format_i18nHour)); case Qt::EditRole: return node->actualEffortTo(m_now).toDouble(Duration::Unit_h); case Role::DurationUnit: return static_cast(Duration::Unit_h); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::remainingEffort(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: { const Task *t = dynamic_cast(node); if (t) { return t->completion().remainingEffort().format(); } break; } case Qt::ToolTipRole: { const Task *t = dynamic_cast(node); if (t) { return xi18nc("@info:tooltip", "Remaining effort: %1", t->completion().remainingEffort().toString(Duration::Format_i18nHour)); } break; } case Qt::EditRole: { const Task *t = dynamic_cast(node); if (t == 0) { return QVariant(); } return t->completion().remainingEffort().toDouble(Duration::Unit_h); } case Role::DurationUnit: return static_cast(Duration::Unit_h); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::plannedCostTo(const Node *node, int role) const { Locale *l = m_project->locale(); switch (role) { case Qt::DisplayRole: return l->formatMoney(node->plannedCostTo(m_now, id())); case Qt::ToolTipRole: return xi18nc("@info:tooltip", "Planned cost until %1: %2", QLocale().toString(m_now, QLocale::ShortFormat), l->formatMoney(node->plannedCostTo(m_now, id()))); case Qt::EditRole: return node->plannedCostTo(m_now); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::actualCostTo(const Node *node, int role) const { Locale *l = m_project->locale(); switch (role) { case Qt::DisplayRole: return l->formatMoney(node->actualCostTo(id(), m_now).cost()); case Qt::ToolTipRole: return xi18nc("@info:tooltip", "Actual cost until %1: %2", QLocale().toString(m_now, QLocale::ShortFormat), l->formatMoney(node->actualCostTo(id(), m_now).cost())); case Qt::EditRole: return node->actualCostTo(id(), m_now).cost(); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::note(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: case Qt::EditRole: case Qt::ToolTipRole: if (node->type() == Node::Type_Task) { Node *n = const_cast(node); return static_cast(n)->completion().note(); } break; case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::nodeSchedulingStatus(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: return node->schedulingStatus(id(), true).value(0); case Qt::EditRole: return node->schedulingStatus(id(), false).value(0); case Qt::ToolTipRole: return node->schedulingStatus(id(), true).join("\n"); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::resourceIsMissing(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: if (node->resourceError(id())) { return i18n("Error"); } break; case Qt::EditRole: return node->resourceError(id()); case Qt::ToolTipRole: if (node->resourceError(id())) { return xi18nc("@info:tooltip", "Resource allocation is expected when the task estimate type is Effort"); } break; case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); case Role::Foreground: { if (! m_project) { break; } switch (node->type()) { case Node::Type_Task: return m_project->config().taskErrorColor(); case Node::Type_Milestone: return m_project->config().milestoneErrorColor(); default: break; } } } return QVariant(); } QVariant NodeModel::resourceIsOverbooked(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: if (node->resourceOverbooked(id())) { return i18n("Error"); } break; case Qt::EditRole: return node->resourceOverbooked(id()); case Qt::ToolTipRole: if (node->resourceOverbooked(id())) { return xi18nc("@info:tooltip", "A resource has been overbooked"); } break; case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); case Role::Foreground: { if (! m_project) { break; } switch (node->type()) { case Node::Type_Task: return m_project->config().taskErrorColor(); case Node::Type_Milestone: return m_project->config().milestoneErrorColor(); default: break; } } } return QVariant(); } QVariant NodeModel::resourceIsNotAvailable(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: if (node->resourceNotAvailable(id())) { return i18n("Error"); } break; case Qt::EditRole: return node->resourceNotAvailable(id()); case Qt::ToolTipRole: if (node->resourceNotAvailable(id())) { return xi18nc("@info:tooltip", "No resource is available for this task"); } break; case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); case Role::Foreground: { if (! m_project) { break; } switch (node->type()) { case Node::Type_Task: return m_project->config().taskErrorColor(); case Node::Type_Milestone: return m_project->config().milestoneErrorColor(); default: break; } } } return QVariant(); } QVariant NodeModel::schedulingConstraintsError(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: if (node->constraintError(id())) { return i18n("Error"); } break; case Qt::EditRole: return node->constraintError(id()); case Qt::ToolTipRole: if (node->constraintError(id())) { return xi18nc("@info:tooltip", "Failed to comply with a timing constraint"); } break; case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); case Role::Foreground: { if (! m_project) { break; } switch (node->type()) { case Node::Type_Task: return m_project->config().taskErrorColor(); case Node::Type_Milestone: return m_project->config().milestoneErrorColor(); default: break; } } } return QVariant(); } QVariant NodeModel::nodeIsNotScheduled(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: if (node->notScheduled(id())) { return i18n("Error"); } break; case Qt::EditRole: return node->notScheduled(id()); case Qt::ToolTipRole: if (node->notScheduled(id())) { return xi18nc("@info:tooltip", "This task has not been scheduled"); } break; case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); case Role::Foreground: { if (! m_project) { break; } switch (node->type()) { case Node::Type_Task: return m_project->config().taskErrorColor(); case Node::Type_Milestone: return m_project->config().milestoneErrorColor(); default: break; } } } return QVariant(); } QVariant NodeModel::effortNotMet(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: if (node->effortMetError(id())) { return i18n("Error"); } break; case Qt::EditRole: return node->effortMetError(id()); case Qt::ToolTipRole: if (node->effortMetError(id())) { return xi18nc("@info:tooltip", "The assigned resources cannot deliver the required estimated effort"); } break; case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); case Role::Foreground: { if (! m_project) { break; } switch (node->type()) { case Node::Type_Task: return m_project->config().taskErrorColor(); case Node::Type_Milestone: return m_project->config().milestoneErrorColor(); default: break; } } } return QVariant(); } QVariant NodeModel::schedulingError(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: if (node->schedulingError(id())) { return i18n("Error"); } break; case Qt::EditRole: return node->schedulingError(id()); case Qt::ToolTipRole: if (node->schedulingError(id())) { return xi18nc("@info:tooltip", "Scheduling error"); } break; case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); case Role::Foreground: { if (! m_project) { break; } switch (node->type()) { case Node::Type_Task: return m_project->config().taskErrorColor(); case Node::Type_Milestone: return m_project->config().milestoneErrorColor(); default: break; } } } return QVariant(); } QVariant NodeModel::wbsCode(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: case Qt::EditRole: return node->wbsCode(); case Qt::ToolTipRole: return xi18nc("@info:tooltip", "Work breakdown structure code: %1", node->wbsCode()); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); case SortableRole: return node->wbsCode(true); } return QVariant(); } QVariant NodeModel::nodeLevel(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: case Qt::EditRole: return node->level(); case Qt::ToolTipRole: return xi18nc("@info:tooltip", "Task level: %1", node->level()); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::nodeBCWS(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: return m_project->locale()->formatMoney(node->bcws(m_now, id()), QString(), 0); case Qt::EditRole: return node->bcws(m_now, id()); case Qt::ToolTipRole: return xi18nc("@info:tooltip", "Budgeted Cost of Work Scheduled at %1: %2", QLocale().toString(m_now, QLocale::ShortFormat), m_project->locale()->formatMoney(node->bcws(m_now, id()), QString(), 0)); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::nodeBCWP(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: return m_project->locale()->formatMoney(node->bcwp(id()), QString(), 0); case Qt::EditRole: return node->bcwp(id()); case Qt::ToolTipRole: return xi18nc("@info:tooltip", "Budgeted Cost of Work Performed at %1: %2", QLocale().toString(m_now, QLocale::ShortFormat), m_project->locale()->formatMoney(node->bcwp(id()), QString(), 0)); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::nodeACWP(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: return m_project->locale()->formatMoney(node->acwp(m_now, id()).cost(), QString(), 0); case Qt::EditRole: return node->acwp(m_now, id()).cost(); case Qt::ToolTipRole: return xi18nc("@info:tooltip", "Actual Cost of Work Performed at %1: %2", QLocale().toString(m_now, QLocale::ShortFormat), m_project->locale()->formatMoney(node->acwp(m_now, id()).cost())); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::nodePerformanceIndex(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: return QLocale().toString(node->schedulePerformanceIndex(m_now, id()), 'f', 2); case Qt::EditRole: return node->schedulePerformanceIndex(m_now, id()); case Qt::ToolTipRole: return xi18nc("@info:tooltip", "Schedule Performance Index at %1: %2", m_now.toString(), QLocale().toString(node->schedulePerformanceIndex(m_now, id()), 'f', 2)); case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); case Qt::ForegroundRole: return QColor(node->schedulePerformanceIndex(m_now, id()) < 1.0 ? Qt::red : Qt::black); } return QVariant(); } QVariant NodeModel::nodeIsCritical(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: case Qt::EditRole: return node->isCritical(id()); case Qt::ToolTipRole: case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); case Role::Foreground: { if (! m_project) { break; } switch (node->type()) { case Node::Type_Task: return m_project->config().taskNormalColor(); case Node::Type_Milestone: return m_project->config().milestoneNormalColor(); default: break; } } } return QVariant(); } QVariant NodeModel::nodeInCriticalPath(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: case Qt::EditRole: return node->inCriticalPath(id()); case Qt::ToolTipRole: case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); case Role::Foreground: { if (! m_project) { break; } switch (node->type()) { case Node::Type_Task: return m_project->config().taskNormalColor(); case Node::Type_Milestone: return m_project->config().milestoneNormalColor(); default: break; } } } return QVariant(); } QVariant NodeModel::wpOwnerName(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: case Qt::EditRole: { const Task *t = dynamic_cast(node); if (t == 0) { return QVariant(); } if (t->wpTransmitionStatus() == WorkPackage::TS_None) { return xi18nc("Not available", "NA"); } return t->wpOwnerName(); } case Qt::ToolTipRole: { const Task *task = dynamic_cast(node); if (task == 0) { return QVariant(); } int sts = task->wpTransmitionStatus(); QString t = wpTransmitionTime(node, Qt::DisplayRole).toString(); if (sts == WorkPackage::TS_Send) { return xi18nc("@info:tooltip", "Latest work package sent to %1 at %2", static_cast(node)->wpOwnerName(), t); } if (sts == WorkPackage::TS_Receive) { return xi18nc("@info:tooltip", "Latest work package received from %1 at %2", static_cast(node)->wpOwnerName(), t); } return xi18nc("@info:tooltip", "Not available"); } case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::wpTransmitionStatus(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: { const Task *t = dynamic_cast(node); if (t == 0) { return QVariant(); } if (t->wpTransmitionStatus() == WorkPackage::TS_None) { return xi18nc("Not available", "NA"); } return WorkPackage::transmitionStatusToString(t->wpTransmitionStatus(), true); } case Qt::EditRole: { const Task *t = dynamic_cast(node); if (t == 0) { return QVariant(); } return WorkPackage::transmitionStatusToString(t->wpTransmitionStatus(), false); } case Qt::ToolTipRole: case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::wpTransmitionTime(const Node *node, int role) const { switch (role) { case Qt::DisplayRole: case Qt::EditRole: { const Task *t = dynamic_cast(node); if (t == 0) { return QVariant(); } if (t->wpTransmitionStatus() == WorkPackage::TS_None) { return xi18nc("Not available", "NA"); } return QLocale().toString(t->wpTransmitionTime(), QLocale::ShortFormat); } case Qt::ToolTipRole: { const Task *task = dynamic_cast(node); if (task == 0) { return QVariant(); } int sts = task->wpTransmitionStatus(); QString t = wpTransmitionTime(node, Qt::DisplayRole).toString(); if (sts == WorkPackage::TS_Send) { return xi18nc("@info:tooltip", "Latest work package sent: %1", t); } if (sts == WorkPackage::TS_Receive) { return xi18nc("@info:tooltip", "Latest work package received: %1", t); } return xi18nc("@info:tooltip", "Not available"); } case Qt::StatusTipRole: case Qt::WhatsThisRole: return QVariant(); } return QVariant(); } QVariant NodeModel::data(const Node *n, int property, int role) const { QVariant result; switch (property) { // Edited by user case NodeName: result = name(n, role); break; case NodeType: result = type(n, role); break; case NodeResponsible: result = leader(n, role); break; case NodeAllocation: result = allocation(n, role); break; case NodeEstimateType: result = estimateType(n, role); break; case NodeEstimateCalendar: result = estimateCalendar(n, role); break; case NodeEstimate: result = estimate(n, role); break; case NodeOptimisticRatio: result = optimisticRatio(n, role); break; case NodePessimisticRatio: result = pessimisticRatio(n, role); break; case NodeRisk: result = riskType(n, role); break; case NodePriority: result = priority(n, role); break; case NodeConstraint: result = constraint(n, role); break; case NodeConstraintStart: result = constraintStartTime(n, role); break; case NodeConstraintEnd: result = constraintEndTime(n, role); break; case NodeRunningAccount: result = runningAccount(n, role); break; case NodeStartupAccount: result = startupAccount(n, role); break; case NodeStartupCost: result = startupCost(n, role); break; case NodeShutdownAccount: result = shutdownAccount(n, role); break; case NodeShutdownCost: result = shutdownCost(n, role); break; case NodeDescription: result = description(n, role); break; // Based on edited values case NodeExpected: result = pertExpected(n->estimate(), role); break; case NodeVarianceEstimate: result = varianceEstimate(n->estimate(), role); break; case NodeOptimistic: result = optimisticEstimate(n->estimate(), role); break; case NodePessimistic: result = pessimisticEstimate(n->estimate(), role); break; // After scheduling case NodeStartTime: result = startTime(n, role); break; case NodeEndTime: result = endTime(n, role); break; case NodeEarlyStart: result = earlyStart(n, role); break; case NodeEarlyFinish: result = earlyFinish(n, role); break; case NodeLateStart: result = lateStart(n, role); break; case NodeLateFinish: result = lateFinish(n, role); break; case NodePositiveFloat: result = positiveFloat(n, role); break; case NodeFreeFloat: result = freeFloat(n, role); break; case NodeNegativeFloat: result = negativeFloat(n, role); break; case NodeStartFloat: result = startFloat(n, role); break; case NodeFinishFloat: result = finishFloat(n, role); break; case NodeAssignments: result = assignedResources(n, role); break; // Based on scheduled values case NodeDuration: result = duration(n, role); break; case NodeVarianceDuration: result = varianceDuration(n, role); break; case NodeOptimisticDuration: result = optimisticDuration(n, role); break; case NodePessimisticDuration: result = pessimisticDuration(n, role); break; // Completion case NodeStatus: result = status(n, role); break; case NodeCompleted: result = completed(n, role); break; case NodePlannedEffort: result = plannedEffortTo(n, role); break; case NodeActualEffort: result = actualEffortTo(n, role); break; case NodeRemainingEffort: result = remainingEffort(n, role); break; case NodePlannedCost: result = plannedCostTo(n, role); break; case NodeActualCost: result = actualCostTo(n, role); break; case NodeActualStart: result = startedTime(n, role); break; case NodeStarted: result = isStarted(n, role); break; case NodeActualFinish: result = finishedTime(n, role); break; case NodeFinished: result = isFinished(n, role); break; case NodeStatusNote: result = note(n, role); break; // Scheduling errors case NodeSchedulingStatus: result = nodeSchedulingStatus(n, role); break; case NodeNotScheduled: result = nodeIsNotScheduled(n, role); break; case NodeAssignmentMissing: result = resourceIsMissing(n, role); break; case NodeResourceOverbooked: result = resourceIsOverbooked(n, role); break; case NodeResourceUnavailable: result = resourceIsNotAvailable(n, role); break; case NodeConstraintsError: result = schedulingConstraintsError(n, role); break; case NodeEffortNotMet: result = effortNotMet(n, role); break; case NodeSchedulingError: result = schedulingError(n, role); break; case NodeWBSCode: result = wbsCode(n, role); break; case NodeLevel: result = nodeLevel(n, role); break; // Performance case NodeBCWS: result = nodeBCWS(n, role); break; case NodeBCWP: result = nodeBCWP(n, role); break; case NodeACWP: result = nodeACWP(n, role); break; case NodePerformanceIndex: result = nodePerformanceIndex(n, role); break; case NodeCritical: result = nodeIsCritical(n, role); break; case NodeCriticalPath: result = nodeInCriticalPath(n, role); break; case WPOwnerName: result = wpOwnerName(n, role); break; case WPTransmitionStatus: result = wpTransmitionStatus(n, role); break; case WPTransmitionTime: result = wpTransmitionTime(n, role); break; default: //debugPlan<<"Invalid property number: "<name()) { return 0; } KUndo2MagicString s = kundo2_i18n("Modify name"); switch (node->type()) { case Node::Type_Task: s = kundo2_i18n("Modify task name"); break; case Node::Type_Milestone: s = kundo2_i18n("Modify milestone name"); break; case Node::Type_Summarytask: s = kundo2_i18n("Modify summarytask name"); break; case Node::Type_Project: s = kundo2_i18n("Modify project name"); break; } return new NodeModifyNameCmd(*node, value.toString(), s); } } return 0; } KUndo2Command *NodeModel::setLeader(Node *node, const QVariant &value, int role) { switch (role) { case Qt::EditRole: { if (value.toString() != node->leader()) { return new NodeModifyLeaderCmd(*node, value.toString(), kundo2_i18n("Modify responsible")); } break; } default: break; } return 0; } KUndo2Command *NodeModel::setAllocation(Node */*node*/, const QVariant &/*value*/, int /*role*/) { return 0; } KUndo2Command *NodeModel::setDescription(Node *node, const QVariant &value, int role) { switch (role) { case Qt::EditRole: if (value.toString() == node->description()) { return 0; } return new NodeModifyDescriptionCmd(*node, value.toString(), kundo2_i18n("Modify task description")); } return 0; } KUndo2Command *NodeModel::setType(Node *, const QVariant &, int) { return 0; } KUndo2Command *NodeModel::setConstraint(Node *node, const QVariant &value, int role) { switch (role) { case Qt::EditRole: { Node::ConstraintType v; QStringList lst = node->constraintList(false); if (lst.contains(value.toString())) { v = Node::ConstraintType(lst.indexOf(value.toString())); } else { v = Node::ConstraintType(value.toInt()); } //debugPlan<constraint()) { return new NodeModifyConstraintCmd(*node, v, kundo2_i18n("Modify constraint type")); } break; } default: break; } return 0; } KUndo2Command *NodeModel::setConstraintStartTime(Node *node, const QVariant &value, int role) { switch (role) { case Qt::EditRole: { QDateTime dt = value.toDateTime(); dt.setTime(QTime(dt.time().hour(), dt.time().minute(), 0)); // reset possible secs/msecs if (dt != node->constraintStartTime()) { return new NodeModifyConstraintStartTimeCmd(*node, dt, kundo2_i18n("Modify constraint start time")); } break; } default: break; } return 0; } KUndo2Command *NodeModel::setConstraintEndTime(Node *node, const QVariant &value, int role) { switch (role) { case Qt::EditRole: { QDateTime dt = value.toDateTime(); dt.setTime(QTime(dt.time().hour(), dt.time().minute(), 0)); // reset possible secs/msecs if (dt != node->constraintEndTime()) { return new NodeModifyConstraintEndTimeCmd(*node, dt, kundo2_i18n("Modify constraint end time")); } break; } default: break; } return 0; } KUndo2Command *NodeModel::setEstimateType(Node *node, const QVariant &value, int role) { if (node->estimate() == 0) { return 0; } switch (role) { case Qt::EditRole: { Estimate::Type v; QStringList lst = node->estimate()->typeToStringList(false); if (lst.contains(value.toString())) { v = Estimate::Type(lst.indexOf(value.toString())); } else { v = Estimate::Type(value.toInt()); } if (v != node->estimate()->type()) { return new ModifyEstimateTypeCmd(*node, node->estimate()->type(), v, kundo2_i18n("Modify estimate type")); } break; } default: break; } return 0; } KUndo2Command *NodeModel::setEstimateCalendar(Node *node, const QVariant &value, int role) { if (node->estimate() == 0) { return 0; } switch (role) { case Qt::EditRole: { Calendar *c = 0; Calendar *old = node->estimate()->calendar(); if (value.toInt() > 0) { QStringList lst = estimateCalendar(node, Role::EnumList).toStringList(); if (value.toInt() < lst.count()) { c = m_project->calendarByName(lst.at(value.toInt())); } } if (c != old) { return new ModifyEstimateCalendarCmd(*node, old, c, kundo2_i18n("Modify estimate calendar")); } break; } default: break; } return 0; } KUndo2Command *NodeModel::setEstimate(Node *node, const QVariant &value, int role) { if (node->estimate() == 0) { return 0; } switch (role) { case Qt::EditRole: { double d; Duration::Unit unit; if (value.toList().count() == 2) { d = value.toList()[0].toDouble(); unit = static_cast(value.toList()[1].toInt()); } else if (value.canConvert()) { bool ok = Duration::valueFromString(value.toString(), d, unit); if (! ok) { return 0; } } else { return 0; } //debugPlan<"<estimate()->expectedEstimate()) { if (cmd == 0) cmd = new MacroCommand(kundo2_i18n("Modify estimate")); cmd->addCommand(new ModifyEstimateCmd(*node, node->estimate()->expectedEstimate(), d)); } if (unit != node->estimate()->unit()) { if (cmd == 0) cmd = new MacroCommand(kundo2_i18n("Modify estimate")); cmd->addCommand(new ModifyEstimateUnitCmd(*node, node->estimate()->unit(), unit)); } if (cmd) { return cmd; } break; } default: break; } return 0; } KUndo2Command *NodeModel::setOptimisticRatio(Node *node, const QVariant &value, int role) { if (node->estimate() == 0) { return 0; } switch (role) { case Qt::EditRole: if (value.toInt() != node->estimate()->optimisticRatio()) { return new EstimateModifyOptimisticRatioCmd(*node, node->estimate()->optimisticRatio(), value.toInt(), kundo2_i18n("Modify optimistic estimate")); } break; default: break; } return 0; } KUndo2Command *NodeModel::setPessimisticRatio(Node *node, const QVariant &value, int role) { if (node->estimate() == 0) { return 0; } switch (role) { case Qt::EditRole: if (value.toInt() != node->estimate()->pessimisticRatio()) { return new EstimateModifyPessimisticRatioCmd(*node, node->estimate()->pessimisticRatio(), value.toInt(), kundo2_i18n("Modify pessimistic estimate")); } default: break; } return 0; } KUndo2Command *NodeModel::setRiskType(Node *node, const QVariant &value, int role) { if (node->estimate() == 0) { return 0; } switch (role) { case Qt::EditRole: { int val = 0; QStringList lst = node->estimate()->risktypeToStringList(false); if (lst.contains(value.toString())) { val = lst.indexOf(value.toString()); } else { val = value.toInt(); } if (val != node->estimate()->risktype()) { Estimate::Risktype v = Estimate::Risktype(val); return new EstimateModifyRiskCmd(*node, node->estimate()->risktype(), v, kundo2_i18n("Modify risk type")); } break; } default: break; } return 0; } KUndo2Command *NodeModel::setPriority(Node *node, const QVariant &value, int role) { switch (role) { case Qt::EditRole: { if (value.toInt() != node->priority()) { return new NodeModifyPriorityCmd(*node, node->priority(), value.toInt(), kundo2_i18n("Modify priority")); } break; } default: break; } return 0; } KUndo2Command *NodeModel::setRunningAccount(Node *node, const QVariant &value, int role) { switch (role) { case Qt::EditRole: { //debugPlan<name(); QStringList lst = runningAccount(node, Role::EnumList).toStringList(); if (value.toInt() < lst.count()) { Account *a = m_project->accounts().findAccount(lst.at(value.toInt())); Account *old = node->runningAccount(); if (old != a) { return new NodeModifyRunningAccountCmd(*node, old, a, kundo2_i18n("Modify running account")); } } break; } default: break; } return 0; } KUndo2Command *NodeModel::setStartupAccount(Node *node, const QVariant &value, int role) { switch (role) { case Qt::EditRole: { //debugPlan<name(); QStringList lst = startupAccount(node, Role::EnumList).toStringList(); if (value.toInt() < lst.count()) { Account *a = m_project->accounts().findAccount(lst.at(value.toInt())); Account *old = node->startupAccount(); //debugPlan<<(value.toInt())<<";"<<(lst.at(value.toInt()))<<":"<startupCost()) { return new NodeModifyStartupCostCmd(*node, v, kundo2_i18n("Modify startup cost")); } break; } default: break; } return 0; } KUndo2Command *NodeModel::setShutdownAccount(Node *node, const QVariant &value, int role) { switch (role) { case Qt::EditRole: { //debugPlan<name(); QStringList lst = shutdownAccount(node, Role::EnumList).toStringList(); if (value.toInt() < lst.count()) { Account *a = m_project->accounts().findAccount(lst.at(value.toInt())); Account *old = node->shutdownAccount(); if (old != a) { return new NodeModifyShutdownAccountCmd(*node, old, a, kundo2_i18n("Modify shutdown account")); } } break; } default: break; } return 0; } KUndo2Command *NodeModel::setShutdownCost(Node *node, const QVariant &value, int role) { switch (role) { case Qt::EditRole: { double v = value.toDouble(); if (v != node->shutdownCost()) { return new NodeModifyShutdownCostCmd(*node, v, kundo2_i18n("Modify shutdown cost")); } break; } default: break; } return 0; } KUndo2Command *NodeModel::setCompletion(Node */*node*/, const QVariant &/*value*/, int /*role*/) { return 0; } KUndo2Command *NodeModel::setRemainingEffort(Node *node, const QVariant &value, int role) { if (role == Qt::EditRole && node->type() == Node::Type_Task) { Task *t = static_cast(node); double d(value.toList()[0].toDouble()); Duration::Unit unit = static_cast(value.toList()[1].toInt()); Duration dur(d, unit); return new ModifyCompletionRemainingEffortCmd(t->completion(), QDate::currentDate(), dur, kundo2_i18n("Modify remaining effort")); } return 0; } KUndo2Command *NodeModel::setActualEffort(Node *node, const QVariant &value, int role) { if (role == Qt::EditRole && node->type() == Node::Type_Task) { Task *t = static_cast(node); double d(value.toList()[0].toDouble()); Duration::Unit unit = static_cast(value.toList()[1].toInt()); Duration dur(d, unit); return new ModifyCompletionActualEffortCmd(t->completion(), QDate::currentDate(), dur, kundo2_i18n("Modify actual effort")); } return 0; } KUndo2Command *NodeModel::setStartedTime(Node *node, const QVariant &value, int role) { switch (role) { case Qt::EditRole: { Task *t = qobject_cast(node); if (t == 0) { return 0; } MacroCommand *m = new MacroCommand(kundo2_i18n("Modify actual start time")); if (! t->completion().isStarted()) { m->addCommand(new ModifyCompletionStartedCmd(t->completion(), true)); } m->addCommand(new ModifyCompletionStartTimeCmd(t->completion(), value.toDateTime())); if (t->type() == Node::Type_Milestone) { m->addCommand(new ModifyCompletionFinishedCmd(t->completion(), true)); m->addCommand(new ModifyCompletionFinishTimeCmd(t->completion(), value.toDateTime())); if (t->completion().percentFinished() < 100) { Completion::Entry *e = new Completion::Entry(100, Duration::zeroDuration, Duration::zeroDuration); m->addCommand(new AddCompletionEntryCmd(t->completion(), value.toDate(), e)); } } return m; } default: break; } return 0; } KUndo2Command *NodeModel::setFinishedTime(Node *node, const QVariant &value, int role) { switch (role) { case Qt::EditRole: { Task *t = qobject_cast(node); if (t == 0) { return 0; } MacroCommand *m = new MacroCommand(kundo2_i18n("Modify actual finish time")); if (! t->completion().isFinished()) { m->addCommand(new ModifyCompletionFinishedCmd(t->completion(), true)); if (t->completion().percentFinished() < 100) { Completion::Entry *e = new Completion::Entry(100, Duration::zeroDuration, Duration::zeroDuration); m->addCommand(new AddCompletionEntryCmd(t->completion(), value.toDate(), e)); } } m->addCommand(new ModifyCompletionFinishTimeCmd(t->completion(), value.toDateTime())); if (t->type() == Node::Type_Milestone) { m->addCommand(new ModifyCompletionStartedCmd(t->completion(), true)); m->addCommand(new ModifyCompletionStartTimeCmd(t->completion(), value.toDateTime())); } return m; } default: break; } return 0; } //---------------------------- NodeItemModel::NodeItemModel(QObject *parent) : ItemModelBase(parent), m_node(0), m_projectshown(false) { setReadOnly(NodeModel::NodeDescription, true); } NodeItemModel::~NodeItemModel() { } void NodeItemModel::setShowProject(bool on) { beginResetModel(); m_projectshown = on; endResetModel(); emit projectShownChanged(on); } void NodeItemModel::slotNodeToBeInserted(Node *parent, int row) { //debugPlan<name()<<"; "<parentNode()->name()<<"-->"<name(); Q_ASSERT(node->parentNode() == m_node); endInsertRows(); m_node = 0; emit nodeInserted(node); } void NodeItemModel::slotNodeToBeRemoved(Node *node) { //debugPlan<name(); Q_ASSERT(m_node == 0); m_node = node; int row = index(node).row(); beginRemoveRows(index(node->parentNode()), row, row); } void NodeItemModel::slotNodeRemoved(Node *node) { //debugPlan<name(); Q_ASSERT(node == m_node); #ifdef NDEBUG Q_UNUSED(node) #endif endRemoveRows(); m_node = 0; } void NodeItemModel::slotNodeToBeMoved(Node *node, int pos, Node *newParent, int newPos) { //debugPlan<parentNode()->name()<name()<parentNode()), pos, pos, index(newParent), newPos); } void NodeItemModel::slotNodeMoved(Node *node) { Q_UNUSED(node); //debugPlan<parentNode()->name()<parentNode()->indexOf(node); endMoveRows(); } void NodeItemModel::slotLayoutChanged() { //debugPlan<name(); emit layoutAboutToBeChanged(); emit layoutChanged(); } void NodeItemModel::slotProjectCalculated(ScheduleManager *sm) { debugPlan<allNodes()) { int row = n->parentNode()->indexOf(n); QModelIndex idx = createIndex(row, NodeModel::NodeWBSCode, n); emit dataChanged(idx, idx); } } void NodeItemModel::setProject(Project *project) { beginResetModel(); if (m_project) { disconnect(m_project, &Project::aboutToBeDeleted, this, &NodeItemModel::projectDeleted); disconnect(m_project, SIGNAL(localeChanged()), this, SLOT(slotLayoutChanged())); disconnect(m_project, &Project::wbsDefinitionChanged, this, &NodeItemModel::slotWbsDefinitionChanged); disconnect(m_project, &Project::nodeChanged, this, &NodeItemModel::slotNodeChanged); disconnect(m_project, &Project::nodeToBeAdded, this, &NodeItemModel::slotNodeToBeInserted); disconnect(m_project, &Project::nodeToBeRemoved, this, &NodeItemModel::slotNodeToBeRemoved); disconnect(m_project, &Project::nodeToBeMoved, this, &NodeItemModel::slotNodeToBeMoved); disconnect(m_project, &Project::nodeMoved, this, &NodeItemModel::slotNodeMoved); disconnect(m_project, &Project::nodeAdded, this, &NodeItemModel::slotNodeInserted); disconnect(m_project, &Project::nodeRemoved, this, &NodeItemModel::slotNodeRemoved); disconnect(m_project, &Project::projectCalculated, this, &NodeItemModel::slotProjectCalculated); } m_project = project; debugPlan<"<isBaselined(); flags |= Qt::ItemIsDropEnabled; switch (index.column()) { case NodeModel::NodeName: // name flags |= Qt::ItemIsEditable; break; case NodeModel::NodeType: break; // Node type case NodeModel::NodeResponsible: // Responsible flags |= Qt::ItemIsEditable; break; case NodeModel::NodeAllocation: // allocation if (n->type() == Node::Type_Task) { flags |= Qt::ItemIsEditable; } break; case NodeModel::NodeEstimateType: // estimateType { if (! baselined && (n->type() == Node::Type_Task || n->type() == Node::Type_Milestone)) { flags |= Qt::ItemIsEditable; } break; } case NodeModel::NodeEstimate: // estimate { if (! baselined && (n->type() == Node::Type_Task || n->type() == Node::Type_Milestone)) { flags |= Qt::ItemIsEditable; } break; } case NodeModel::NodeOptimisticRatio: // optimisticRatio case NodeModel::NodePessimisticRatio: // pessimisticRatio { if (! baselined && n->type() == Node::Type_Task) { flags |= Qt::ItemIsEditable; } break; } case NodeModel::NodeEstimateCalendar: { if (! baselined && n->type() == Node::Type_Task) { flags |= Qt::ItemIsEditable; } break; } case NodeModel::NodeRisk: // risktype { if (! baselined && n->type() == Node::Type_Task) { flags |= Qt::ItemIsEditable; } break; } case NodeModel::NodePriority: { if (!baselined) { flags |= Qt::ItemIsEditable; } break; } case NodeModel::NodeConstraint: // constraint type if (! baselined && (n->type() == Node::Type_Task || n->type() == Node::Type_Milestone)) { flags |= Qt::ItemIsEditable; } break; case NodeModel::NodeConstraintStart: { // constraint start if (! baselined && n->type() == Node::Type_Project) { flags |= Qt::ItemIsEditable; break; } if (! baselined && ! (n->type() == Node::Type_Task || n->type() == Node::Type_Milestone)) { break; } flags |= Qt::ItemIsEditable; break; } case NodeModel::NodeConstraintEnd: { // constraint end if (! baselined && n->type() == Node::Type_Project) { flags |= Qt::ItemIsEditable; break; } if (! baselined && ! (n->type() == Node::Type_Task || n->type() == Node::Type_Milestone)) { break; } flags |= Qt::ItemIsEditable; break; } case NodeModel::NodeRunningAccount: // running account if (! baselined && n->type() == Node::Type_Task) { flags |= Qt::ItemIsEditable; } break; case NodeModel::NodeStartupAccount: // startup account case NodeModel::NodeStartupCost: // startup cost case NodeModel::NodeShutdownAccount: // shutdown account case NodeModel::NodeShutdownCost: { // shutdown cost if (! baselined && (n->type() == Node::Type_Task || n->type() == Node::Type_Milestone)) { flags |= Qt::ItemIsEditable; } break; } case NodeModel::NodeDescription: // description flags |= Qt::ItemIsEditable; break; default: break; } Task *t = static_cast(n); if (manager() && t->isScheduled(id())) { if (! t->completion().isStarted()) { switch (index.column()) { case NodeModel::NodeActualStart: flags |= Qt::ItemIsEditable; break; case NodeModel::NodeActualFinish: if (t->type() == Node::Type_Milestone) { flags |= Qt::ItemIsEditable; } break; case NodeModel::NodeCompleted: if (t->state() & Node::State_ReadyToStart) { flags |= Qt::ItemIsEditable; } break; default: break; } } else if (! t->completion().isFinished()) { switch (index.column()) { case NodeModel::NodeActualFinish: case NodeModel::NodeRemainingEffort: flags |= Qt::ItemIsEditable; break; case NodeModel::NodeCompleted: flags |= Qt::ItemIsEditable; break; case NodeModel::NodeActualEffort: if (t->completion().entrymode() == Completion::EnterEffortPerTask || t->completion().entrymode() == Completion::EnterEffortPerResource) { flags |= Qt::ItemIsEditable; } break; default: break; } } } } return flags; } QModelIndex NodeItemModel::parent(const QModelIndex &index) const { if (! index.isValid()) { return QModelIndex(); } Node *n = node(index); if (n == 0 || n == m_project) { return QModelIndex(); } Node *p = n->parentNode(); if (p == m_project) { return m_projectshown ? createIndex(0, 0, p) : QModelIndex(); } int row = p->parentNode()->indexOf(p); if (row == -1) { return QModelIndex(); } return createIndex(row, 0, p); } QModelIndex NodeItemModel::index(int row, int column, const QModelIndex &parent) const { if (parent.isValid()) { Q_ASSERT(parent.model() == this); } //debugPlan<= columnCount() || row < 0) { //debugPlan<= p->numChildren()) { errorPlan<name()<<" row too high"<numChildren(); return QModelIndex(); } // now get the internal pointer for the index Node *n = p->childNode(row); QModelIndex idx = createIndex(row, column, n); //debugPlan<parentNode(); if (par) { //debugPlan<"<indexOf(node), column, const_cast(node)); } if (m_projectshown && node == m_project) { return createIndex(0, column, m_project); } //debugPlan<(node); if (task == 0) { return false; } switch (role) { case Qt::EditRole: { MacroCommand *cmd = 0; QStringList res = m_project->resourceNameList(); QStringList req = node->requestNameList(); QStringList alloc; foreach (const QString &s, value.toString().split(QRegExp(" *, *"), QString::SkipEmptyParts)) { alloc << s.trimmed(); } // first add all new resources (to "default" group) ResourceGroup *pargr = m_project->groupByName(i18n("Resources")); foreach (const QString &s, alloc) { Resource *r = m_project->resourceByName(s.trimmed()); if (r != 0) { continue; } if (cmd == 0) cmd = new MacroCommand(kundo2_i18n("Add resource")); if (pargr == 0) { pargr = new ResourceGroup(); pargr->setName(i18n("Resources")); cmd->addCommand(new AddResourceGroupCmd(m_project, pargr)); //debugPlan<<"add group:"<name(); } r = new Resource(); r->setName(s.trimmed()); cmd->addCommand(new AddResourceCmd(pargr, r)); //debugPlan<<"add resource:"<name(); emit executeCommand(cmd); cmd = 0; } KUndo2MagicString c = kundo2_i18n("Modify resource allocations"); // Handle deleted requests foreach (const QString &s, req) { // if a request is not in alloc, it must have been be removed by the user if (alloc.indexOf(s) == -1) { // remove removed resource request ResourceRequest *r = node->resourceRequest(s); if (r) { if (cmd == 0) cmd = new MacroCommand(c); //debugPlan<<"delete request:"<resource()->name()<<" group:"<parent()->group()->name(); cmd->addCommand(new RemoveResourceRequestCmd(r->parent(), r)); } } } // Handle new requests QHash groupmap; foreach (const QString &s, alloc) { // if an allocation is not in req, it must be added if (req.indexOf(s) == -1) { ResourceGroup *pargr = 0; Resource *r = m_project->resourceByName(s); if (r == 0) { // Handle request to non existing resource pargr = m_project->groupByName(i18n("Resources")); if (pargr == 0) { pargr = new ResourceGroup(); pargr->setName(i18n("Resources")); cmd->addCommand(new AddResourceGroupCmd(m_project, pargr)); //debugPlan<<"add group:"<name(); } r = new Resource(); r->setName(s); cmd->addCommand(new AddResourceCmd(pargr, r)); //debugPlan<<"add resource:"<name(); emit executeCommand(cmd); cmd = 0; } else { pargr = r->parentGroups().value(0); // ### //debugPlan<<"add '"<name()<<"' to group:"<resourceGroupRequest(pargr); if (g == 0) { g = groupmap.value(pargr); } if (g == 0) { // create a group request if (cmd == 0) cmd = new MacroCommand(c); g = new ResourceGroupRequest(pargr); cmd->addCommand(new AddResourceGroupRequestCmd(*task, g)); groupmap.insert(pargr, g); //debugPlan<<"add group request:"<addCommand(new AddResourceRequestCmd(g, new ResourceRequest(r, r->units()))); //debugPlan<<"add request:"<name()<<" group:"<name()<type() == Node::Type_Task) { Completion &c = static_cast(node)->completion(); QDateTime dt = QDateTime::currentDateTime(); QDate date = dt.date(); MacroCommand *m = new MacroCommand(kundo2_i18n("Modify completion")); if (! c.isStarted()) { m->addCommand(new ModifyCompletionStartTimeCmd(c, dt)); m->addCommand(new ModifyCompletionStartedCmd(c, true)); } m->addCommand(new ModifyCompletionPercentFinishedCmd(c, date, value.toInt())); if (value.toInt() == 100) { m->addCommand(new ModifyCompletionFinishTimeCmd(c, dt)); m->addCommand(new ModifyCompletionFinishedCmd(c, true)); } emit executeCommand(m); // also adds a new entry if necessary if (c.entrymode() != Completion::EnterEffortPerResource) { Duration planned = static_cast(node)->plannedEffort(m_nodemodel.id()); Duration actual = (planned * value.toInt()) / 100; debugPlan<execute(); m->addCommand(cmd); cmd = new ModifyCompletionRemainingEffortCmd(c, date, planned - actual); cmd->execute(); m->addCommand(cmd); } return true; } if (node->type() == Node::Type_Milestone) { Completion &c = static_cast(node)->completion(); if (value.toInt() > 0) { QDateTime dt = QDateTime::currentDateTime(); QDate date = dt.date(); MacroCommand *m = new MacroCommand(kundo2_i18n("Set finished")); m->addCommand(new ModifyCompletionStartTimeCmd(c, dt)); m->addCommand(new ModifyCompletionStartedCmd(c, true)); m->addCommand(new ModifyCompletionFinishTimeCmd(c, dt)); m->addCommand(new ModifyCompletionFinishedCmd(c, true)); m->addCommand(new ModifyCompletionPercentFinishedCmd(c, date, 100)); emit executeCommand(m); // also adds a new entry if necessary return true; } return false; } return false; } QVariant NodeItemModel::data(const QModelIndex &index, int role) const { if (role == Qt::TextAlignmentRole) { return headerData(index.column(), Qt::Horizontal, role); } Node *n = node(index); if (role == Role::Object) { return n ? QVariant::fromValue(static_cast(n)) : QVariant(); } QVariant result; if (n != 0) { result = m_nodemodel.data(n, index.column(), role); //debugPlan<name()<<": "<numChildren(); } Qt::DropActions NodeItemModel::supportedDropActions() const { return Qt::CopyAction | Qt::MoveAction; } QStringList NodeItemModel::mimeTypes() const { return ItemModelBase::mimeTypes() << "application/x-vnd.kde.plan.nodeitemmodel.internal" << "application/x-vnd.kde.plan.resourceitemmodel.internal" << "application/x-vnd.kde.plan.project" << "application/x-vnd.kde.plan.taskmodule" << "text/uri-list"; } QMimeData *NodeItemModel::mimeData(const QModelIndexList & indexes) const { QMimeData *m = ItemModelBase::mimeData(indexes); QByteArray encodedData; QDataStream stream(&encodedData, QIODevice::WriteOnly); QList rows; foreach (const QModelIndex &index, indexes) { if (index.isValid() && !rows.contains(index.row())) { //debugPlan<id(); } } } m->setData("application/x-vnd.kde.plan.nodeitemmodel.internal", encodedData); QList nodes; foreach (const QModelIndex &index, indexes) { if (index.isValid()) { //debugPlan<setData("application/x-vnd.kde.plan.project", context.document.toByteArray()); } return m; } bool NodeItemModel::dropAllowed(const QModelIndex &index, int dropIndicatorPosition, const QMimeData *data) { debugPlan<hasFormat("application/x-vnd.kde.plan.resourceitemmodel.internal")) { switch (dropIndicatorPosition) { case ItemModelBase::OnItem: if (index.column() == NodeModel::NodeAllocation) { debugPlan<<"resource:"<type() == Node::Type_Task); return dn->type() == Node::Type_Task; } else if (index.column() == NodeModel::NodeResponsible) { debugPlan<<"resource:"<hasFormat("application/x-vnd.kde.plan.nodeitemmodel.internal") || data->hasFormat("application/x-vnd.kde.plan.project") || data->hasFormat("application/x-vnd.kde.plan.taskmodule") || data->hasUrls()) { switch (dropIndicatorPosition) { case ItemModelBase::AboveItem: case ItemModelBase::BelowItem: // dn == sibling, if not project if (dn == m_project) { return dropAllowed(dn, data); } return dropAllowed(dn->parentNode(), data); case ItemModelBase::OnItem: // dn == new parent return dropAllowed(dn, data); default: break; } } return false; } QList NodeItemModel::resourceList(QDataStream &stream) { QList lst; while (!stream.atEnd()) { QString id; stream >> id; debugPlan<<"id"<findResource(id); if (r) { lst << r; } } debugPlan<isBaselined() && on->type() != Node::Type_Summarytask && on->type() != Node::Type_Project) { return false; } if (data->hasFormat("application/x-vnd.kde.plan.nodeitemmodel.internal")) { QByteArray encodedData = data->data("application/x-vnd.kde.plan.nodeitemmodel.internal"); QDataStream stream(&encodedData, QIODevice::ReadOnly); QList lst = nodeList(stream); foreach (Node *n, lst) { if (n->type() == Node::Type_Project || on == n || on->isChildOf(n)) { return false; } } lst = removeChildNodes(lst); foreach (Node *n, lst) { if (! m_project->canMoveTask(n, on)) { return false; } } } return true; } QList NodeItemModel::nodeList(QDataStream &stream) { QList lst; while (!stream.atEnd()) { QString id; stream >> id; Node *node = m_project->findNode(id); if (node) { lst << node; } } return lst; } QList NodeItemModel::removeChildNodes(const QList &nodes) { QList lst; foreach (Node *node, nodes) { bool ins = true; foreach (Node *n, lst) { if (node->isChildOf(n)) { //debugPlan<name()<<" is child of"<name(); ins = false; break; } } if (ins) { //debugPlan<<" insert"<name(); lst << node; } } QList nl = lst; QList nlst = lst; foreach (Node *node, nl) { foreach (Node *n, nlst) { if (n->isChildOf(node)) { //debugPlan<name()<<" is child of"<name(); int i = nodes.indexOf(n); lst.removeAt(i); } } } return lst; } bool NodeItemModel::dropResourceMimeData(const QMimeData *data, Qt::DropAction action, int /*row*/, int /*column*/, const QModelIndex &parent) { QByteArray encodedData = data->data("application/x-vnd.kde.plan.resourceitemmodel.internal"); QDataStream stream(&encodedData, QIODevice::ReadOnly); Node *n = node(parent); debugPlan<name(); if (parent.column() == NodeModel::NodeResponsible) { QString s; foreach (Resource *r, resourceList(stream)) { s += r->name(); } if (! s.isEmpty()) { if (action == Qt::CopyAction && ! n->leader().isEmpty()) { s += ',' + n->leader(); } KUndo2Command *cmd = m_nodemodel.setLeader(n, s, Qt::EditRole); if (cmd) { emit executeCommand(cmd); } debugPlan<type() == Node::Type_Task) { QList lst = resourceList(stream); if (action == Qt::CopyAction) { lst += static_cast(n)->requestedResources(); } KUndo2Command *cmd = createAllocationCommand(static_cast(*n), lst); if (cmd) { emit executeCommand(cmd); } return true; } return true; } bool NodeItemModel::dropProjectMimeData(const QMimeData *data, Qt::DropAction action, int row, int /*column*/, const QModelIndex &parent) { Node *n = node(parent); if (n == 0) { n = m_project; } debugPlan<data("application/x-vnd.kde.plan.project"), n, n->childNode(row), kundo2_i18n("Insert tasks")); emit executeCommand(cmd); return true; } bool NodeItemModel::dropTaskModuleMimeData(const QMimeData *data, Qt::DropAction action, int row, int /*column*/, const QModelIndex &parent) { Node *n = node(parent); if (n == 0) { n = m_project; } debugPlan<data("application/x-vnd.kde.plan.taskmodule")); QStringList substitute; while (it.hasNext()) { QRegularExpressionMatch match = it.next(); QString param = match.captured().remove("[[").remove("]]"); if (!substitute.contains(param)) { substitute << param; } } QMap params; if (!substitute.isEmpty()) { ParameterSubstitutionDialog dlg(substitute); dlg.setCaption(xi18nc("@title:window", "Task Module Parameters")); if (!dlg.exec()) { return false; } params = dlg.parameters(); } KUndo2Command *cmd = new InsertTaskModuleCommand(project(), data->data("application/x-vnd.kde.plan.taskmodule"), n, n->childNode(row), params, kundo2_i18n("Insert task module")); emit executeCommand(cmd); return true; } bool NodeItemModel::dropUrlMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { if (data->hasUrls()) { QList urls = data->urls(); debugPlan<bad()) { // d->lastErrorMessage = i18n("Not a valid Calligra file: %1", file); debugPlan<<"bad store"<open("root")) { // maindoc.xml debugPlan<<"No root"<device()); KoXmlElement element = doc.documentElement().namedItem("project").toElement(); Project project; XMLLoaderObject status; status.setVersion(doc.documentElement().attribute("version", PLAN_FILE_SYNTAX_VERSION)); status.setProject(&project); if (! project.load(element, status)) { debugPlan<<"Failed to load project from:"<childNode(row - 1), kundo2_i18n("Insert %1", url.fileName())); emit executeCommand(cmd); return true; } KUndo2Command *NodeItemModel::createAllocationCommand(Task &task, const QList &lst) { MacroCommand *cmd = new MacroCommand(kundo2_i18n("Modify resource allocations")); QHash groups; foreach (Resource *r, lst) { if (! groups.contains(r->parentGroups().value(0)) && task.resourceGroupRequest(r->parentGroups().value(0)) == 0) { ResourceGroupRequest *gr = new ResourceGroupRequest(r->parentGroups().value(0)); groups[ r->parentGroups().value(0) ] = gr; cmd->addCommand(new AddResourceGroupRequestCmd(task, gr)); } } QList resources = task.requestedResources(); foreach (Resource *r, lst) { if (resources.contains(r)) { continue; } ResourceGroupRequest *gr = groups.value(r->parentGroups().value(0)); if (gr == 0) { gr = task.resourceGroupRequest(r->parentGroups().value(0)); } if (gr == 0) { errorPlan<<"No group request found, cannot add resource request:"<name(); continue; } cmd->addCommand(new AddResourceRequestCmd(gr, new ResourceRequest(r, 100))); } foreach (Resource *r, resources) { if (! lst.contains(r)) { ResourceGroupRequest *gr = task.resourceGroupRequest(r->parentGroups().value(0)); ResourceRequest *rr = task.requests().find(r); if (gr && rr) { cmd->addCommand(new RemoveResourceRequestCmd(gr, rr)); } } } if (cmd->isEmpty()) { delete cmd; return 0; } return cmd; } bool NodeItemModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) { debugPlan<formats(); if (action == Qt::IgnoreAction) { return true; } if (data->hasFormat("application/x-vnd.kde.plan.resourceitemmodel.internal")) { return dropResourceMimeData(data, action, row, column, parent); } if (data->hasFormat("application/x-vnd.kde.plan.nodeitemmodel.internal")) { if (action == Qt::MoveAction) { //debugPlan<<"MoveAction"; QByteArray encodedData = data->data("application/x-vnd.kde.plan.nodeitemmodel.internal"); QDataStream stream(&encodedData, QIODevice::ReadOnly); Node *par = 0; if (parent.isValid()) { par = node(parent); } else { par = m_project; } QList lst = nodeList(stream); QList nodes = removeChildNodes(lst); // children goes with their parent foreach (Node *n, nodes) { if (! m_project->canMoveTask(n, par)) { //debugPlan<<"Can't move task:"<name(); return false; } } int offset = 0; MacroCommand *cmd = 0; foreach (Node *n, nodes) { if (cmd == 0) cmd = new MacroCommand(kundo2_i18n("Move tasks")); // append nodes if dropped *on* another node, insert if dropped *after* int pos = row == -1 ? -1 : row + offset; if (pos >= 0 && n->parentNode() == par && par->indexOf(n) < pos) { --pos; } if (n->parentNode() == par) { // avoid drop into the same position, QAbstractItemModel does not like it int crow = par->indexOf(n); if (((pos == -1) && (crow == par->numChildren() - 1)) || (pos == crow)) { delete cmd; cmd = 0; continue; } } cmd->addCommand(new NodeMoveCmd(m_project, n, par, pos)); offset++; } if (cmd) { emit executeCommand(cmd); } //debugPlan<name(); return true; } } if (data->hasFormat("application/x-vnd.kde.plan.project")) { debugPlan; return dropProjectMimeData(data, action, row, column, parent); } if (data->hasFormat("application/x-vnd.kde.plan.taskmodule")) { debugPlan; return dropTaskModuleMimeData(data, action, row, column, parent); } if (data->hasUrls()) { return dropUrlMimeData(data, action, row, column, parent); } return false; } Node *NodeItemModel::node(const QModelIndex &index) const { Node *n = m_project; if (index.isValid()) { //debugPlan<(index.internalPointer()); Q_ASSERT(n); } return n; } void NodeItemModel::slotNodeChanged(Node *node, int property) { if (node == 0 || (! m_projectshown && node->type() == Node::Type_Project)) { return; } if (node->type() == Node::Type_Project) { emit dataChanged(createIndex(0, 0, node), createIndex(0, columnCount()-1, node)); return; } int row = node->parentNode()->findChildNode(node); Q_ASSERT(row >= 0); emit dataChanged(createIndex(row, 0, node), createIndex(row, columnCount()-1, node)); } QModelIndex NodeItemModel::insertTask(Node *node, Node *after) { MacroCommand *cmd = new MacroCommand(kundo2_i18n("Add task")); cmd->addCommand(new TaskAddCmd(m_project, node, after)); if (m_project && node->type() == Node::Type_Task) { QHash groups; foreach (Resource *r, m_project->autoAllocateResources()) { if (! groups.contains(r->parentGroups().value(0))) { ResourceGroupRequest *gr = new ResourceGroupRequest(r->parentGroups().value(0)); cmd->addCommand(new AddResourceGroupRequestCmd(static_cast(*node), gr)); groups[ r->parentGroups().value(0) ] = gr; } ResourceRequest *rr = new ResourceRequest(r, 100); cmd->addCommand(new AddResourceRequestCmd(groups[ r->parentGroups().value(0) ], rr)); } } emit executeCommand(cmd); int row = -1; if (node->parentNode()) { row = node->parentNode()->indexOf(node); } if (row != -1) { //debugPlan<<"Inserted: "<name()<<"; "<name(); return QModelIndex(); } QModelIndex NodeItemModel::insertSubtask(Node *node, Node *parent) { emit executeCommand(new SubtaskAddCmd(m_project, node, parent, kundo2_i18n("Add sub-task"))); int row = -1; if (node->parentNode()) { row = node->parentNode()->indexOf(node); } if (row != -1) { //debugPlan<parentNode()<<" inserted: "<name()<<"; "<name(); return QModelIndex(); } int NodeItemModel::sortRole(int column) const { int v = Qt::DisplayRole; switch (column) { case NodeModel::NodeStartTime: case NodeModel::NodeEndTime: case NodeModel::NodeActualStart: case NodeModel::NodeActualFinish: case NodeModel::NodeEarlyStart: case NodeModel::NodeEarlyFinish: case NodeModel::NodeLateStart: case NodeModel::NodeLateFinish: case NodeModel::NodeConstraintStart: case NodeModel::NodeConstraintEnd: v = Qt::EditRole; break; case NodeModel::NodeWBSCode: v = NodeModel::SortableRole; break; default: break; } debugPlan< lst = parentmap.values(); while (! lst.isEmpty()) delete (int*)(lst.takeFirst()); } int GanttItemModel::rowCount(const QModelIndex &parent) const { if (m_showSpecial) { if (parentmap.values().contains(parent.internalPointer())) { // clazy:exclude=container-anti-pattern return 0; } Node *n = node(parent); if (n && n->type() == Node::Type_Task) { return 5; // the task + early start + late finish ++ } } return NodeItemModel::rowCount(parent); } QModelIndex GanttItemModel::index(int row, int column, const QModelIndex &parent) const { if (m_showSpecial && parent.isValid()) { Node *p = node(parent); if (p->type() == Node::Type_Task) { void *v = 0; foreach (void *i, parentmap.values(p)) { // clazy:exclude=container-anti-pattern if (*((int*)(i)) == row) { v = i; break; } } if (v == 0) { v = new int(row); const_cast(this)->parentmap.insertMulti(p, v); } return createIndex(row, column, v); } } return NodeItemModel::index(row, column, parent); } QModelIndex GanttItemModel::parent(const QModelIndex &idx) const { if (m_showSpecial) { QList lst = parentmap.keys(idx.internalPointer()); if (! lst.isEmpty()) { Q_ASSERT(lst.count() == 1); return index(lst.first()); } } return NodeItemModel::parent(idx); } QVariant GanttItemModel::data(const QModelIndex &index, int role) const { if (! index.isValid()) { return QVariant(); } if (role == Qt::TextAlignmentRole) { return headerData(index.column(), Qt::Horizontal, role); } QModelIndex idx = index; QList lst; if (m_showSpecial) { lst = parentmap.keys(idx.internalPointer()); } if (! lst.isEmpty()) { Q_ASSERT(lst.count() == 1); int row = *((int*)(idx.internalPointer())); Node *n = lst.first(); if (role == SpecialItemTypeRole) { return row; // 0=task, 1=early start, 2=late finish... } switch (row) { case 0: // the task if (idx.column() == NodeModel::NodeType && role == KGantt::ItemTypeRole) { switch (n->type()) { case Node::Type_Task: return KGantt::TypeTask; default: break; } } break; case 1: { // early start if (role != Qt::DisplayRole && role != Qt::EditRole && role != KGantt::ItemTypeRole) { return QVariant(); } switch (idx.column()) { case NodeModel::NodeName: return "Early Start"; case NodeModel::NodeType: return KGantt::TypeEvent; case NodeModel::NodeStartTime: case NodeModel::NodeEndTime: return n->earlyStart(id()); default: break; } return QVariant(); } case 2: { // late finish if (role != Qt::DisplayRole && role != Qt::EditRole && role != KGantt::ItemTypeRole) { return QVariant(); } switch (idx.column()) { case NodeModel::NodeName: return "Late Finish"; case NodeModel::NodeType: return KGantt::TypeEvent; case NodeModel::NodeStartTime: case NodeModel::NodeEndTime: return n->lateFinish(id()); default: break; } return QVariant(); } case 3: { // late start if (role != Qt::DisplayRole && role != Qt::EditRole && role != KGantt::ItemTypeRole) { return QVariant(); } switch (idx.column()) { case NodeModel::NodeName: return "Late Start"; case NodeModel::NodeType: return KGantt::TypeEvent; case NodeModel::NodeStartTime: case NodeModel::NodeEndTime: return n->lateStart(id()); default: break; } return QVariant(); } case 4: { // early finish if (role != Qt::DisplayRole && role != Qt::EditRole && role != KGantt::ItemTypeRole) { return QVariant(); } switch (idx.column()) { case NodeModel::NodeName: return "Early Finish"; case NodeModel::NodeType: return KGantt::TypeEvent; case NodeModel::NodeStartTime: case NodeModel::NodeEndTime: return n->earlyFinish(id()); default: break; } return QVariant(); } default: return QVariant(); } idx = createIndex(idx.row(), idx.column(), n); } else { if (role == SpecialItemTypeRole) { return 0; // task of some type } if (idx.column() == NodeModel::NodeType && role == KGantt::ItemTypeRole) { QModelIndex notScheduled = idx.sibling(idx.row(), NodeModel::NodeNotScheduled); if (notScheduled.data(Qt::EditRole).toBool()) { return QVariant(); } QVariant result = NodeItemModel::data(idx, Qt::EditRole); switch (result.toInt()) { case Node::Type_Project: return KGantt::TypeSummary; case Node::Type_Summarytask: return KGantt::TypeSummary; case Node::Type_Milestone: return KGantt::TypeEvent; default: return m_showSpecial ? KGantt::TypeMulti : KGantt::TypeTask; } } } return NodeItemModel::data(idx, role); } //---------------------------- MilestoneItemModel::MilestoneItemModel(QObject *parent) : ItemModelBase(parent) { } MilestoneItemModel::~MilestoneItemModel() { } QList MilestoneItemModel::mileStones() const { QList lst; foreach(Node* n, m_nodemap) { if (n->type() == Node::Type_Milestone) { lst << n; } } return lst; } void MilestoneItemModel::slotNodeToBeInserted(Node *parent, int row) { Q_UNUSED(parent); Q_UNUSED(row); } void MilestoneItemModel::slotNodeInserted(Node *node) { Q_UNUSED(node); resetModel(); } void MilestoneItemModel::slotNodeToBeRemoved(Node *node) { Q_UNUSED(node); //debugPlan<name(); /* int row = m_nodemap.values().indexOf(node); if (row != -1) { Q_ASSERT(m_nodemap.contains(node->wbsCode())); Q_ASSERT(m_nodemap.keys().indexOf(node->wbsCode()) == row); beginRemoveRows(QModelIndex(), row, row); m_nodemap.remove(node->wbsCode()); endRemoveRows(); }*/ } void MilestoneItemModel::slotNodeRemoved(Node *node) { Q_UNUSED(node); resetModel(); //endRemoveRows(); } void MilestoneItemModel::slotLayoutChanged() { //debugPlan<name(); emit layoutAboutToBeChanged(); emit layoutChanged(); } void MilestoneItemModel::slotNodeToBeMoved(Node *node, int pos, Node *newParent, int newPos) { Q_UNUSED(node); Q_UNUSED(pos); Q_UNUSED(newParent); Q_UNUSED(newPos); } void MilestoneItemModel::slotNodeMoved(Node *node) { Q_UNUSED(node); resetModel(); } void MilestoneItemModel::setProject(Project *project) { if (m_project) { disconnect(m_project, &Project::aboutToBeDeleted, this, &MilestoneItemModel::projectDeleted); disconnect(m_project, SIGNAL(localeChanged()), this, SLOT(slotLayoutChanged())); disconnect(m_project, &Project::wbsDefinitionChanged, this, &MilestoneItemModel::slotWbsDefinitionChanged); disconnect(m_project, &Project::nodeChanged, this, &MilestoneItemModel::slotNodeChanged); disconnect(m_project, &Project::nodeToBeAdded, this, &MilestoneItemModel::slotNodeToBeInserted); disconnect(m_project, &Project::nodeToBeRemoved, this, &MilestoneItemModel::slotNodeToBeRemoved); disconnect(m_project, &Project::nodeToBeMoved, this, &MilestoneItemModel::slotNodeToBeMoved); disconnect(m_project, &Project::nodeMoved, this, &MilestoneItemModel::slotNodeMoved); disconnect(m_project, &Project::nodeAdded, this, &MilestoneItemModel::slotNodeInserted); disconnect(m_project, &Project::nodeRemoved, this, &MilestoneItemModel::slotNodeRemoved); } m_project = project; //debugPlan<"<allNodes()) { m_nodemap.insert(n->wbsCode(true), n); } } return cnt != m_nodemap.count(); } void MilestoneItemModel::resetModel() { beginResetModel(); resetData(); endResetModel(); } Qt::ItemFlags MilestoneItemModel::flags(const QModelIndex &index) const { Qt::ItemFlags flags = QAbstractItemModel::flags(index); if (!index.isValid()) { if (m_readWrite) { flags |= Qt::ItemIsDropEnabled; } return flags; } flags |= Qt::ItemIsDragEnabled; if (m_readWrite) { flags |= Qt::ItemIsDropEnabled; switch (index.column()) { case NodeModel::NodeName: // name if (!isColumnReadOnly(index.column())) { flags |= Qt::ItemIsEditable; } break; case NodeModel::NodeType: break; // Node type case NodeModel::NodeResponsible: // Responsible if (!isColumnReadOnly(index.column())) { flags |= Qt::ItemIsEditable; } break; case NodeModel::NodeConstraint: // constraint type if (!isColumnReadOnly(index.column())) { flags |= Qt::ItemIsEditable; } break; case NodeModel::NodeConstraintStart: { // constraint start if (!isColumnReadOnly(index.column())) { Node *n = node(index); if (n == 0) break; int c = n->constraint(); if (c == Node::MustStartOn || c == Node::StartNotEarlier || c == Node::FixedInterval) { flags |= Qt::ItemIsEditable; } } break; } case NodeModel::NodeConstraintEnd: { // constraint end if (!isColumnReadOnly(index.column())) { Node *n = node(index); if (n == 0) break; int c = n->constraint(); if (c == Node::MustFinishOn || c == Node::FinishNotLater || c == Node::FixedInterval) { flags |= Qt::ItemIsEditable; } } break; } case NodeModel::NodeStartupAccount: // startup account case NodeModel::NodeStartupCost: // startup cost case NodeModel::NodeShutdownAccount: // shutdown account case NodeModel::NodeShutdownCost: { // shutdown cost if (!isColumnReadOnly(index.column())) { Node *n = node(index); if (n && (n->type() == Node::Type_Task || n->type() == Node::Type_Milestone)) { flags |= Qt::ItemIsEditable; } } break; } case NodeModel::NodeDescription: // description break; case NodeModel::NodeCompleted: if (!isColumnReadOnly(index.column())) { flags |= Qt::ItemIsEditable; } break; default: flags &= ~Qt::ItemIsEditable; } } return flags; } QModelIndex MilestoneItemModel::parent(const QModelIndex &index) const { Q_UNUSED(index); return QModelIndex(); } QModelIndex MilestoneItemModel::index(int row, int column, const QModelIndex &parent) const { //debugPlan<= m_nodemap.count()) { //debugPlan<<"No index for"<(node)), 0, const_cast(node)); // clazy:exclude=container-anti-pattern } QVariant MilestoneItemModel::data(const QModelIndex &index, int role) const { QVariant result; if (role == Qt::TextAlignmentRole) { return headerData(index.column(), Qt::Horizontal, role); } Node *n = node(index); if (n != 0) { if (index.column() == NodeModel::NodeType && role == KGantt::ItemTypeRole) { result = m_nodemodel.data(n, index.column(), Qt::EditRole); switch (result.toInt()) { case Node::Type_Summarytask: return KGantt::TypeSummary; case Node::Type_Milestone: return KGantt::TypeEvent; default: return KGantt::TypeTask; } return result; } } result = m_nodemodel.data(n, index.column(), role); return result; } bool MilestoneItemModel::setData(const QModelIndex &index, const QVariant &/*value*/, int role) { if ((flags(index) &Qt::ItemIsEditable) == 0 || role != Qt::EditRole) { return false; } // Node *n = node(index); switch (index.column()) { default: qWarning("data: invalid display value column %d", index.column()); return false; } return false; } QVariant MilestoneItemModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal) { if (role == Qt::DisplayRole || role == Qt::TextAlignmentRole || role == Qt::EditRole) { return m_nodemodel.headerData(section, role); } } if (role == Qt::ToolTipRole) { return NodeModel::headerData(section, role); } return ItemModelBase::headerData(section, orientation, role); } QAbstractItemDelegate *MilestoneItemModel::createDelegate(int column, QWidget *parent) const { switch (column) { case NodeModel::NodeEstimateType: return new EnumDelegate(parent); case NodeModel::NodeEstimateCalendar: return new EnumDelegate(parent); case NodeModel::NodeEstimate: return new DurationSpinBoxDelegate(parent); case NodeModel::NodeOptimisticRatio: return new SpinBoxDelegate(parent); case NodeModel::NodePessimisticRatio: return new SpinBoxDelegate(parent); case NodeModel::NodeRisk: return new EnumDelegate(parent); case NodeModel::NodeConstraint: return new EnumDelegate(parent); case NodeModel::NodeRunningAccount: return new EnumDelegate(parent); case NodeModel::NodeStartupAccount: return new EnumDelegate(parent); case NodeModel::NodeStartupCost: return new MoneyDelegate(parent); case NodeModel::NodeShutdownAccount: return new EnumDelegate(parent); case NodeModel::NodeShutdownCost: return new MoneyDelegate(parent); case NodeModel::NodeCompleted: return new TaskCompleteDelegate(parent); case NodeModel::NodeRemainingEffort: return new DurationSpinBoxDelegate(parent); case NodeModel::NodeActualEffort: return new DurationSpinBoxDelegate(parent); default: return 0; } return 0; } int MilestoneItemModel::columnCount(const QModelIndex &/*parent*/) const { return m_nodemodel.propertyCount(); } int MilestoneItemModel::rowCount(const QModelIndex &parent) const { //debugPlan< rows; foreach (const QModelIndex &index, indexes) { if (index.isValid() && !rows.contains(index.row())) { //debugPlan<id(); } } } m->setData("application/x-vnd.kde.plan.nodeitemmodel.internal", encodedData); return m; } bool MilestoneItemModel::dropAllowed(const QModelIndex &index, int dropIndicatorPosition, const QMimeData *data) { //debugPlan; Node *dn = node(index); if (dn == 0) { errorPlan<<"no node to drop on!"; return false; // hmmm } switch (dropIndicatorPosition) { case ItemModelBase::AboveItem: case ItemModelBase::BelowItem: // dn == sibling return dropAllowed(dn->parentNode(), data); case ItemModelBase::OnItem: // dn == new parent return dropAllowed(dn, data); default: break; } return false; } bool MilestoneItemModel::dropAllowed(Node *on, const QMimeData *data) { if (!data->hasFormat("application/x-vnd.kde.plan.nodeitemmodel.internal")) { return false; } if (on == m_project) { return true; } QByteArray encodedData = data->data("application/x-vnd.kde.plan.nodeitemmodel.internal"); QDataStream stream(&encodedData, QIODevice::ReadOnly); QList lst = nodeList(stream); foreach (Node *n, lst) { if (on == n || on->isChildOf(n)) { return false; } } lst = removeChildNodes(lst); foreach (Node *n, lst) { if (! m_project->canMoveTask(n, on)) { return false; } } return true; } QList MilestoneItemModel::nodeList(QDataStream &stream) { QList lst; while (!stream.atEnd()) { QString id; stream >> id; Node *node = m_project->findNode(id); if (node) { lst << node; } } return lst; } QList MilestoneItemModel::removeChildNodes(const QList &nodes) { QList lst; foreach (Node *node, nodes) { bool ins = true; foreach (Node *n, lst) { if (node->isChildOf(n)) { //debugPlan<name()<<" is child of"<name(); ins = false; break; } } if (ins) { //debugPlan<<" insert"<name(); lst << node; } } QList nl = lst; QList nlst = lst; foreach (Node *node, nl) { foreach (Node *n, nlst) { if (n->isChildOf(node)) { //debugPlan<name()<<" is child of"<name(); int i = nodes.indexOf(n); lst.removeAt(i); } } } return lst; } bool MilestoneItemModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int /*column*/, const QModelIndex &parent) { //debugPlan<hasFormat("application/x-vnd.kde.plan.nodeitemmodel.internal")) { return false; } if (action == Qt::MoveAction) { //debugPlan<<"MoveAction"; QByteArray encodedData = data->data("application/x-vnd.kde.plan.nodeitemmodel.internal"); QDataStream stream(&encodedData, QIODevice::ReadOnly); Node *par = 0; if (parent.isValid()) { par = node(parent); } else { par = m_project; } QList lst = nodeList(stream); QList nodes = removeChildNodes(lst); // children goes with their parent foreach (Node *n, nodes) { if (! m_project->canMoveTask(n, par)) { //debugPlan<<"Can't move task:"<name(); return false; } } int offset = 0; MacroCommand *cmd = 0; foreach (Node *n, nodes) { if (cmd == 0) cmd = new MacroCommand(kundo2_i18n("Move tasks")); // append nodes if dropped *on* another node, insert if dropped *after* int pos = row == -1 ? -1 : row + offset; cmd->addCommand(new NodeMoveCmd(m_project, n, par, pos)); offset++; } if (cmd) { emit executeCommand(cmd); } //debugPlan<name(); return true; } return false; } Node *MilestoneItemModel::node(const QModelIndex &index) const { Node *n = 0; if (index.isValid()) { //debugPlan<(index.internalPointer()); } return n; } void MilestoneItemModel::slotNodeChanged(Node *node) { //debugPlan<name(); if (node == 0) { return; } beginResetModel(); resetData(); endResetModel(); } void MilestoneItemModel::slotWbsDefinitionChanged() { //debugPlan; if (m_project == 0) { return; } if (! m_nodemap.isEmpty()) { beginResetModel(); resetData(); endResetModel(); } } int MilestoneItemModel::sortRole(int column) const { int v = Qt::DisplayRole; switch (column) { case NodeModel::NodeStartTime: case NodeModel::NodeEndTime: case NodeModel::NodeActualStart: case NodeModel::NodeActualFinish: case NodeModel::NodeEarlyStart: case NodeModel::NodeEarlyFinish: case NodeModel::NodeLateStart: case NodeModel::NodeLateFinish: case NodeModel::NodeConstraintStart: case NodeModel::NodeConstraintEnd: v = Qt::EditRole; break; case NodeModel::NodeWBSCode: v = NodeModel::SortableRole; break; default: break; } return v; } //-------------- NodeSortFilterProxyModel::NodeSortFilterProxyModel(ItemModelBase* model, QObject *parent, bool filterUnscheduled) : QSortFilterProxyModel(parent), m_filterUnscheduled(filterUnscheduled) { setSourceModel(model); setDynamicSortFilter(true); } ItemModelBase *NodeSortFilterProxyModel::itemModel() const { return static_cast(sourceModel()); } void NodeSortFilterProxyModel::setFilterUnscheduled(bool on) { m_filterUnscheduled = on; invalidateFilter(); } bool NodeSortFilterProxyModel::filterAcceptsRow (int row, const QModelIndex & parent) const { //debugPlan<project() == 0) { //debugPlan<project(); return false; } if (m_filterUnscheduled) { QString s = sourceModel()->data(sourceModel()->index(row, NodeModel::NodeNotScheduled, parent), Qt::EditRole).toString(); if (s == "true") { //debugPlan<<"Filtered unscheduled:"<index(row, 0, parent); return false; } } bool accepted = QSortFilterProxyModel::filterAcceptsRow(row, parent); //debugPlan<index(row, 0, parent)<<"accepted ="<sortRole(column)); QSortFilterProxyModel::sort(column, order); } //------------------ TaskModuleModel::TaskModuleModel(QObject *parent) : QAbstractItemModel(parent) , m_project(nullptr) { } void TaskModuleModel::setProject(Project *project) { if (m_project) { disconnect(m_project, &Project::taskModulesChanged, this, &TaskModuleModel::slotTaskModulesChanged); } m_project = project; if (m_project) { connect(m_project, &Project::taskModulesChanged, this, &TaskModuleModel::slotTaskModulesChanged); } slotReset(); } void TaskModuleModel::slotReset() { slotTaskModulesChanged(m_project ? m_project->taskModules() : QList()); } void TaskModuleModel::addTaskModule(Project *project, const QUrl &url) { beginInsertRows(QModelIndex(), m_modules.count(), m_modules.count()); m_modules << project; m_urls << url; endInsertRows(); } Qt::ItemFlags TaskModuleModel::flags(const QModelIndex &idx) const { Qt::ItemFlags f = QAbstractItemModel::flags(idx) | Qt::ItemIsDropEnabled; if (idx.isValid()) { f |= Qt::ItemIsDragEnabled; } return f; } int TaskModuleModel::columnCount (const QModelIndex &/*idx*/) const { return 1; } int TaskModuleModel::rowCount(const QModelIndex &idx) const { return idx.isValid() ? 0 : m_modules.count(); } QVariant TaskModuleModel::data(const QModelIndex& idx, int role) const { if (!idx.isValid() || idx.row() >= m_modules.count()) { return QVariant(); } switch (role) { case Qt::DisplayRole: return m_modules.value(idx.row())->name(); case Qt::ToolTipRole: return m_modules.value(idx.row())->description(); case Qt::WhatsThisRole: return QVariant(); case Qt::UserRole: return m_urls.value(idx.row()); default: break; } return QVariant(); } QVariant TaskModuleModel::headerData(int /*section*/, Qt::Orientation orientation , int role) const { if (orientation == Qt::Horizontal) { switch (role) { case Qt::DisplayRole: return xi18nc("@title:column", "Name"); default: break; } } return QVariant(); } QModelIndex TaskModuleModel::parent(const QModelIndex& /*idx*/) const { return QModelIndex(); } QModelIndex TaskModuleModel::index(int row, int column, const QModelIndex &parent) const { if (parent.isValid()) { return QModelIndex(); } return createIndex(row, column, m_modules.value(row)); } QStringList TaskModuleModel::mimeTypes() const { return QStringList() << "application/x-vnd.kde.plan" << "text/uri-list"; } bool TaskModuleModel::dropMimeData(const QMimeData *data, Qt::DropAction /*action*/, int /*row*/, int /*column*/, const QModelIndex &/*parent*/) { if (data->hasUrls()) { QList urls = data->urls(); debugPlan<bad()) { // d->lastErrorMessage = i18n("Not a valid Calligra file: %1", file); warnPlan<open("root")) { // maindoc.xml warnPlan<device()); KoXmlElement element = doc.documentElement().namedItem("project").toElement(); Project *project = new Project(); XMLLoaderObject status; status.setVersion(doc.documentElement().attribute("version", PLAN_FILE_SYNTAX_VERSION)); status.setProject(project); if (project->load(element, status)) { stripProject(project); } else { warnPlan<setData("application/x-vnd.kde.plan.taskmodule", context.document.toByteArray()); delete project; } } } return mime; } void TaskModuleModel::stripProject(Project *project) const { foreach (ScheduleManager *sm, project->scheduleManagers()) { DeleteScheduleManagerCmd c(*project, sm); } } void TaskModuleModel::loadTaskModules(const QStringList &files) { debugPlan< &modules) { debugPlan< 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 "InsertProjectXmlCommandTester.h" #include "kptcommand.h" #include "kptcalendar.h" #include "kptdatetime.h" #include "kptresource.h" #include "kptnode.h" #include "kpttask.h" #include "kptschedule.h" #include "kptappointment.h" #include "XmlSaveContext.h" #include "InsertProjectXmlCommand.h" #include #include -namespace QTest -{ - template<> - char *toString(const KPlato::DateTime &dt) - { - return toString(dt.toString()); - } -} - +#include using namespace KPlato; -void InsertProjectXmlCommandTester::printDebug(Project *project) const -{ - Project *p = project; - qInfo()<<"Debug info -------------------------------------"; - qInfo()<<"Project:"<allTasks().count(); - for (int i = 0; i < p->numChildren(); ++i) { - qInfo()<childNode(i); - const ResourceRequestCollection &coll = p->childNode(i)->requests(); - qInfo()<<"\tRequests:"<childNode(i)->requests().requests().at(g); - qInfo()<<"\tGroup:"<group(); - for (int r = 0; r < gr->resourceRequests().count(); ++r) { - ResourceRequest *rr = gr->resourceRequests().at(r); - qInfo()<<"\t\tResource:"<resource(); - } - } - } -} - Project *createProject() { Project *project = new Project(); project->setName("P1"); project->setId(project->uniqueNodeId()); project->registerNodeId(project); DateTime targetstart = DateTime(QDate::currentDate(), QTime(0,0,0)); DateTime targetend = DateTime(targetstart.addDays(3)); project->setConstraintStartTime(targetstart); project->setConstraintEndTime(targetend); // standard worktime defines 8 hour day as default Calendar *calendar = new Calendar("Test"); calendar->setDefault(true); QTime t1(9, 0, 0); QTime t2 (17, 0, 0); int length = t1.msecsTo(t2); for (int i=1; i <= 7; ++i) { CalendarDay *d = calendar->weekday(i); d->setState(CalendarDay::Working); d->addInterval(t1, length); } project->addCalendar(calendar); return project; } void addResources(Project *project) { ResourceGroup *g = new ResourceGroup(); g->setName("G1"); project->addResourceGroup(g); Resource *resource = new Resource(); resource->setName("R1"); resource->setCalendar(project->calendars().value(0)); project->addResource(resource); g->addResource(resource); } void addTask(Project *project, const QString &name, Task *parent = 0) { Task *task = project->createTask(); task->setName(name); project->addSubTask(task, parent ? (Node*)parent : (Node*)project); task->estimate()->setUnit(Duration::Unit_h); task->estimate()->setExpectedEstimate(8.0); task->estimate()->setType(Estimate::Type_Effort); } void addRequests(Node *task, ResourceGroup *g, Resource *r) { ResourceGroupRequest *gr = new ResourceGroupRequest(g); - gr->addResourceRequest(new ResourceRequest(r, 100)); + ResourceRequest *rr = new ResourceRequest(r, 100); task->requests().addRequest(gr); + task->requests().addResourceRequest(rr, gr); } void addDependency(Node *t1, Node *t2) { t1->addDependChildNode(t2, Relation::FinishStart); } void InsertProjectXmlCommandTester::init() { m_project = createProject(); } void InsertProjectXmlCommandTester::cleanup() { - printDebug(m_project); + Debug::print(m_project, "--------------------", true); delete m_project; } void InsertProjectXmlCommandTester::copyBasics() { addTask(m_project, "T1"); QList old = m_project->childNodeIterator(); XmlSaveContext context(m_project); - context.options = XmlSaveContext::SaveNodes; + context.options = XmlSaveContext::SaveSelectedNodes; context.nodes << m_project->childNode(0); context.save(); qInfo()<childNode(0)); cmd.redo(); QCOMPARE(m_project->allTasks().count(), 2); QVERIFY(!old.contains(m_project->childNode(0))); QCOMPARE(m_project->childNode(1), old.at(0)); } void InsertProjectXmlCommandTester::copyRequests() { addResources(m_project); addTask(m_project, "T1"); addTask(m_project, "T2"); addRequests(m_project->childNode(0), m_project->resourceGroupAt(0), m_project->resourceGroupAt(0)->resourceAt(0)); addRequests(m_project->childNode(1), m_project->resourceGroupAt(0), m_project->resourceGroupAt(0)->resourceAt(0)); - printDebug(m_project); + Debug::print(m_project, "--------------------", true); XmlSaveContext context(m_project); - context.options = XmlSaveContext::SaveNodes; + context.options = XmlSaveContext::SaveSelectedNodes|XmlSaveContext::SaveRequests; context.nodes << m_project->childNode(0) << m_project->childNode(1); context.save(); - qInfo()<allTasks().count(), 4); Node *copy1 = m_project->childNode(2); QVERIFY(m_project->childNode(0)->id() != copy1->id()); QCOMPARE(m_project->childNode(0)->name(), copy1->name()); QCOMPARE(m_project->childNode(0)->requests().requests().count(), copy1->requests().requests().count()); ResourceGroupRequest *gr = m_project->childNode(0)->requests().requests().at(0); ResourceGroupRequest *cpgr = copy1->requests().requests().at(0); QCOMPARE(gr->group(), cpgr->group()); QCOMPARE(gr->units(), cpgr->units()); QCOMPARE(gr->count(), cpgr->count()); ResourceRequest *rr = gr->resourceRequests().at(0); ResourceRequest *cprr = cpgr->resourceRequests().at(0); QCOMPARE(rr->resource(), cprr->resource()); QCOMPARE(rr->units(), cprr->units()); } void InsertProjectXmlCommandTester::copyDependency() { addResources(m_project); addTask(m_project, "T1"); addTask(m_project, "T2"); addDependency(m_project->childNode(0), m_project->childNode(1)); - printDebug(m_project); + Debug::print(m_project, "--------------------", true); XmlSaveContext context(m_project); - context.options = XmlSaveContext::SaveNodes; + context.options = XmlSaveContext::SaveSelectedNodes | XmlSaveContext::SaveRelations; context.nodes << m_project->childNode(0) << m_project->childNode(1); context.save(); InsertProjectXmlCommand cmd(m_project, context.document.toByteArray(), m_project, nullptr /*last*/); cmd.redo(); - printDebug(m_project); + Debug::print(m_project, "--------------------", true); QCOMPARE(m_project->allTasks().count(), 4); Node *copy1 = m_project->childNode(2); Node *copy2 = m_project->childNode(3); QVERIFY(m_project->childNode(0)->id() != copy1->id()); QCOMPARE(m_project->childNode(0)->name(), copy1->name()); QCOMPARE(m_project->childNode(0)->numDependChildNodes(), copy1->numDependChildNodes()); QCOMPARE(m_project->childNode(1)->numDependParentNodes(), copy2->numDependParentNodes()); } void InsertProjectXmlCommandTester::copyToPosition() { addTask(m_project, "T1"); addTask(m_project, "T2"); QList old = m_project->childNodeIterator(); XmlSaveContext context(m_project); - context.options = XmlSaveContext::SaveNodes; + context.options = XmlSaveContext::SaveSelectedNodes; context.nodes << m_project->childNode(0); context.save(); { InsertProjectXmlCommand cmd(m_project, context.document.toByteArray(), m_project, m_project->childNode(1)); cmd.redo(); } QCOMPARE(m_project->allTasks().count(), 3); QCOMPARE(m_project->childNode(0), old.at(0)); QCOMPARE(m_project->childNode(2), old.at(1)); QVERIFY(!old.contains(m_project->childNode(1))); - qInfo()<childNodeIterator(); old = m_project->childNodeIterator(); - context.options = XmlSaveContext::SaveNodes; + context.options = XmlSaveContext::SaveSelectedNodes; context.save(); { InsertProjectXmlCommand cmd(m_project, context.document.toByteArray(), m_project->childNode(1), nullptr); cmd.redo(); } - qInfo()<childNodeIterator(); + QCOMPARE(m_project->numChildren(), 3); QCOMPARE(m_project->allNodes().count(), 4); QVERIFY(m_project->childNode(1)->type() == Node::Type_Summarytask); QCOMPARE(m_project->childNode(1)->numChildren(), 1); } QTEST_GUILESS_MAIN(KPlato::InsertProjectXmlCommandTester) diff --git a/src/libs/models/tests/InsertProjectXmlCommandTester.h b/src/libs/models/tests/InsertProjectXmlCommandTester.h index f16aed60..0c82f74a 100644 --- a/src/libs/models/tests/InsertProjectXmlCommandTester.h +++ b/src/libs/models/tests/InsertProjectXmlCommandTester.h @@ -1,55 +1,53 @@ /* This file is part of the KDE project Copyright (C) 2019 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 KPlato_InsertProjectXmlCommandTester_h #define KPlato_InsertProjectXmlCommandTester_h #include #include "kptresourceappointmentsmodel.h" #include "kptproject.h" #include "kptdatetime.h" namespace KPlato { class Task; class InsertProjectXmlCommandTester : public QObject { Q_OBJECT private Q_SLOTS: void init(); void cleanup(); void copyBasics(); void copyRequests(); void copyDependency(); void copyToPosition(); private: - void printDebug(Project *project) const; - Project *m_project; }; } //namespace KPlato #endif diff --git a/src/libs/models/tests/ResourceModelTester.cpp b/src/libs/models/tests/ResourceModelTester.cpp index 32faca26..53655324 100644 --- a/src/libs/models/tests/ResourceModelTester.cpp +++ b/src/libs/models/tests/ResourceModelTester.cpp @@ -1,365 +1,365 @@ /* This file is part of the KDE project Copyright (C) 2009 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 "ResourceModelTester.h" #include "kptcommand.h" #include "kptcalendar.h" #include "kptdatetime.h" #include "kptresource.h" #include "kptnode.h" #include "kpttask.h" #include "kptschedule.h" #include "kptappointment.h" #include #include namespace QTest { template<> char *toString(const KPlato::DateTime &dt) { return toString(dt.toString()); } } namespace KPlato { void ResourceModelTester::printDebug(long id) const { Project *p = m_project; Resource *r = m_resource; qDebug()<<"Debug info -------------------------------------"; qDebug()<<"project start time:"<startTime().toString(); qDebug()<<"project end time :"<endTime().toString(); qDebug()<<"Resource start:"<startTime(id).toString(); qDebug()<<"Resource end :"<endTime(id).toString(); qDebug()<<"Appointments:"<numAppointments(id)<<"(internal)"; foreach (Appointment *a, r->appointments(id)) { foreach (const AppointmentInterval &i, a->intervals().map()) { qDebug()<<" "<numExternalAppointments()<<"(external)"; foreach (Appointment *a, r->externalAppointmentList()) { foreach (const AppointmentInterval &i, a->intervals().map()) { qDebug()<<" "<logMessages()) { qDebug()<setName("P1"); m_project->setId(m_project->uniqueNodeId()); m_project->registerNodeId(m_project); DateTime targetstart = DateTime(QDate::currentDate(), QTime(0,0,0)); DateTime targetend = DateTime(targetstart.addDays(3)); m_project->setConstraintStartTime(targetstart); m_project->setConstraintEndTime(targetend); // standard worktime defines 8 hour day as default QVERIFY(m_project->standardWorktime()); QCOMPARE(m_project->standardWorktime()->day(), 8.0); m_calendar = new Calendar("Test"); m_calendar->setDefault(true); QTime t1(9, 0, 0); QTime t2 (17, 0, 0); int length = t1.msecsTo(t2); for (int i=1; i <= 7; ++i) { CalendarDay *d = m_calendar->weekday(i); d->setState(CalendarDay::Working); d->addInterval(t1, length); } m_project->addCalendar(m_calendar); ResourceGroup *g = new ResourceGroup(); g->setName("G1"); m_project->addResourceGroup(g); m_resource = new Resource(); m_resource->setName("R1"); m_resource->setCalendar(m_calendar); m_project->addResource(m_resource); g->addResource(m_resource); m_task = m_project->createTask(); m_task->setName("T1"); m_project->addTask(m_task, m_project); m_task->estimate()->setUnit(Duration::Unit_h); m_task->estimate()->setExpectedEstimate(8.0); m_task->estimate()->setType(Estimate::Type_Effort); ResourceGroupRequest *gr = new ResourceGroupRequest(g); - gr->addResourceRequest(new ResourceRequest(m_resource, 100)); m_task->addRequest(gr); + m_task->requests().addResourceRequest(new ResourceRequest(m_resource, 100), gr); m_model.setProject(m_project); QModelIndex idx; int rows = m_model.rowCount(idx); QCOMPARE(rows, 1); idx = m_model.index(0, 0, idx); QCOMPARE(g->name(), m_model.data(idx).toString()); rows = m_model.rowCount(idx); QCOMPARE(rows, 1); idx = m_model.index(0, 0, idx); QCOMPARE(m_resource->name(), m_model.data(idx).toString()); idx = m_model.parent(idx); QCOMPARE(g->name(), m_model.data(idx).toString()); } void ResourceModelTester::cleanupTestCase() { delete m_project; } void ResourceModelTester::internalAppointments() { ScheduleManager *sm = m_project->createScheduleManager("Test Plan"); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); long id = sm->scheduleId(); m_model.setScheduleManager(sm); //printDebug(sm->scheduleId()); QModelIndex idx; // resource group int rows = m_model.rowCount(idx); QCOMPARE(rows, 1); QModelIndex gidx = m_model.index(0, 0, idx); QVERIFY(gidx.isValid()); // resource rows = m_model.rowCount(gidx); QCOMPARE(rows, 1); QModelIndex ridx = m_model.index(0, 0, gidx); QCOMPARE(m_resource->name(), m_model.data(ridx).toString()); ridx = m_model.index(m_resource); QVERIFY(ridx.isValid()); // appointment rows = m_model.rowCount(ridx); QCOMPARE(rows, m_resource->numAppointments(id)); QCOMPARE(rows, 1); QModelIndex aidx = m_model.index(0, 0, ridx); // first appointment QVERIFY(aidx.isValid()); rows = m_model.rowCount(aidx); // num intervals QCOMPARE(rows, 1); // interval QModelIndex iidx = m_model.index(0, 0, aidx); // first interval QVERIFY(iidx.isValid()); rows = m_model.rowCount(iidx); // intervals don't have children QCOMPARE(rows, 0); // appointment idx = m_model.parent(iidx); QCOMPARE(idx, aidx); // resource idx = m_model.parent(aidx); QCOMPARE(idx, ridx); // resource group idx = m_model.parent(ridx); QCOMPARE(idx, gidx); // top idx = m_model.parent(gidx); QVERIFY(! idx.isValid()); } void ResourceModelTester::externalAppointments() { DateTime targetstart = m_project->constraintStartTime(); DateTime targetend = m_project->constraintEndTime(); Task *t = m_task; Resource *r = m_resource; r->addExternalAppointment("Ext-1", "External project 1", targetstart, targetstart.addDays(1), 100); r->addExternalAppointment("Ext-1", "External project 1", targetend.addDays(-1), targetend, 100); ScheduleManager *sm = m_project->createScheduleManager("Test Plan"); m_project->addScheduleManager(sm); sm->createSchedules(); m_project->calculate(*sm); long id = sm->scheduleId(); m_model.setScheduleManager(sm); //printSchedulingLog(*sm); //printDebug(sm->scheduleId()); // resource group QModelIndex idx; int rows = m_model.rowCount(idx); QCOMPARE(rows, 1); idx = m_model.index(0, 0, idx); QVERIFY(idx.isValid()); QVERIFY(! m_model.parent(idx).isValid()); // resource rows = m_model.rowCount(idx); QCOMPARE(rows, 1); QModelIndex ridx = m_model.index(0, 0, idx); QCOMPARE(m_resource->name(), m_model.data(ridx).toString()); QCOMPARE(ridx.parent(), idx); ridx = m_model.index(m_resource); QVERIFY(ridx.isValid()); // appointments rows = m_model.rowCount(ridx); QCOMPARE(rows, m_resource->numAppointments(id) + m_resource->numExternalAppointments()); QCOMPARE(rows, 2); // one internal, one external // internal appointment QModelIndex aidx = m_model.index(0, 0, ridx); // first appointment (internal) QVERIFY(aidx.isValid()); rows = m_model.rowCount(aidx); // num intervals QCOMPARE(rows, 1); QCOMPARE(aidx.parent(), ridx); QModelIndex iidx = m_model.index(0, 0, aidx); // first interval QVERIFY(iidx.isValid()); rows = m_model.rowCount(iidx); // intervals don't have children QCOMPARE(rows, 0); QCOMPARE(iidx.parent(), aidx); // external appointment aidx = m_model.index(1, 0, ridx); // second appointment (external) QVERIFY(aidx.isValid()); rows = m_model.rowCount(aidx); // num intervals QCOMPARE(rows, 2); QCOMPARE(aidx.parent(), ridx); iidx = m_model.index(0, 0, aidx); // first interval QVERIFY(iidx.isValid()); rows = m_model.rowCount(iidx); // intervals don't have children QCOMPARE(rows, 0); QCOMPARE(iidx.parent(), aidx); iidx = m_model.index(1, 0, aidx); // second interval QVERIFY(iidx.isValid()); rows = m_model.rowCount(iidx); // intervals don't have children QCOMPARE(rows, 0); QCOMPARE(iidx.parent(), aidx); QCOMPARE(t->startTime(), m_calendar->firstAvailableAfter(targetstart + Duration(1, 0, 0), t->endTime())); QCOMPARE(t->endTime(), t->startTime() + Duration(0, 8, 0)); } void ResourceModelTester::externalOverbook() { DateTime targetstart = m_project->constraintStartTime(); DateTime targetend = m_project->constraintEndTime(); Task *t = m_task; Resource *r = m_resource; r->addExternalAppointment("Ext-1", "External project 1", targetstart, targetstart.addDays(1), 100); r->addExternalAppointment("Ext-1", "External project 1", targetend.addDays(-1), targetend, 100); ScheduleManager *sm = m_project->createScheduleManager("Test Plan"); m_project->addScheduleManager(sm); sm->setAllowOverbooking(true); sm->createSchedules(); m_project->calculate(*sm); long id = sm->scheduleId(); m_model.setScheduleManager(sm); //printSchedulingLog(*sm); //printDebug(id); // resource group QModelIndex idx; int rows = m_model.rowCount(idx); QCOMPARE(rows, 1); idx = m_model.index(0, 0, idx); QVERIFY(idx.isValid()); // resource rows = m_model.rowCount(idx); QCOMPARE(rows, 1); idx = m_model.index(0, 0, idx); QCOMPARE(m_resource->name(), m_model.data(idx).toString()); idx = m_model.index(m_resource); QVERIFY(idx.isValid()); // appointments rows = m_model.rowCount(idx); QCOMPARE(rows, m_resource->numAppointments(id) + m_resource->numExternalAppointments()); QCOMPARE(rows, 2); // one internal, one external // internal appointment QModelIndex aidx = m_model.index(0, 0, idx); // first appointment (internal) QVERIFY(aidx.isValid()); rows = m_model.rowCount(aidx); // num intervals QCOMPARE(rows, 1); QModelIndex iidx = m_model.index(0, 0, aidx); // first interval QVERIFY(iidx.isValid()); rows = m_model.rowCount(iidx); // intervals don't have children QCOMPARE(rows, 0); // external appointment aidx = m_model.index(1, 0, idx); // second appointment (external) QVERIFY(aidx.isValid()); rows = m_model.rowCount(aidx); // num intervals QCOMPARE(rows, 2); iidx = m_model.index(0, 0, aidx); // first interval QVERIFY(iidx.isValid()); rows = m_model.rowCount(iidx); // intervals don't have children QCOMPARE(rows, 0); iidx = m_model.index(1, 0, aidx); // second interval QVERIFY(iidx.isValid()); rows = m_model.rowCount(iidx); // intervals don't have children QCOMPARE(rows, 0); QCOMPARE(t->startTime(), m_calendar->firstAvailableAfter(targetstart, t->endTime())); QCOMPARE(t->endTime(), t->startTime() + Duration(0, 8, 0)); } } //namespace KPlato QTEST_GUILESS_MAIN(KPlato::ResourceModelTester) diff --git a/src/libs/models/tests/WorkPackageProxyModelTester.cpp b/src/libs/models/tests/WorkPackageProxyModelTester.cpp index cbf90b7c..6b4e5d8e 100644 --- a/src/libs/models/tests/WorkPackageProxyModelTester.cpp +++ b/src/libs/models/tests/WorkPackageProxyModelTester.cpp @@ -1,227 +1,228 @@ /* This file is part of the KDE project Copyright (C) 2012 Dag Andersen This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ // clazy:excludeall=qstring-arg #include "WorkPackageProxyModelTester.h" #include "kptcommand.h" #include "kpttask.h" #include "kptnode.h" #include "kptnodeitemmodel.h" #include #include #include "../../kernel/tests/debug.cpp" namespace KPlato { Task *WorkPackageProxyModelTester::addTask(Node *parent, int after) { Task *task = project.createTask(); TaskAddCmd cmd(&project, task, parent->childNode(after)); cmd.redo(); ResourceGroupRequest *gr = new ResourceGroupRequest(project.resourceGroupAt(0)); - ResourceRequest *rr = new ResourceRequest(project.resourceGroupAt(0)->resourceAt(0), 100); - gr->addResourceRequest(rr); AddResourceGroupRequestCmd c(*task, gr); c.redo(); + ResourceRequest *rr = new ResourceRequest(project.resourceGroupAt(0)->resourceAt(0), 100); + AddResourceRequestCmd c2(&task->requests(), rr, gr); + c2.redo(); return task; } void WorkPackageProxyModelTester::removeTask(Node *task) { NodeDeleteCmd cmd(task); cmd.redo(); } void WorkPackageProxyModelTester::moveTask(Node *task, Node *newParent, int newPos) { NodeMoveCmd cmd(&project, task, newParent, newPos); cmd.redo(); } void WorkPackageProxyModelTester::initTestCase() { project.setName("P1"); project.setId(project.uniqueNodeId()); project.registerNodeId(&project); Calendar *calendar = new Calendar(); calendar->setName("C1"); calendar->setDefault(true); QTime t1(9, 0, 0); QTime t2 (17, 0, 0); int length = t1.msecsTo(t2); for (int i=1; i <= 7; ++i) { CalendarDay *d = calendar->weekday(i); d->setState(CalendarDay::Working); d->addInterval(t1, length); } project.addCalendar(calendar); QCOMPARE(m_model.rowCount(), 0); m_model.setProject(&project); QCOMPARE(m_model.rowCount(), 0); ResourceGroup *g = new ResourceGroup(); g->setName("G1"); project.addResourceGroup(g); Resource *r = new Resource(); r->setName("R1"); project.addResource(r); g->addResource(r); sm = project.createScheduleManager(); m_model.setScheduleManager(sm); } void WorkPackageProxyModelTester::testInsert() { Task *task = addTask(&project, 0); task->setName("T1"); QCOMPARE(m_model.baseModel()->rowCount(), 1); QCOMPARE(m_model.rowCount(), 0); // filtered, (not scheduled) sm->createSchedules(); project.calculate(*sm); QVERIFY(task->isScheduled()); QCOMPARE(m_model.rowCount(), 1); QCOMPARE(m_model.index(0, NodeModel::NodeName).data().toString(), QString("T1")); task = addTask(&project, -1); task->setName("T2"); QCOMPARE(m_model.baseModel()->rowCount(), 2); QCOMPARE(m_model.rowCount(), 1); // filtered, (not scheduled) sm->createSchedules(); project.calculate(*sm); QVERIFY(task->isScheduled()); QCOMPARE(m_model.rowCount(), 2); QCOMPARE(m_model.index(1, NodeModel::NodeName).data().toString(), QString("T2")); task = addTask(&project, -1); task->setName("T3"); QCOMPARE(m_model.baseModel()->rowCount(), 3); QCOMPARE(m_model.rowCount(), 2); // filtered, (not scheduled) sm->createSchedules(); project.calculate(*sm); QVERIFY(task->isScheduled()); QCOMPARE(m_model.rowCount(), 3); QCOMPARE(m_model.index(2, NodeModel::NodeName).data().toString(), QString("T3")); } void WorkPackageProxyModelTester::testRemove() { removeTask(project.childNode(0)); QCOMPARE(m_model.rowCount(), 2); QCOMPARE(m_model.index(0, NodeModel::NodeName).data().toString(), QString("T2")); QCOMPARE(m_model.index(1, NodeModel::NodeName).data().toString(), QString("T3")); removeTask(project.childNode(1)); QCOMPARE(m_model.rowCount(), 1); QCOMPARE(m_model.index(0, NodeModel::NodeName).data().toString(), QString("T2")); removeTask(project.childNode(0)); QCOMPARE(m_model.rowCount(), 0); } void WorkPackageProxyModelTester::testMove() { testInsert(); //Debug::print(&project, "T1, T2, T3 ---------", true); moveTask(project.childNode(0), &project, 1); //Debug::print(&project, "T2, T1, T3 ---------", true); QCOMPARE(m_model.rowCount(), 3); QCOMPARE(m_model.index(0, NodeModel::NodeName).data().toString(), QString("T2")); QCOMPARE(m_model.index(1, NodeModel::NodeName).data().toString(), QString("T1")); QCOMPARE(m_model.index(2, NodeModel::NodeName).data().toString(), QString("T3")); moveTask(project.childNode(1), &project, 0); QCOMPARE(m_model.index(0, NodeModel::NodeName).data().toString(), QString("T1")); QCOMPARE(m_model.index(1, NodeModel::NodeName).data().toString(), QString("T2")); QCOMPARE(m_model.index(2, NodeModel::NodeName).data().toString(), QString("T3")); moveTask(project.childNode(1), &project, 0); QCOMPARE(m_model.index(0, NodeModel::NodeName).data().toString(), QString("T2")); QCOMPARE(m_model.index(1, NodeModel::NodeName).data().toString(), QString("T1")); QCOMPARE(m_model.index(2, NodeModel::NodeName).data().toString(), QString("T3")); moveTask(project.childNode(0), &project, -1); // last QCOMPARE(m_model.index(0, NodeModel::NodeName).data().toString(), QString("T1")); QCOMPARE(m_model.index(1, NodeModel::NodeName).data().toString(), QString("T3")); QCOMPARE(m_model.index(2, NodeModel::NodeName).data().toString(), QString("T2")); } void WorkPackageProxyModelTester::testMoveChild() { while (project.numChildren() > 0) { Node *t = project.childNode(0); project.takeTask(t); delete t; } testInsert(); Task *task = addTask(&project, 1); // after 1 = 2 :( task->setName("T4"); //Debug::print(&project, "T1, T2, T4, T3 ----", true); QCOMPARE(project.numChildren(), 4); QCOMPARE(m_model.rowCount(), 3); // filtered, (not scheduled) sm->createSchedules(); project.calculate(*sm); //Debug::print(&project, "T1, T2, T4, T3 ----", true); QVERIFY(task->isScheduled()); QCOMPARE(m_model.rowCount(), 4); QCOMPARE(m_model.index(0, NodeModel::NodeName).data().toString(), QString("T1")); QCOMPARE(m_model.index(1, NodeModel::NodeName).data().toString(), QString("T2")); QCOMPARE(m_model.index(2, NodeModel::NodeName).data().toString(), QString("T4")); QCOMPARE(m_model.index(3, NodeModel::NodeName).data().toString(), QString("T3")); project.indentTask(project.childNode(2)); //Debug::print(&project, "T1, T2 -> T4, T3 ----", true); QCOMPARE(m_model.rowCount(), 3); QCOMPARE(m_model.index(0, NodeModel::NodeName).data().toString(), QString("T1")); QCOMPARE(m_model.index(1, NodeModel::NodeName).data().toString(), QString("T4")); QCOMPARE(m_model.index(2, NodeModel::NodeName).data().toString(), QString("T3")); moveTask(project.childNode(1)->childNode(0), project.childNode(0), 0); //Debug::print(&project, "T1 -> T4, T2, T3 ----", true); QCOMPARE(m_model.rowCount(), 3); QCOMPARE(m_model.index(0, NodeModel::NodeName).data().toString(), QString("T4")); QCOMPARE(m_model.index(1, NodeModel::NodeName).data().toString(), QString("T2")); QCOMPARE(m_model.index(2, NodeModel::NodeName).data().toString(), QString("T3")); moveTask(project.childNode(0)->childNode(0), project.childNode(2), 0); //Debug::print(&project, "T1, T2, T3 -> T4 ----", true); QCOMPARE(m_model.rowCount(), 3); QCOMPARE(m_model.index(0, NodeModel::NodeName).data().toString(), QString("T1")); QCOMPARE(m_model.index(1, NodeModel::NodeName).data().toString(), QString("T2")); QCOMPARE(m_model.index(2, NodeModel::NodeName).data().toString(), QString("T4")); moveTask(project.childNode(2)->childNode(0), &project, 0); //Debug::print(&project, "T4, T1, T2, T3 ----", true); QCOMPARE(m_model.rowCount(), 4); QCOMPARE(m_model.index(0, NodeModel::NodeName).data().toString(), QString("T4")); QCOMPARE(m_model.index(1, NodeModel::NodeName).data().toString(), QString("T1")); QCOMPARE(m_model.index(2, NodeModel::NodeName).data().toString(), QString("T2")); QCOMPARE(m_model.index(3, NodeModel::NodeName).data().toString(), QString("T3")); } } //namespace KPlato QTEST_GUILESS_MAIN(KPlato::WorkPackageProxyModelTester) diff --git a/src/plugins/filters/planner/import/plannerimport.cpp b/src/plugins/filters/planner/import/plannerimport.cpp index a2822382..78422821 100644 --- a/src/plugins/filters/planner/import/plannerimport.cpp +++ b/src/plugins/filters/planner/import/plannerimport.cpp @@ -1,553 +1,553 @@ /* This file is part of the KDE project * Copyright (C) 2019 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 "plannerimport.h" #include "kptproject.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KPlato; #define PLANNERIMPORT_LOG "calligra.plan.filter.planner.import" #define debugPlannerImport qCDebug(QLoggingCategory(PLANNERIMPORT_LOG))<();) PlannerImport::PlannerImport(QObject* parent, const QVariantList &) : KoFilter(parent) { } KoFilter::ConversionStatus PlannerImport::convert(const QByteArray& from, const QByteArray& to) { debugPlannerImport << from << to; if ((from != "application/x-planner") || (to != "application/x-vnd.kde.plan")) { return KoFilter::NotImplemented; } QFile in(m_chain->inputFile()); if (!in.open(QIODevice::ReadOnly)) { errorPlannerImport << "Unable to open input file!"; in.close(); return KoFilter::FileNotFound; } QDomDocument inDoc; if (!inDoc.setContent(&in)) { errorPlannerImport << "Invalid format in input file!"; in.close(); return KoFilter::InvalidFormat; } KoDocument *part = 0; bool batch = false; if (m_chain->manager()) { batch = m_chain->manager()->getBatchMode(); } if (batch) { //TODO debugPlannerImport << "batch"; } else { //debugPlannerImport<<"online"; part = m_chain->outputDocument(); } if (part == 0 || part->project() == 0) { errorPlannerImport << "Cannot open document"; return KoFilter::InternalError; } if (!loadPlanner(inDoc, part)) { return KoFilter::ParsingError; } return KoFilter::OK; } DateTime toDateTime(const QString &dts) { // NOTE: time ends in Z, so should be UTC, but it seems it is in local time anyway. // Atm, just ignore timezone const QString format = QString("yyyyMMddThhmmssZ"); return DateTime(QDateTime::fromString(dts, format)); } // bool loadProject(const QDomElement &el, Project &project) { ScheduleManager *sm = project.createScheduleManager("Planner"); project.addScheduleManager(sm); sm->createSchedules(); sm->setAllowOverbooking(true); sm->expected()->setScheduled(true); project.setCurrentSchedule(sm->scheduleId()); project.setName(el.attribute("name")); project.setLeader(el.attribute("manager")); DateTime dt = toDateTime(el.attribute("project-start")); if (dt.isValid()) { project.setConstraintStartTime(dt); project.setStartTime(dt); } if (el.hasAttribute("calendar")) { Calendar *c = new Calendar(); c->setId(el.attribute("calendar")); project.addCalendar(c); project.setDefaultCalendar(c); debugPlannerImport<<"Added default calendar:"< // // // // // // // // // // // // interval start="0900" end="1700"/> // /overridden-day-type> // /overridden-day-types> // days/> // /calendar> // calendar id="2" name="Standard"> // default-week mon="0" tue="0" wed="0" thu="0" fri="0" sat="1" sun="1"/> // overridden-day-types> // overridden-day-type id="0"> // interval start="0800" end="1200"/> // interval start="1300" end="1700"/> // /overridden-day-type> // /overridden-day-types> // days/> // calendar id="3" name="Min kalender"> // // overridden-day-types> // overridden-day-type id="3"> // interval start="0800" end="1600"/> // /overridden-day-type> // /overridden-day-types> // days> // day date="20190506" type="day-type" id="3"/> // /days> // /calendar> // /calendar> // CalendarDay::State toDayState(int type) { QList state = QList() << CalendarDay::Working << CalendarDay::NonWorking; if (type < state.count()) { return state.value(type); } return CalendarDay::Undefined; } bool loadWeek(const QDomElement &el, Calendar *calendar) { debugPlannerImport<name(); QList defaultWeek = QList() << 2 << 2 << 2 << 2 << 2 << 2 << 2; QDomElement wel; forEachChildElementWithTag(wel, el, "default-week") { defaultWeek[0] = wel.attribute("mon", "2").toInt(); defaultWeek[1] = wel.attribute("tue", "2").toInt(); defaultWeek[2] = wel.attribute("wed", "2").toInt(); defaultWeek[3] = wel.attribute("thu", "2").toInt(); defaultWeek[4] = wel.attribute("fri", "2").toInt(); defaultWeek[5] = wel.attribute("sat", "2").toInt(); defaultWeek[6] = wel.attribute("sun", "2").toInt(); } debugPlannerImport<weekday(i+1); day->setState(toDayState(defaultWeek.at(i))); } forEachChildElementWithTag(wel, el, "overridden-day-types") { QDomElement oel; forEachChildElementWithTag(oel, wel, "overridden-day-type") { if (oel.hasAttribute("id")) { int id = oel.attribute("id").toInt(); if (!defaultWeek.contains(id)) { continue; } for (int i = 0; i < defaultWeek.count(); ++i) { if (defaultWeek.at(i) != id) { continue; } CalendarDay *day = calendar->weekday(i+1); day->setState(CalendarDay::Working); QDomElement iel; forEachChildElementWithTag(iel, oel, "interval") { QTime start = QTime::fromString(iel.attribute("start"), "hhmm"); QTime end = QTime::fromString(iel.attribute("end"), "hhmm"); day->addInterval(TimeInterval(start, start.msecsTo(end))); debugPlannerImport<<"Overridden:"<"<addInterval(TimeInterval(start, start.msecsTo(end))); } calendar->addDay(day); } return true; } bool loadCalendars(const QDomElement &el, Project &project, Calendar *parent = 0) { QDomElement cel; forEachChildElementWithTag(cel, el, "calendar") { QString id = cel.attribute("id"); Calendar *calendar = project.findCalendar(id); if (!calendar) { calendar = new Calendar(); calendar->setId(cel.attribute("id")); project.addCalendar(calendar, parent); debugPlannerImport<<"Loading new calendar"<id(); } else debugPlannerImport<<"Loading default calendar"<id(); calendar->setName(cel.attribute("name")); loadWeek(cel, calendar); loadDays(cel, calendar); loadCalendars(cel, project, calendar); } return true; } // // // bool loadResourceGroups(const QDomElement &el, Project &project) { QDomNodeList lst = el.elementsByTagName("group"); QDomElement gel; forEachElementInList(gel, lst) { ResourceGroup *g = new ResourceGroup(); g->setId(gel.attribute("id")); g->setName(gel.attribute("name")); project.addResourceGroup(g); } return true; } // // // Resource::Type toResourceType(const QString &type) { QMap types; types["0"] = Resource::Type_Material; types["1"] = Resource::Type_Work; return types.contains(type) ? types[type] : Resource::Type_Work; } bool loadResources(const QDomElement &el, Project &project) { QDomNodeList lst = el.elementsByTagName("resource"); QDomElement rel; forEachElementInList(rel, lst) { Resource *r = new Resource(); r->setId(rel.attribute("id")); r->setName(rel.attribute("name")); r->setInitials(rel.attribute("short-name")); r->setEmail(rel.attribute("email")); r->setType(toResourceType(rel.attribute("type"))); int units = rel.attribute("units", "0").toInt(); if (units == 0) { // atm. planner saves 0 but assumes 100% units = 100; } r->setUnits(units); r->setNormalRate(rel.attribute("std-rate").toDouble()); r->setCalendar(project.findCalendar(rel.attribute("calendar"))); QString gid = rel.attribute("group"); ResourceGroup *g = project.group(gid); if (!g) { // add a default g = new ResourceGroup(); g->setId(gid); // FIXME handle: gid *should* be empty if group was not found g->setName(i18n("Resources")); project.addResourceGroup(g); } project.addResource(r); g->addResource(-1, r, nullptr); } return true; } Estimate::Type toEstimateType(const QString type) { Estimate::Type res = Estimate::Type_Effort; if (type == "fixed-work") { res = Estimate::Type_Effort; } else if (type == "fixed-duration") { res = Estimate::Type_Duration; } return res; } Node::ConstraintType toConstraintType(const QString &type) { Node::ConstraintType res = Node::ASAP; if (type == "must-start-on") { res = Node::MustStartOn; } else if (type == "start-no-earlier-than") { res = Node::StartNotEarlier; } return res; } bool loadConstraint(const QDomElement &el, Task *t) { QDomElement cel; forEachChildElementWithTag(cel, el, "constraint") { t->setConstraint(toConstraintType(cel.attribute("type"))); t->setConstraintStartTime(toDateTime(cel.attribute("time"))); } return true; } // bool loadTasks(const QDomElement &el, Project &project, Node *parent = 0) { QDomElement cel; forEachChildElementWithTag(cel, el, "task") { Task *t = project.createTask(); t->setId(cel.attribute("id", t->id())); t->setName(cel.attribute("name")); t->setDescription(cel.attribute("note")); loadConstraint(cel, t); t->estimate()->setType(toEstimateType(cel.attribute("scheduling"))); t->estimate()->setExpectedEstimate(Duration(cel.attribute("work", "0").toDouble(), Duration::Unit_s).toDouble()); project.addSubTask(t, parent); long sid = project.scheduleManagers().first()->scheduleId(); NodeSchedule *sch = new NodeSchedule(); sch->setId(sid); sch->setNode(t); t->addSchedule(sch); sch->setParent(t->parentNode()->currentSchedule()); t->setCurrentSchedule(sid); const QString format = QString("yyyyMMddThhmmssZ"); QDateTime start = QDateTime::fromString(cel.attribute("work-start"), format); QDateTime end = QDateTime::fromString(cel.attribute("end"), format); t->setStartTime(DateTime(start)); t->setEndTime(DateTime(end)); sch->setScheduled(true); debugPlannerImport<<"Loaded:"< types; types["FS"] = Relation::FinishStart; types["FF"] = Relation::FinishFinish; types["SS"] = Relation::StartStart; types["SF"] = Relation::FinishStart; // not supported, use default return types.value(type); } // // // bool loadDependencies(const QDomElement &el, Project &project) { QDomElement cel; forEachChildElementWithTag(cel, el, "task") { QString succid = cel.attribute("id"); Node *child = project.findNode(succid); if (!child) { warnPlannerImport<<"Task"< bool loadAllocations(const QDomElement &el, Project &project) { QDomNodeList lst = el.elementsByTagName("allocation"); QDomElement pel; forEachElementInList(pel, lst) { Task *t = dynamic_cast(project.findNode(pel.attribute("task-id"))); Resource *r = project.findResource(pel.attribute("resource-id")); if (!t || !r) { warnPlannerImport<<"Could not find task/resource:"<resourceGroupRequest(r->parentGroups().value(0)); if (!gr) { gr = new ResourceGroupRequest(r->parentGroups().value(0)); t->addRequest(gr); } ResourceRequest *rr = new ResourceRequest(r); rr->setUnits(pel.attribute("units").toInt()); - gr->addResourceRequest(rr); + t->requests().addResourceRequest(rr, gr); // do assignments Calendar *calendar = r->calendar(); if (!calendar) { warnPlannerImport<<"No resource calendar:"<currentSchedule(); Schedule *rs = r->schedule(ts->id()); if (!rs) { rs = r->createSchedule(t->name(), t->type(), ts->id()); } r->setCurrentSchedulePtr(rs); AppointmentIntervalList apps = calendar->workIntervals(t->startTime(), t->endTime(), rr->units()); foreach (const AppointmentInterval &a, apps.map()) { r->addAppointment(ts, a.startTime(), a.endTime(), a.load()); } rs->setScheduled(true); debugPlannerImport<<"Assignments:"<appointmentIntervals().intervals(); } return true; } bool PlannerImport::loadPlanner(const QDomDocument &in, KoDocument *doc) const { QDomElement pel = in.documentElement(); if (pel.tagName() != "project") { errorPlannerImport << "Missing project element"; return false; } Project &project = *doc->project(); if (!loadProject(pel, project)) { return false; } QDomElement el = pel.elementsByTagName("calendars").item(0).toElement(); if (el.isNull()) { debugPlannerImport << "No calendars element"; } loadCalendars(el, project); el = pel.elementsByTagName("resource-groups").item(0).toElement(); if (el.isNull()) { debugPlannerImport << "No resource-groups element"; } loadResourceGroups(el, project); el = pel.elementsByTagName("resources").item(0).toElement(); if (el.isNull()) { debugPlannerImport << "No resources element"; } loadResources(el, project); el = pel.elementsByTagName("tasks").item(0).toElement(); if (el.isNull()) { debugPlannerImport << "No tasks element"; } else { loadTasks(el, project); loadDependencies(el, project); } loadAllocations(pel, project); foreach(const Node *n, project.allNodes()) { if (n->endTime() > project.endTime()) { project.setEndTime(n->endTime()); } } return true; } #include "plannerimport.moc" diff --git a/src/plugins/schedulers/tj/tests/SchedulerTester.cpp b/src/plugins/schedulers/tj/tests/SchedulerTester.cpp index 04bdc2ca..c7c51d46 100644 --- a/src/plugins/schedulers/tj/tests/SchedulerTester.cpp +++ b/src/plugins/schedulers/tj/tests/SchedulerTester.cpp @@ -1,137 +1,139 @@ /* This file is part of the KDE project Copyright (C) 2007 - 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. */ // clazy:excludeall=qstring-arg #include "SchedulerTester.h" #include "PlanTJPlugin.h" #include "kptcommand.h" #include "kptcalendar.h" #include "kptdatetime.h" #include "kptresource.h" #include "kptnode.h" #include "kpttask.h" #include "kptschedule.h" #include "kptxmlloaderobject.h" #include #include "tests/DateTimeTester.h" #include "tests/debug.cpp" namespace KPlato { QStringList SchedulerTester::data() { return QStringList() << "test1.plan" << "startnotearlier.plan" ; } void SchedulerTester::test() { QString dir = QFINDTESTDATA("data/"); foreach (const QString &fname, data()) { qDebug()<<"Testing file:"<calculationResult() != ScheduleManager::CalculationDone) { + Debug::printSchedulingLog(*sm, "----"); + } s = QString("%1: Scheduling failed").arg(fname); QVERIFY2(sm->calculationResult() == ScheduleManager::CalculationDone, s.toLatin1()); long id1 = manager->scheduleId(); long id2 = sm->scheduleId(); qDebug()<name()); QVERIFY2(n->startTime(id1) == n->startTime(id2), (s.arg(n->startTime(id1).toString(Qt::ISODate)).arg(n->startTime(id2).toString(Qt::ISODate))).toLatin1()); QVERIFY2(n->endTime(id1) == n->endTime(id2), (s.arg(n->endTime(id1).toString(Qt::ISODate)).arg(n->endTime(id2).toString(Qt::ISODate))).toLatin1()); } } //namespace KPlato QTEST_GUILESS_MAIN(KPlato::SchedulerTester) diff --git a/src/tests/InsertProjectTester.cpp b/src/tests/InsertProjectTester.cpp index a1df0110..59b95caa 100644 --- a/src/tests/InsertProjectTester.cpp +++ b/src/tests/InsertProjectTester.cpp @@ -1,695 +1,698 @@ /* This file is part of the KDE project Copyright (C) 2009 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 "InsertProjectTester.h" #include "kptcommand.h" #include #include "kptmaindocument.h" #include "kptpart.h" #include "kptcalendar.h" #include "kptresource.h" #include "kpttask.h" #include "kptaccount.h" #include #include #include namespace KPlato { Account *InsertProjectTester::addAccount(MainDocument &part, Account *parent) { Project &p = part.getProject(); Account *a = new Account(); QString s = parent == 0 ? "Account" : parent->name(); a->setName(p.accounts().uniqueId(s)); KUndo2Command *c = new AddAccountCmd(p, a, parent); part.addCommand(c); return a; } void InsertProjectTester::testAccount() { Part pp(0); MainDocument part(&pp); pp.setDocument(&part); addAccount(part); Project &p = part.getProject(); QCOMPARE(p.accounts().accountCount(), 1); Part pp2(0); MainDocument part2(&pp2); pp2.setDocument(&part2); part2.insertProject(p, 0, 0); QCOMPARE(part2.getProject().accounts().accountCount(), 1); part2.insertProject(part.getProject(), 0, 0); QCOMPARE(part2.getProject().accounts().accountCount(), 1); Part ppB(0); MainDocument partB(&ppB); ppB.setDocument(&partB); Account *parent = addAccount(partB); QCOMPARE(partB.getProject().accounts().accountCount(), 1); addAccount(partB, parent); QCOMPARE(partB.getProject().accounts().accountCount(), 1); QCOMPARE(parent->childCount(), 1); part2.insertProject(partB.getProject(), 0, 0); QCOMPARE(part2.getProject().accounts().accountCount(), 1); QCOMPARE(part2.getProject().accounts().accountAt(0)->childCount(), 1); } Calendar *InsertProjectTester::addCalendar(MainDocument &part) { Project &p = part.getProject(); Calendar *c = new Calendar(); p.setCalendarId(c); part.addCommand(new CalendarAddCmd(&p, c, -1, 0)); return c; } void InsertProjectTester::testCalendar() { Part pp(0); MainDocument part(&pp); pp.setDocument(&part); addCalendar(part); Project &p = part.getProject(); QVERIFY(p.calendarCount() == 1); Part pp2(0); MainDocument part2(&pp2); pp2.setDocument(&part2); part2.insertProject(part.getProject(), 0, 0); QVERIFY(part2.getProject().calendarCount() == 1); QVERIFY(part2.getProject().defaultCalendar() == 0); } void InsertProjectTester::testDefaultCalendar() { Part pp(0); MainDocument part(&pp); pp.setDocument(&part); Calendar *c = addCalendar(part); Project &p = part.getProject(); p.setDefaultCalendar(c); QVERIFY(p.calendarCount() == 1); QCOMPARE(p.defaultCalendar(), c); Part pp2(0); MainDocument part2(&pp2); pp2.setDocument(&part2); part2.insertProject(p, 0, 0); QVERIFY(part2.getProject().calendarCount() == 1); QCOMPARE(part2.getProject().defaultCalendar(), c); Part ppB(0); MainDocument partB(&ppB); ppB.setDocument(&partB); Calendar *c2 = addCalendar(partB); partB.getProject().setDefaultCalendar(c2); part2.insertProject(partB.getProject(), 0, 0); QVERIFY(part2.getProject().calendarCount() == 2); QCOMPARE(part2.getProject().defaultCalendar(), c); // NB: still c, not c2 } ResourceGroup *InsertProjectTester::addResourceGroup(MainDocument &part) { Project &p = part.getProject(); ResourceGroup *g = new ResourceGroup(); KUndo2Command *c = new AddResourceGroupCmd(&p, g); part.addCommand(c); QString s = QString("G%1").arg(part.getProject().indexOf(g)); g->setName(s); return g; } void InsertProjectTester::testResourceGroup() { Part pp(0); MainDocument part(&pp); pp.setDocument(&part); addResourceGroup(part); Project &p = part.getProject(); QVERIFY(p.resourceGroupCount() == 1); Part pp2(0); MainDocument part2(&pp2); pp2.setDocument(&part2); part2.insertProject(p, 0, 0); QVERIFY(part2.getProject().resourceGroupCount() == 1); } Resource *InsertProjectTester::addResource(MainDocument &part, ResourceGroup *g) { Project &p = part.getProject(); if (p.resourceGroupCount() == 0) { qInfo()<<"No resource groups in project"; return nullptr; } if (g == 0) { g = p.resourceGroupAt(0); } Q_ASSERT(g); if (!p.resourceGroups().contains(g)) { qInfo()<<"Project does not contain resource group"; return nullptr; } Resource *r = new Resource(); KUndo2Command *c = new AddResourceCmd(g, r); part.addCommand(c); QString s = QString("%1.R%2").arg(g->name()).arg(g->indexOf(r)); r->setName(s); return r; } void InsertProjectTester::testResource() { Part pp(0); MainDocument part(&pp); pp.setDocument(&part); addResourceGroup(part); addResource(part); Project &p = part.getProject(); QVERIFY(p.resourceGroupAt(0)->numResources() == 1); Part pp2(0); MainDocument part2(&pp2); pp2.setDocument(&part2); //Debug::print(&part.getProject(), "Project to insert from: ------------", true); //Debug::print(&part2.getProject(), "Project to insert into: -----------", true); part2.insertProject(p, 0, 0); //Debug::print(&part2.getProject(), "Result: ---------------------------", true); QVERIFY(part2.getProject().resourceGroupAt(0)->numResources() == 1); } void InsertProjectTester::testTeamResource() { Part pp(0); MainDocument part(&pp); pp.setDocument(&part); addResourceGroup(part); Resource *r = addResource(part); r->setType(Resource::Type_Team); ResourceGroup *tg = addResourceGroup(part); Resource *t1 = addResource(part, tg); Resource *t2 = addResource(part, tg); r->setRequiredIds(QStringList() << t1->id() << t2->id()); Project &p = part.getProject(); QVERIFY(p.resourceGroupAt(0)->numResources() == 1); QVERIFY(p.resourceGroupAt(1)->numResources() == 2); QList required = p.resourceGroupAt(0)->resources().at(0)->requiredResources(); QCOMPARE(required.count(), 2); QCOMPARE(required.at(0), t1); QCOMPARE(required.at(1), t2); Part pp2(0); MainDocument part2(&pp2); pp2.setDocument(&part2); part2.insertProject(p, 0, 0); Project &p2 = part2.getProject(); QVERIFY(p2.resourceGroupAt(0)->numResources() == 1); QVERIFY(p2.resourceGroupAt(1)->numResources() == 2); required = p2.resourceGroupAt(0)->resources().at(0)->requiredResources(); QCOMPARE(required.count(), 2); QCOMPARE(required.at(0), t1); QCOMPARE(required.at(1), t2); } void InsertProjectTester::testResourceAccount() { Part pp(0); MainDocument part(&pp); pp.setDocument(&part); addResourceGroup(part); Resource *r = addResource(part); Account *a = addAccount(part); part.addCommand(new ResourceModifyAccountCmd(*r, r->account(), a)); Project &p = part.getProject(); QVERIFY(p.resourceGroupAt(0)->numResources() == 1); Part pp2(0); MainDocument part2(&pp2); pp2.setDocument(&part2); part2.insertProject(p, 0, 0); QVERIFY(part2.getProject().resourceGroupAt(0)->numResources() == 1); QVERIFY(part2.getProject().accounts().allAccounts().contains(a)); QCOMPARE(part2.getProject().resourceGroupAt(0)->resourceAt(0)->account(), a); } void InsertProjectTester::testResourceCalendar() { Part pp(0); MainDocument part(&pp); pp.setDocument(&part); Calendar *c = addCalendar(part); Project &p = part.getProject(); QVERIFY(p.calendarCount() == 1); addResourceGroup(part); Resource *r = addResource(part); part.addCommand(new ModifyResourceCalendarCmd(r, c)); QVERIFY(p.resourceGroupAt(0)->numResources() == 1); Part pp2(0); MainDocument part2(&pp2); pp2.setDocument(&part2); part2.insertProject(p, 0, 0); QVERIFY(part2.getProject().resourceGroupAt(0)->numResources() == 1); QCOMPARE(part2.getProject().allCalendars().count(), 1); QVERIFY(part2.getProject().allCalendars().contains(c)); QCOMPARE(part2.getProject().resourceGroupAt(0)->resourceAt(0)->calendar(true), c); } Task *InsertProjectTester::addTask(MainDocument &part) { Project &p = part.getProject(); Task *t = new Task(); t->setId(p.uniqueNodeId()); KUndo2Command *c = new TaskAddCmd(&p, t, 0); part.addCommand(c); return t; } void InsertProjectTester::testTask() { Part pp(0); MainDocument part(&pp); pp.setDocument(&part); + Project &p = part.getProject(); + p.setName("Insert from"); addTask(part); - Project &p = part.getProject(); QVERIFY(p.numChildren() == 1); Part pp2(0); MainDocument part2(&pp2); pp2.setDocument(&part2); - + part2.getProject().setName("Insert into"); part2.insertProject(p, 0, 0); QVERIFY(part2.getProject().numChildren() == 1); } void InsertProjectTester::addGroupRequest(MainDocument &part) { Project &p = part.getProject(); Task *t = static_cast(p.childNode(0)); int requests = p.resourceGroupAt(0)->requests().count(); KUndo2Command *c = new AddResourceGroupRequestCmd(*t, new ResourceGroupRequest(p.resourceGroupAt(0), 1)); part.addCommand(c); QVERIFY(p.resourceGroupAt(0)->requests().count() == requests+1); } void InsertProjectTester::testGroupRequest() { Part pp(0); MainDocument part(&pp); pp.setDocument(&part); - + Project &p = part.getProject(); + p.setName("Insert from"); addCalendar(part); addResourceGroup(part); addResource(part); addTask(part); addGroupRequest(part); - Project &p = part.getProject(); QVERIFY(p.numChildren() == 1); Part pp2(0); MainDocument part2(&pp2); pp2.setDocument(&part2); - - part2.insertProject(p, 0, 0); Project &p2 = part2.getProject(); + p2.setName("Insert into"); + part2.insertProject(p, 0, 0); QVERIFY(p2.childNode(0)->resourceGroupRequest(p2.resourceGroupAt(0)) != 0); } void InsertProjectTester::addResourceRequest(MainDocument &part) { Project &p = part.getProject(); ResourceGroupRequest *g = p.childNode(0)->requests().requests().at(0); KUndo2Command *c = new AddResourceRequestCmd(g, new ResourceRequest(p.resourceGroupAt(0)->resourceAt(0), 1)); part.addCommand(c); } void InsertProjectTester::testResourceRequest() { Part pp(0); MainDocument part(&pp); pp.setDocument(&part); addCalendar(part); addResourceGroup(part); addResource(part); addTask(part); addGroupRequest(part); addResourceRequest(part); Project &p = part.getProject(); + Debug::print(&p, "To be inserted: --------", true); Part pp2(0); MainDocument part2(&pp2); pp2.setDocument(&part2); part2.insertProject(p, 0, 0); Project &p2 = part2.getProject(); QVERIFY(p2.childNode(0)->requests().find(p2.resourceGroupAt(0)->resourceAt(0)) != 0); + Debug::print(&p2, "After insert: --------", true); } void InsertProjectTester::testTeamResourceRequest() { Part pp(0); MainDocument part(&pp); pp.setDocument(&part); addCalendar(part); addResourceGroup(part); Resource *r = addResource(part); r->setType(Resource::Type_Team); ResourceGroup *tg = addResourceGroup(part); Resource *t1 = addResource(part, tg); r->addTeamMemberId(t1->id()); Resource *t2 = addResource(part, tg); r->addTeamMemberId(t2->id()); addTask(part); addGroupRequest(part); addResourceRequest(part); qDebug()<<"Start test:"; Part pp2(0); MainDocument part2(&pp2); pp2.setDocument(&part2); part2.insertProject(part.getProject(), 0, 0); Project &p2 = part2.getProject(); ResourceRequest *rr = p2.childNode(0)->requests().find(p2.resourceGroupAt(0)->resourceAt(0)); QVERIFY(rr); QCOMPARE(rr->resource(), r); QCOMPARE(rr->resource()->teamMembers().count(), 2); QCOMPARE(rr->resource()->teamMembers().at(0), t1); QCOMPARE(rr->resource()->teamMembers().at(1), t2); } Relation *InsertProjectTester::addDependency(MainDocument &part, Task *t1, Task *t2) { Project &p = part.getProject(); Relation *r = new Relation(t1, t2); part.addCommand(new AddRelationCmd(p, r)); return r; } void InsertProjectTester::testDependencies() { Part pp(0); MainDocument part(&pp); pp.setDocument(&part); Task *t1 = addTask(part); Task *t2 = addTask(part); QCOMPARE(t1->numDependChildNodes(), 0); QCOMPARE(t2->numDependParentNodes(), 0); Relation *r = addDependency(part, t1, t2); QCOMPARE(t1->numDependChildNodes(), 1); QCOMPARE(t2->numDependParentNodes(), 1); QCOMPARE(t1->getDependChildNode(0), r); QCOMPARE(t2->getDependParentNode(0), r); Part pp2(0); MainDocument part2(&pp2); pp2.setDocument(&part2); QVERIFY(part2.insertProject(part.getProject(), 0, 0)); Project &p2 = part2.getProject(); QVERIFY(p2.numChildren() == 2); QCOMPARE(p2.childNode(0), t1); QCOMPARE(p2.childNode(1), t2); QCOMPARE(t1->numDependChildNodes(), 1); QCOMPARE(t2->numDependParentNodes(), 1); QCOMPARE(t1->getDependChildNode(0), r); QCOMPARE(t2->getDependParentNode(0), r); } void InsertProjectTester::testExistingResourceAccount() { Part pp(0); MainDocument part(&pp); pp.setDocument(&part); addResourceGroup(part); Resource *r = addResource(part); Account *a = addAccount(part); part.addCommand(new ResourceModifyAccountCmd(*r, r->account(), a)); Project &p = part.getProject(); QVERIFY(p.resourceGroupAt(0)->numResources() == 1); QDomDocument doc = part.saveXML(); Part pp2(0); MainDocument part2(&pp2); pp2.setDocument(&part2); part2.insertProject(p, 0, 0); QVERIFY(part2.getProject().resourceGroupAt(0)->numResources() == 1); QVERIFY(part2.getProject().accounts().allAccounts().contains(a)); QCOMPARE(part2.getProject().resourceGroupAt(0)->resourceAt(0)->account(), a); part2.addCommand(new ResourceModifyAccountCmd(*(part2.getProject().resourceGroupAt(0)->resourceAt(0)), part2.getProject().resourceGroupAt(0)->resourceAt(0)->account(), 0)); KoXmlDocument xdoc; xdoc.setContent(doc.toString()); part.loadXML(xdoc, 0); part2.insertProject(part.getProject(), 0, 0); QVERIFY(part2.getProject().resourceGroupAt(0)->numResources() == 1); QVERIFY(part2.getProject().accounts().allAccounts().contains(a)); QVERIFY(part2.getProject().resourceGroupAt(0)->resourceAt(0)->account() == 0); } void InsertProjectTester::testExistingResourceCalendar() { Part pp(0); MainDocument part(&pp); pp.setDocument(&part); Calendar *c = addCalendar(part); Project &p = part.getProject(); QVERIFY(p.calendarCount() == 1); addResourceGroup(part); Resource *r = addResource(part); part.addCommand(new ModifyResourceCalendarCmd(r, c)); QVERIFY(p.resourceGroupAt(0)->numResources() == 1); QDomDocument doc = part.saveXML(); Part pp2(0); MainDocument part2(&pp2); pp2.setDocument(&part2); part2.insertProject(p, 0, 0); QVERIFY(part2.getProject().resourceGroupAt(0)->numResources() == 1); QCOMPARE(part2.getProject().allCalendars().count(), 1); QVERIFY(part2.getProject().allCalendars().contains(c)); QCOMPARE(part2.getProject().resourceGroupAt(0)->resourceAt(0)->calendar(true), c); part2.getProject().resourceGroupAt(0)->resourceAt(0)->setCalendar(0); KoXmlDocument xdoc; xdoc.setContent(doc.toString()); part.loadXML(xdoc, 0); part2.insertProject(part.getProject(), 0, 0); QVERIFY(part2.getProject().resourceGroupAt(0)->numResources() == 1); QCOMPARE(part2.getProject().allCalendars().count(), 1); QVERIFY(part2.getProject().allCalendars().contains(c)); QVERIFY(part2.getProject().resourceGroupAt(0)->resourceAt(0)->calendar(true) == 0); } void InsertProjectTester::testExistingResourceRequest() { Part pp(0); MainDocument part(&pp); pp.setDocument(&part); addCalendar(part); addResourceGroup(part); addResource(part); addTask(part); addGroupRequest(part); addResourceRequest(part); QDomDocument doc = part.saveXML(); Project &p = part.getProject(); Part pp2(0); MainDocument part2(&pp2); pp2.setDocument(&part2); //Debug::print(&part.getProject(), "Project to be inserted from: --------------------------", true); //Debug::print(&part2.getProject(), "Project to be inserted into: -------------------------", true); part2.insertProject(p, 0, 0); //Debug::print(&part2.getProject(), "Result1:", true); Project &p2 = part2.getProject(); QVERIFY(p2.childNode(0)->requests().find(p2.resourceGroupAt(0)->resourceAt(0)) != 0); KoXmlDocument xdoc; xdoc.setContent(doc.toString()); part.loadXML(xdoc, 0); //Debug::print(&part.getProject(), "Project to be inserted from:", true); //Debug::print(&part2.getProject(), "Project to be inserted into:", true); part2.insertProject(part.getProject(), 0, 0); //Debug::print(&part2.getProject(), "Result2:", true); QVERIFY(p2.childNode(0)->requests().find(p2.resourceGroupAt(0)->resourceAt(0)) != 0); QVERIFY(p2.childNode(1)->requests().find(p2.resourceGroupAt(0)->resourceAt(0)) != 0); } void InsertProjectTester::testExistingRequiredResourceRequest() { Part pp(0); MainDocument part(&pp); pp.setDocument(&part); addCalendar(part); addResourceGroup(part); Resource *r = addResource(part); ResourceGroup *g = addResourceGroup(part); g->setType(ResourceGroup::Type_Material); QList m; m << addResource(part, g); m.first()->setType(Resource::Type_Material); r->setRequiredIds(QStringList() << m.first()->id()); addTask(part); addGroupRequest(part); addResourceRequest(part); QDomDocument doc = part.saveXML(); Project &p = part.getProject(); Part pp2(0); MainDocument part2(&pp2); pp2.setDocument(&part2); part2.insertProject(p, 0, 0); Project &p2 = part2.getProject(); ResourceRequest *rr = p2.childNode(0)->requests().find(p2.resourceGroupAt(0)->resourceAt(0)); QVERIFY(rr); QVERIFY(! rr->requiredResources().isEmpty()); QCOMPARE(rr->requiredResources().at(0), m.first()); KoXmlDocument xdoc; xdoc.setContent(doc.toString()); part.loadXML(xdoc, 0); part2.insertProject(part.getProject(), 0, 0); rr = p2.childNode(0)->requests().find(p2.resourceGroupAt(0)->resourceAt(0)); QVERIFY(rr); QVERIFY(! rr->requiredResources().isEmpty()); QCOMPARE(rr->requiredResources().at(0), m.first()); rr = p2.childNode(1)->requests().find(p2.resourceGroupAt(0)->resourceAt(0)); QVERIFY(rr); QVERIFY(! rr->requiredResources().isEmpty()); QCOMPARE(rr->requiredResources().at(0), m.first()); } void InsertProjectTester::testExistingTeamResourceRequest() { Part pp(0); MainDocument part(&pp); pp.setDocument(&part); addCalendar(part); addResourceGroup(part); Resource *r = addResource(part); r->setName("R1"); r->setType(Resource::Type_Team); ResourceGroup *tg = addResourceGroup(part); tg->setName("TG"); Resource *t1 = addResource(part, tg); t1->setName("T1"); r->addTeamMemberId(t1->id()); Resource *t2 = addResource(part, tg); t2->setName("T2"); r->addTeamMemberId(t2->id()); addTask(part); addGroupRequest(part); addResourceRequest(part); QDomDocument doc = part.saveXML(); Part pp2(0); MainDocument part2(&pp2); pp2.setDocument(&part2); Project &p2 = part2.getProject(); part2.insertProject(part.getProject(), 0, 0); ResourceRequest *rr = p2.childNode(0)->requests().find(p2.resourceGroupAt(0)->resourceAt(0)); QVERIFY(rr); QCOMPARE(rr->resource()->teamMembers().count(), 2); QCOMPARE(rr->resource()->teamMembers().at(0), t1); QCOMPARE(rr->resource()->teamMembers().at(1), t2); KoXmlDocument xdoc; xdoc.setContent(doc.toString()); part.loadXML(xdoc, 0); part2.insertProject(part.getProject(), 0, 0); QCOMPARE(p2.numChildren(), 2); QVERIFY(! p2.childNode(0)->requests().isEmpty()); rr = p2.childNode(0)->requests().find(p2.resourceGroupAt(0)->resourceAt(0)); QVERIFY(rr); QCOMPARE(rr->resource()->teamMembers().count(), 2); QCOMPARE(rr->resource()->teamMembers().at(0), t1); QCOMPARE(rr->resource()->teamMembers().at(1), t2); QVERIFY(! p2.childNode(1)->requests().isEmpty()); rr = p2.childNode(1)->requests().find(p2.resourceGroupAt(0)->resourceAt(0)); QVERIFY(rr); QCOMPARE(rr->resource()->teamMembers().count(), 2); QCOMPARE(rr->resource()->teamMembers().at(0), t1); QCOMPARE(rr->resource()->teamMembers().at(1), t2); } } //namespace KPlato QTEST_MAIN(KPlato::InsertProjectTester)