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<