1 /* This file is part of the KDE project
2    Copyright (C) 2001 Thomas zander <zander@kde.org>
3    Copyright (C) 2004-2007, 2012 Dag Andersen <danders@get2net.dk>
4    Copyright (C) 2016 Dag Andersen <danders@get2net.dk>
5 
6    This library is free software; you can redistribute it and/or
7    modify it under the terms of the GNU Library General Public
8    License as published by the Free Software Foundation; either
9    version 2 of the License, or (at your option) any later version.
10 
11    This library is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14    Library General Public License for more details.
15 
16    You should have received a copy of the GNU Library General Public License
17    along with this library; see the file COPYING.LIB.  If not, write to
18    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20 */
21 
22 // clazy:excludeall=qstring-arg
23 #include "kptresource.h"
24 
25 #include "kptlocale.h"
26 #include "kptaccount.h"
27 #include "kptappointment.h"
28 #include "kptproject.h"
29 #include "kpttask.h"
30 #include "kptdatetime.h"
31 #include "kptcalendar.h"
32 #include "kpteffortcostmap.h"
33 #include "kptschedule.h"
34 #include "kptxmlloaderobject.h"
35 #include "kptdebug.h"
36 
37 #include <KoXmlReader.h>
38 
39 #include <KLocalizedString>
40 
41 #include <QLocale>
42 
43 
44 namespace KPlato
45 {
46 
ResourceGroup()47 ResourceGroup::ResourceGroup()
48     : QObject(0),
49     m_blockChanged(false),
50     m_shared(false)
51 {
52     m_project = 0;
53     m_type = Type_Work;
54     //debugPlan<<"("<<this<<")";
55 }
56 
ResourceGroup(const ResourceGroup * group)57 ResourceGroup::ResourceGroup(const ResourceGroup *group)
58     : QObject(0)
59 {
60     m_project = 0;
61     copy(group);
62 }
63 
~ResourceGroup()64 ResourceGroup::~ResourceGroup() {
65     //debugPlan<<"("<<this<<")";
66     if (findId() == this) {
67         removeId(); // only remove myself (I may be just a working copy)
68     }
69     foreach (ResourceGroupRequest* r, m_requests) {
70         r->unregister(this);
71     }
72     while (!m_resources.isEmpty()) {
73         delete m_resources.takeFirst();
74     }
75     //debugPlan<<"("<<this<<")";
76 }
77 
copy(const ResourceGroup * group)78 void ResourceGroup::copy(const ResourceGroup *group)
79 {
80     //m_project = group->m_project; //Don't copy
81     m_id = group->m_id;
82     m_type = group->m_type;
83     m_name = group->m_name;
84 }
85 
blockChanged(bool on)86 void ResourceGroup::blockChanged(bool on)
87 {
88     m_blockChanged = on;
89 }
90 
changed()91 void ResourceGroup::changed() {
92     if (m_project && !m_blockChanged) {
93         m_project->changed(this);
94     }
95 }
96 
setId(const QString & id)97 void ResourceGroup::setId(const QString& id) {
98     //debugPlan<<id;
99     m_id = id;
100 }
101 
setName(const QString & n)102 void ResourceGroup::setName(const QString& n)
103 {
104     m_name = n.trimmed();
105     changed();
106 }
107 
setType(Type type)108 void ResourceGroup::setType(Type type)
109 {
110      m_type = type;
111      changed();
112 }
113 
setType(const QString & type)114 void ResourceGroup::setType(const QString &type)
115 {
116     if (type == "Work")
117         setType(Type_Work);
118     else if (type == "Material")
119         setType(Type_Material);
120     else
121         setType(Type_Work);
122 }
123 
typeToString(bool trans) const124 QString ResourceGroup::typeToString(bool trans) const {
125     return typeToStringList(trans).at(m_type);
126 }
127 
typeToStringList(bool trans)128 QStringList ResourceGroup::typeToStringList(bool trans) {
129     // keep these in the same order as the enum!
130     return QStringList()
131             << (trans ? i18n("Work") : QString("Work"))
132             << (trans ? i18n("Material") : QString("Material"));
133 }
134 
setProject(Project * project)135 void ResourceGroup::setProject(Project *project)
136 {
137     if (project != m_project) {
138         if (m_project) {
139             removeId();
140         }
141     }
142     m_project = project;
143     foreach (Resource *r, m_resources) {
144         r->setProject(project);
145     }
146 }
147 
isScheduled() const148 bool ResourceGroup::isScheduled() const
149 {
150     foreach (Resource *r, m_resources) {
151         if (r->isScheduled()) {
152             return true;
153         }
154     }
155     return false;
156 }
157 
isBaselined(long id) const158 bool ResourceGroup::isBaselined(long id) const
159 {
160     Q_UNUSED(id);
161     foreach (const Resource *r, m_resources) {
162         if (r->isBaselined()) {
163             return true;
164         }
165     }
166     return false;
167 }
168 
169 
addResource(int index,Resource * resource,Risk *)170 void ResourceGroup::addResource(int index, Resource* resource, Risk*) {
171     int i = index == -1 ? m_resources.count() : index;
172     resource->setParentGroup(this);
173     resource->setProject(m_project);
174     m_resources.insert(i, resource);
175 }
176 
takeResource(Resource * resource)177 Resource *ResourceGroup::takeResource(Resource *resource) {
178     Resource *r = 0;
179     int i = m_resources.indexOf(resource);
180     if (i != -1) {
181         r = m_resources.takeAt(i);
182         r->setParentGroup(0);
183         r->setProject(0);
184     }
185     return r;
186 }
187 
indexOf(const Resource * resource) const188 int ResourceGroup::indexOf(const Resource *resource) const
189 {
190     return m_resources.indexOf(const_cast<Resource*>(resource)); //???
191 }
192 
getRisk(int)193 Risk* ResourceGroup::getRisk(int) {
194     return 0L;
195 }
196 
addRequiredResource(ResourceGroup *)197 void ResourceGroup::addRequiredResource(ResourceGroup*) {
198 }
199 
getRequiredResource(int)200 ResourceGroup* ResourceGroup::getRequiredResource(int) {
201     return 0L;
202 }
203 
deleteRequiredResource(int)204 void ResourceGroup::deleteRequiredResource(int) {
205 }
206 
load(KoXmlElement & element,XMLLoaderObject & status)207 bool ResourceGroup::load(KoXmlElement &element, XMLLoaderObject &status) {
208     //debugPlan;
209     setId(element.attribute("id"));
210     m_name = element.attribute("name");
211     setType(element.attribute("type"));
212     m_shared = element.attribute("shared", "0").toInt();
213 
214     KoXmlNode n = element.firstChild();
215     for (; ! n.isNull(); n = n.nextSibling()) {
216         if (! n.isElement()) {
217             continue;
218         }
219         KoXmlElement e = n.toElement();
220         if (e.tagName() == "resource") {
221             // Load the resource
222             Resource *child = new Resource();
223             if (child->load(e, status)) {
224                 addResource(-1, child, 0);
225             } else {
226                 // TODO: Complain about this
227                 delete child;
228             }
229         }
230     }
231     return true;
232 }
233 
save(QDomElement & element) const234 void ResourceGroup::save(QDomElement &element)  const {
235     //debugPlan;
236 
237     QDomElement me = element.ownerDocument().createElement("resource-group");
238     element.appendChild(me);
239 
240     me.setAttribute("id", m_id);
241     me.setAttribute("name", m_name);
242     me.setAttribute("type", typeToString());
243     me.setAttribute("shared", m_shared);
244 
245     foreach (Resource *r, m_resources) {
246         r->save(me);
247     }
248 }
249 
saveWorkPackageXML(QDomElement & element,const QList<Resource * > & lst) const250 void ResourceGroup::saveWorkPackageXML(QDomElement &element, const QList<Resource*> &lst) const
251 {
252     QDomElement me = element.ownerDocument().createElement("resource-group");
253     element.appendChild(me);
254 
255     me.setAttribute("id", m_id);
256     me.setAttribute("name", m_name);
257 
258     foreach (Resource *r, m_resources) {
259         if (lst.contains(r)) {
260             r->save(me);
261         }
262     }
263 }
264 
initiateCalculation(Schedule & sch)265 void ResourceGroup::initiateCalculation(Schedule &sch) {
266     foreach (Resource *r, m_resources) {
267         r->initiateCalculation(sch);
268     }
269     clearNodes();
270 }
271 
units() const272 int ResourceGroup::units() const {
273     int u = 0;
274     foreach (const Resource *r, m_resources) {
275         u += r->units();
276     }
277     return u;
278 }
279 
findId(const QString & id) const280 ResourceGroup *ResourceGroup::findId(const QString &id) const {
281     return m_project ? m_project->findResourceGroup(id) : 0;
282 }
283 
removeId(const QString & id)284 bool ResourceGroup::removeId(const QString &id) {
285     return m_project ? m_project->removeResourceGroupId(id): false;
286 }
287 
insertId(const QString & id)288 void ResourceGroup::insertId(const QString &id) {
289     //debugPlan;
290     if (m_project)
291         m_project->insertResourceGroupId(id, this);
292 }
293 
appointmentIntervals() const294 Appointment ResourceGroup::appointmentIntervals() const {
295     Appointment a;
296     foreach (Resource *r, m_resources) {
297         a += r->appointmentIntervals();
298     }
299     return a;
300 }
301 
startTime(long id) const302 DateTime ResourceGroup::startTime(long id) const
303 {
304     DateTime dt;
305     foreach (Resource *r, m_resources) {
306         DateTime t = r->startTime(id);
307         if (! dt.isValid() || t < dt) {
308             dt = t;
309         }
310     }
311     return dt;
312 }
313 
endTime(long id) const314 DateTime ResourceGroup::endTime(long id) const
315 {
316     DateTime dt;
317     foreach (Resource *r, m_resources) {
318         DateTime t = r->endTime(id);
319         if (! dt.isValid() || t > dt) {
320             dt = t;
321         }
322     }
323     return dt;
324 }
325 
isShared() const326 bool ResourceGroup::isShared() const
327 {
328     return m_shared;
329 }
330 
setShared(bool on)331 void ResourceGroup::setShared(bool on)
332 {
333     m_shared = on;
334 }
335 
Resource()336 Resource::Resource()
337     : QObject(0), // atm QObject is only for casting
338     m_project(0),
339     m_parent(0),
340     m_autoAllocate(false),
341     m_currentSchedule(0),
342     m_blockChanged(false),
343     m_shared(false)
344 {
345     m_type = Type_Work;
346     m_units = 100; // %
347 
348 //     m_availableFrom = DateTime(QDate::currentDate(), QTime(0, 0, 0));
349 //     m_availableUntil = m_availableFrom.addYears(2);
350 
351     cost.normalRate = 100;
352     cost.overtimeRate = 0;
353     cost.fixed = 0;
354     cost.account = 0;
355     m_calendar = 0;
356     m_currentSchedule = 0;
357     //debugPlan<<"("<<this<<")";
358 
359     // material: by default material is always available
360     for (int i = 1; i <= 7; ++i) {
361         CalendarDay *wd = m_materialCalendar.weekday(i);
362         wd->setState(CalendarDay::Working);
363         wd->addInterval(TimeInterval(QTime(0, 0, 0), 24*60*60*1000));
364     }
365 }
366 
Resource(Resource * resource)367 Resource::Resource(Resource *resource)
368     : QObject(0), // atm QObject is only for casting
369     m_project(0),
370     m_parent(0),
371     m_currentSchedule(0),
372     m_shared(false)
373 {
374     //debugPlan<<"("<<this<<") from ("<<resource<<")";
375     copy(resource);
376 }
377 
~Resource()378 Resource::~Resource() {
379     //debugPlan<<"("<<this<<")";
380     if (findId() == this) {
381         removeId(); // only remove myself (I may be just a working copy)
382     }
383     removeRequests();
384     foreach (Schedule *s, m_schedules) {
385         delete s;
386     }
387     clearExternalAppointments();
388     if (cost.account) {
389         cost.account->removeRunning(*this);
390     }
391 }
392 
removeRequests()393 void Resource::removeRequests() {
394     foreach (ResourceRequest *r, m_requests) {
395         r->setResource(0); // avoid the request to mess with my list
396         r->parent()->deleteResourceRequest(r);
397     }
398     m_requests.clear();
399 }
400 
setId(const QString & id)401 void Resource::setId(const QString& id) {
402     //debugPlan<<id;
403     m_id = id;
404 }
405 
copy(Resource * resource)406 void Resource::copy(Resource *resource) {
407     m_project = 0; // NOTE: Don't copy, will be set when added to a project
408     //m_appointments = resource->appointments(); // Note
409     m_id = resource->id();
410     m_name = resource->name();
411     m_initials = resource->initials();
412     m_email = resource->email();
413     m_autoAllocate = resource->m_autoAllocate;
414     m_availableFrom = resource->availableFrom();
415     m_availableUntil = resource->availableUntil();
416 
417     m_units = resource->units(); // available units in percent
418 
419     m_type = resource->type();
420 
421     cost.normalRate = resource->normalRate();
422     cost.overtimeRate = resource->overtimeRate();
423     cost.account = resource->account();
424     m_calendar = resource->m_calendar;
425 
426     m_requiredIds = resource->requiredIds();
427     m_teamMembers = resource->m_teamMembers;
428 
429     // hmmmm
430     //m_externalAppointments = resource->m_externalAppointments;
431     //m_externalNames = resource->m_externalNames;
432 }
433 
blockChanged(bool on)434 void Resource::blockChanged(bool on)
435 {
436     m_blockChanged = on;
437 }
438 
changed()439 void Resource::changed()
440 {
441     if (m_project && !m_blockChanged) {
442         m_project->changed(this);
443     }
444 }
445 
setType(Type type)446 void Resource::setType(Type type)
447 {
448     m_type = type;
449     changed();
450 }
451 
setType(const QString & type)452 void Resource::setType(const QString &type)
453 {
454     if (type == "Work")
455         setType(Type_Work);
456     else if (type == "Material")
457         setType(Type_Material);
458     else if (type == "Team")
459         setType(Type_Team);
460     else
461         setType(Type_Work);
462 }
463 
typeToString(bool trans) const464 QString Resource::typeToString(bool trans) const {
465     return typeToStringList(trans).at(m_type);
466 }
467 
typeToStringList(bool trans)468 QStringList Resource::typeToStringList(bool trans) {
469     // keep these in the same order as the enum!
470     return QStringList()
471             << (trans ? xi18nc("@item:inlistbox resource type", "Work") : QString("Work"))
472             << (trans ? xi18nc("@item:inlistbox resource type", "Material") : QString("Material"))
473             << (trans ? xi18nc("@item:inlistbox resource type", "Team") : QString("Team"));
474 }
475 
setName(const QString & n)476 void Resource::setName(const QString &n)
477 {
478     m_name = n.trimmed();
479     changed();
480 }
481 
setInitials(const QString & initials)482 void Resource::setInitials(const QString &initials)
483 {
484     m_initials = initials.trimmed();
485     changed();
486 }
487 
setEmail(const QString & email)488 void Resource::setEmail(const QString &email)
489 {
490     m_email = email;
491     changed();
492 }
493 
autoAllocate() const494 bool Resource::autoAllocate() const
495 {
496     return m_autoAllocate;
497 }
498 
setAutoAllocate(bool on)499 void Resource::setAutoAllocate(bool on)
500 {
501     if (m_autoAllocate != on) {
502         m_autoAllocate = on;
503         changed();
504     }
505 }
506 
setUnits(int units)507 void Resource::setUnits(int units)
508 {
509     m_units = units;
510     m_workinfocache.clear();
511     changed();
512 }
513 
calendar(bool local) const514 Calendar *Resource::calendar(bool local) const {
515     if (local || m_calendar) {
516         return m_calendar;
517     }
518     // No calendar is set, try default calendar
519     Calendar *c = 0;
520     if (m_type == Type_Work && project()) {
521         c =  project()->defaultCalendar();
522     } else if (m_type == Type_Material) {
523         c = const_cast<Calendar*>(&m_materialCalendar);
524     }
525     return c;
526 }
527 
setCalendar(Calendar * calendar)528 void Resource::setCalendar(Calendar *calendar)
529 {
530     m_calendar = calendar;
531     m_workinfocache.clear();
532     changed();
533 }
534 
firstAvailableAfter(const DateTime &,const DateTime &) const535 DateTime Resource::firstAvailableAfter(const DateTime &, const DateTime &) const {
536     return DateTime();
537 }
538 
getBestAvailableTime(const Duration &)539 DateTime Resource::getBestAvailableTime(const Duration &/*duration*/) {
540     return DateTime();
541 }
542 
getBestAvailableTime(const DateTime &,const Duration &)543 DateTime Resource::getBestAvailableTime(const DateTime &/*after*/, const Duration &/*duration*/) {
544     return DateTime();
545 }
546 
load(KoXmlElement & element,XMLLoaderObject & status)547 bool Resource::load(KoXmlElement &element, XMLLoaderObject &status) {
548     //debugPlan;
549     const Locale *locale = status.project().locale();
550     QString s;
551     setId(element.attribute("id"));
552     m_name = element.attribute("name");
553     m_initials = element.attribute("initials");
554     m_email = element.attribute("email");
555     m_autoAllocate = (bool)(element.attribute("auto-allocate", "0").toInt());
556     setType(element.attribute("type"));
557     m_shared = element.attribute("shared", "0").toInt();
558     m_calendar = status.project().findCalendar(element.attribute("calendar-id"));
559     m_units = element.attribute("units", "100").toInt();
560     s = element.attribute("available-from");
561     if (!s.isEmpty())
562         m_availableFrom = DateTime::fromString(s, status.projectTimeZone());
563     s = element.attribute("available-until");
564     if (!s.isEmpty())
565         m_availableUntil = DateTime::fromString(s, status.projectTimeZone());
566 
567     // NOTE: money was earlier (2.x) saved with symbol so we need to handle that
568     QString money = element.attribute("normal-rate");
569     bool ok = false;
570     cost.normalRate = money.toDouble(&ok);
571     if (!ok) {
572         cost.normalRate = locale->readMoney(money);
573         debugPlan<<"normal-rate failed, tried readMoney()"<<money<<"->"<<cost.normalRate;;
574     }
575     money = element.attribute("overtime-rate");
576     cost.overtimeRate = money.toDouble(&ok);
577     if (!ok) {
578         cost.overtimeRate = locale->readMoney(money);
579         debugPlan<<"overtime-rate failed, tried readMoney()"<<money<<"->"<<cost.overtimeRate;;
580     }
581     cost.account = status.project().accounts().findAccount(element.attribute("account"));
582 
583     KoXmlElement e;
584     KoXmlElement parent = element.namedItem("required-resources").toElement();
585     forEachElement(e, parent) {
586         if (e.nodeName() == "resource") {
587             QString id = e.attribute("id");
588             if (id.isEmpty()) {
589                 warnPlan<<"Missing resource id";
590                 continue;
591             }
592             addRequiredId(id);
593         }
594     }
595     parent = element.namedItem("external-appointments").toElement();
596     forEachElement(e, parent) {
597         if (e.nodeName() == "project") {
598             QString id = e.attribute("id");
599             if (id.isEmpty()) {
600                 errorPlan<<"Missing project id";
601                 continue;
602             }
603             clearExternalAppointments(id); // in case...
604             AppointmentIntervalList lst;
605             lst.loadXML(e, status);
606             Appointment *a = new Appointment();
607             a->setIntervals(lst);
608             a->setAuxcilliaryInfo(e.attribute("name", "Unknown"));
609             m_externalAppointments[ id ] = a;
610         }
611     }
612     // Do not load cache from old format, there was a bug (now fixed).
613     if (status.version() >= "0.6.7") {
614         loadCalendarIntervalsCache(element, status);
615     }
616     return true;
617 }
618 
requiredResources() const619 QList<Resource*> Resource::requiredResources() const
620 {
621     QList<Resource*> lst;
622     foreach (const QString &s, m_requiredIds) {
623         Resource *r = findId(s);
624         if (r) {
625             lst << r;
626         }
627     }
628     return lst;
629 }
630 
setRequiredIds(const QStringList & ids)631 void Resource::setRequiredIds(const QStringList &ids)
632 {
633     debugPlan<<ids;
634     m_requiredIds = ids;
635 }
636 
addRequiredId(const QString & id)637 void Resource::addRequiredId(const QString &id)
638 {
639     if (! id.isEmpty() && ! m_requiredIds.contains(id)) {
640         m_requiredIds << id;
641     }
642 }
643 
644 
setAccount(Account * account)645 void Resource::setAccount(Account *account)
646 {
647     if (cost.account) {
648         cost.account->removeRunning(*this);
649     }
650     cost.account = account;
651     changed();
652 }
653 
save(QDomElement & element) const654 void Resource::save(QDomElement &element) const {
655     //debugPlan;
656     QDomElement me = element.ownerDocument().createElement("resource");
657     element.appendChild(me);
658 
659     if (calendar(true))
660         me.setAttribute("calendar-id", m_calendar->id());
661     me.setAttribute("id", m_id);
662     me.setAttribute("name", m_name);
663     me.setAttribute("initials", m_initials);
664     me.setAttribute("email", m_email);
665     me.setAttribute("auto-allocate", m_autoAllocate);
666     me.setAttribute("type", typeToString());
667     me.setAttribute("shared", m_shared);
668     me.setAttribute("units", QString::number(m_units));
669     if (m_availableFrom.isValid()) {
670         me.setAttribute("available-from", m_availableFrom.toString(Qt::ISODate));
671     }
672     if (m_availableUntil.isValid()) {
673         me.setAttribute("available-until", m_availableUntil.toString(Qt::ISODate));
674     }
675     QString money;
676     me.setAttribute("normal-rate", money.setNum(cost.normalRate));
677     me.setAttribute("overtime-rate", money.setNum(cost.overtimeRate));
678     if (cost.account) {
679         me.setAttribute("account", cost.account->name());
680     }
681 
682     if (! m_requiredIds.isEmpty()) {
683         QDomElement e = me.ownerDocument().createElement("required-resources");
684         me.appendChild(e);
685         foreach (const QString &id, m_requiredIds) {
686             QDomElement el = e.ownerDocument().createElement("resource");
687             e.appendChild(el);
688             el.setAttribute("id", id);
689         }
690     }
691 
692     if (! m_externalAppointments.isEmpty()) {
693         QDomElement e = me.ownerDocument().createElement("external-appointments");
694         me.appendChild(e);
695         foreach (const QString &id, m_externalAppointments.uniqueKeys()) {
696             QDomElement el = e.ownerDocument().createElement("project");
697             e.appendChild(el);
698             el.setAttribute("id", id);
699             el.setAttribute("name", m_externalAppointments[ id ]->auxcilliaryInfo());
700             m_externalAppointments[ id ]->intervals().saveXML(el);
701         }
702     }
703     saveCalendarIntervalsCache(me);
704 }
705 
isAvailable(Task *)706 bool Resource::isAvailable(Task * /*task*/) {
707     bool busy = false;
708 /*
709     foreach (Appointment *a, m_appointments) {
710         if (a->isBusy(task->startTime(), task->endTime())) {
711             busy = true;
712             break;
713         }
714     }*/
715     return !busy;
716 }
717 
appointments(long id) const718 QList<Appointment*> Resource::appointments(long id) const {
719     Schedule *s = schedule(id);
720     if (s == 0) {
721         return QList<Appointment*>();
722     }
723     return s->appointments();
724 }
725 
addAppointment(Appointment * appointment)726 bool Resource::addAppointment(Appointment *appointment) {
727     if (m_currentSchedule)
728         return m_currentSchedule->add(appointment);
729     return false;
730 }
731 
addAppointment(Appointment * appointment,Schedule & main)732 bool Resource::addAppointment(Appointment *appointment, Schedule &main) {
733     Schedule *s = findSchedule(main.id());
734     if (s == 0) {
735         s = createSchedule(&main);
736     }
737     appointment->setResource(s);
738     return s->add(appointment);
739 }
740 
741 // called from makeAppointment
addAppointment(Schedule * node,const DateTime & start,const DateTime & end,double load)742 void Resource::addAppointment(Schedule *node, const DateTime &start, const DateTime &end, double load)
743 {
744     Q_ASSERT(start < end);
745     Schedule *s = findSchedule(node->id());
746     if (s == 0) {
747         s = createSchedule(node->parent());
748     }
749     s->setCalculationMode(node->calculationMode());
750     //debugPlan<<"id="<<node->id()<<" Mode="<<node->calculationMode()<<""<<start<<end;
751     s->addAppointment(node, start, end, load);
752 }
753 
initiateCalculation(Schedule & sch)754 void Resource::initiateCalculation(Schedule &sch) {
755     m_currentSchedule = createSchedule(&sch);
756 }
757 
schedule(long id) const758 Schedule *Resource::schedule(long id) const
759 {
760     return id == -1 ? m_currentSchedule : findSchedule(id);
761 }
762 
isBaselined(long id) const763 bool Resource::isBaselined(long id) const
764 {
765     if (m_type == Resource::Type_Team) {
766         foreach (const Resource *r, teamMembers()) {
767             if (r->isBaselined(id)) {
768                 return true;
769             }
770         }
771         return false;
772     }
773     Schedule *s = schedule(id);
774     return s ? s->isBaselined() : false;
775 }
776 
findSchedule(long id) const777 Schedule *Resource::findSchedule(long id) const
778 {
779     if (m_schedules.contains(id)) {
780         return m_schedules[ id ];
781     }
782     if (id == CURRENTSCHEDULE) {
783         return m_currentSchedule;
784     }
785     if (id == BASELINESCHEDULE || id == ANYSCHEDULED) {
786         foreach (Schedule *s, m_schedules) {
787             if (s->isBaselined()) {
788                 return s;
789             }
790         }
791     }
792     if (id == ANYSCHEDULED) {
793         foreach (Schedule *s, m_schedules) {
794             if (s->isScheduled()) {
795                 return s;
796             }
797         }
798     }
799     return 0;
800 }
801 
isScheduled() const802 bool Resource::isScheduled() const
803 {
804     foreach (Schedule *s, m_schedules) {
805         if (s->isScheduled()) {
806             return true;
807         }
808     }
809     return false;
810 }
811 
deleteSchedule(Schedule * schedule)812 void Resource::deleteSchedule(Schedule *schedule) {
813     takeSchedule(schedule);
814     delete schedule;
815 }
816 
takeSchedule(const Schedule * schedule)817 void Resource::takeSchedule(const Schedule *schedule) {
818     if (schedule == 0)
819         return;
820     if (m_currentSchedule == schedule)
821         m_currentSchedule = 0;
822     m_schedules.take(schedule->id());
823 }
824 
addSchedule(Schedule * schedule)825 void Resource::addSchedule(Schedule *schedule) {
826     if (schedule == 0)
827         return;
828     m_schedules.remove(schedule->id());
829     m_schedules.insert(schedule->id(), schedule);
830 }
831 
createSchedule(const QString & name,int type,long id)832 ResourceSchedule *Resource::createSchedule(const QString& name, int type, long id) {
833     ResourceSchedule *sch = new ResourceSchedule(this, name, (Schedule::Type)type, id);
834     addSchedule(sch);
835     return sch;
836 }
837 
createSchedule(Schedule * parent)838 ResourceSchedule *Resource::createSchedule(Schedule *parent) {
839     ResourceSchedule *sch = new ResourceSchedule(parent, this);
840     //debugPlan<<"id="<<sch->id();
841     addSchedule(sch);
842     return sch;
843 }
844 
timeZone() const845 QTimeZone Resource::timeZone() const
846 {
847     Calendar *cal = calendar();
848 
849     return
850         cal ?       cal->timeZone() :
851         m_project ? m_project->timeZone() :
852         /* else */  QTimeZone();
853 }
854 
requiredAvailable(Schedule * node,const DateTime & start,const DateTime & end) const855 DateTimeInterval Resource::requiredAvailable(Schedule *node, const DateTime &start, const DateTime &end) const
856 {
857     Q_ASSERT(m_currentSchedule);
858     DateTimeInterval interval(start, end);
859 #ifndef PLAN_NLOGDEBUG
860     if (m_currentSchedule) m_currentSchedule->logDebug(QString("Required available in interval: %1").arg(interval.toString()));
861 #endif
862     DateTime availableFrom = m_availableFrom.isValid() ? m_availableFrom : (m_project ? m_project->constraintStartTime() : DateTime());
863     DateTime availableUntil = m_availableUntil.isValid() ? m_availableUntil : (m_project ? m_project->constraintEndTime() : DateTime());
864     DateTimeInterval x = interval.limitedTo(availableFrom, availableUntil);
865     if (calendar() == 0) {
866 #ifndef PLAN_NLOGDEBUG
867         if (m_currentSchedule) m_currentSchedule->logDebug(QString("Required available: no calendar, %1").arg(x.toString()));
868 #endif
869         return x;
870     }
871     DateTimeInterval i = m_currentSchedule->firstBookedInterval(x, node);
872     if (i.isValid()) {
873 #ifndef PLAN_NLOGDEBUG
874         if (m_currentSchedule) m_currentSchedule->logDebug(QString("Required available: booked, %1").arg(i.toString()));
875 #endif
876         return i;
877     }
878     i = calendar()->firstInterval(x.first, x.second, m_currentSchedule);
879 #ifndef PLAN_NLOGDEBUG
880     if (m_currentSchedule) m_currentSchedule->logDebug(QString("Required first available in %1:  %2").arg(x.toString()).arg(i.toString()));
881 #endif
882     return i;
883 }
884 
makeAppointment(Schedule * node,const DateTime & from,const DateTime & end,int load,const QList<Resource * > & required)885 void Resource::makeAppointment(Schedule *node, const DateTime &from, const DateTime &end, int load, const QList<Resource*> &required) {
886     //debugPlan<<"node id="<<node->id()<<" mode="<<node->calculationMode()<<""<<from<<" -"<<end;
887     if (!from.isValid() || !end.isValid()) {
888         m_currentSchedule->logWarning(i18n("Make appointments: Invalid time"));
889         return;
890     }
891     Calendar *cal = calendar();
892     if (cal == 0) {
893         m_currentSchedule->logWarning(i18n("Resource %1 has no calendar defined", m_name));
894         return;
895     }
896 #ifndef PLAN_NLOGDEBUG
897     if (m_currentSchedule) {
898         QStringList lst; foreach (Resource *r, required) { lst << r->name(); }
899         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));
900     }
901 #endif
902     AppointmentIntervalList lst = workIntervals(from, end, m_currentSchedule);
903     foreach (const AppointmentInterval &i, lst.map()) {
904         m_currentSchedule->addAppointment(node, i.startTime(), i.endTime(), load);
905         foreach (Resource *r, required) {
906             r->addAppointment(node, i.startTime(), i.endTime(), r->units()); //FIXME: units may not be correct
907         }
908     }
909 }
910 
makeAppointment(Schedule * node,int load,const QList<Resource * > & required)911 void Resource::makeAppointment(Schedule *node, int load, const QList<Resource*> &required) {
912     //debugPlan<<m_name<<": id="<<m_currentSchedule->id()<<" mode="<<m_currentSchedule->calculationMode()<<node->node()->name()<<": id="<<node->id()<<" mode="<<node->calculationMode()<<""<<node->startTime;
913     QLocale locale;
914     if (!node->startTime.isValid()) {
915         m_currentSchedule->logWarning(i18n("Make appointments: Node start time is not valid"));
916         return;
917     }
918     if (!node->endTime.isValid()) {
919         m_currentSchedule->logWarning(i18n("Make appointments: Node end time is not valid"));
920         return;
921     }
922     if (m_type == Type_Team) {
923 #ifndef PLAN_NLOGDEBUG
924         m_currentSchedule->logDebug("Make appointments to team " + m_name);
925 #endif
926         Duration e;
927         foreach (Resource *r, teamMembers()) {
928             r->makeAppointment(node, load, required);
929         }
930         return;
931     }
932     node->resourceNotAvailable = false;
933     node->workStartTime = DateTime();
934     node->workEndTime = DateTime();
935     Calendar *cal = calendar();
936     if (m_type == Type_Material) {
937         DateTime from = availableAfter(node->startTime, node->endTime);
938         DateTime end = availableBefore(node->endTime, node->startTime);
939         if (!from.isValid() || !end.isValid()) {
940             return;
941         }
942         if (cal == 0) {
943             // Allocate the whole period
944             addAppointment(node, from, end, m_units);
945             return;
946         }
947         makeAppointment(node, from, end, load);
948         return;
949     }
950     if (!cal) {
951         m_currentSchedule->logWarning(i18n("Resource %1 has no calendar defined", m_name));
952         return;
953     }
954     DateTime time = node->startTime;
955     DateTime end = node->endTime;
956     if (time == end) {
957 #ifndef PLAN_NLOGDEBUG
958         m_currentSchedule->logDebug(QString("Task '%1' start time == end time: %2").arg(node->node()->name(), time.toString(Qt::ISODate)));
959 #endif
960         node->resourceNotAvailable = true;
961         return;
962     }
963     time = availableAfter(time, end);
964     if (!time.isValid()) {
965         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)));
966         node->resourceNotAvailable = true;
967         return;
968     }
969     end = availableBefore(end, time);
970     foreach (Resource *r, required) {
971         time = r->availableAfter(time, end);
972         end = r->availableBefore(end, time);
973         if (! (time.isValid() && end.isValid())) {
974 #ifndef PLAN_NLOGDEBUG
975             if (m_currentSchedule) m_currentSchedule->logDebug("The required resource '" + r->name() + "'is not available in interval:" + node->startTime.toString() + ',' + node->endTime.toString());
976 #endif
977             break;
978         }
979     }
980     if (!end.isValid()) {
981         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)));
982         node->resourceNotAvailable = true;
983         return;
984     }
985     //debugPlan<<time.toString()<<" to"<<end.toString();
986     makeAppointment(node, time, end, load, required);
987 }
988 
workIntervals(const DateTime & from,const DateTime & until) const989 AppointmentIntervalList Resource::workIntervals(const DateTime &from, const DateTime &until) const
990 {
991     return workIntervals(from, until, 0);
992 }
993 
workIntervals(const DateTime & from,const DateTime & until,Schedule * sch) const994 AppointmentIntervalList Resource::workIntervals(const DateTime &from, const DateTime &until, Schedule *sch) const
995 {
996     Calendar *cal = calendar();
997     if (cal == 0) {
998         return AppointmentIntervalList();
999     }
1000     // update cache
1001     calendarIntervals(from, until);
1002     AppointmentIntervalList work = m_workinfocache.intervals.extractIntervals(from, until);
1003     if (sch && ! sch->allowOverbooking()) {
1004         foreach (const Appointment *a, sch->appointments(sch->calculationMode())) {
1005             work -= a->intervals();
1006         }
1007         foreach (const Appointment *a, m_externalAppointments) {
1008             work -= a->intervals();
1009         }
1010     }
1011     return work;
1012 }
1013 
calendarIntervals(const DateTime & dtFrom,const DateTime & dtUntil) const1014 void Resource::calendarIntervals(const DateTime &dtFrom, const DateTime &dtUntil) const
1015 {
1016     Calendar *cal = calendar();
1017     if (cal == 0) {
1018         m_workinfocache.clear();
1019         return;
1020     }
1021     if (cal->cacheVersion() != m_workinfocache.version) {
1022         m_workinfocache.clear();
1023         m_workinfocache.version = cal->cacheVersion();
1024     }
1025     const DateTime from = dtFrom.toTimeZone(timeZone());
1026     const DateTime until = dtUntil.toTimeZone(timeZone());
1027     if (! m_workinfocache.isValid()) {
1028         // First time
1029 //         debugPlan<<"First time:"<<from<<until;
1030         m_workinfocache.start = from;
1031         m_workinfocache.end = until;
1032         m_workinfocache.intervals = cal->workIntervals(from, until, m_units);
1033 //         debugPlan<<"calendarIntervals (first):"<<m_workinfocache.intervals;
1034     } else {
1035         if (from < m_workinfocache.start) {
1036 //             debugPlan<<"Add to start:"<<from<<m_workinfocache.start;
1037             m_workinfocache.intervals += cal->workIntervals(from, m_workinfocache.start, m_units);
1038             m_workinfocache.start = from;
1039 //             debugPlan<<"calendarIntervals (start):"<<m_workinfocache.intervals;
1040         }
1041         if (until > m_workinfocache.end) {
1042 //             debugPlan<<"Add to end:"<<m_workinfocache.end<<until;
1043             m_workinfocache.intervals += cal->workIntervals(m_workinfocache.end, until, m_units);
1044             m_workinfocache.end = until;
1045 //             debugPlan<<"calendarIntervals: (end)"<<m_workinfocache.intervals;
1046         }
1047     }
1048 }
1049 
loadCalendarIntervalsCache(const KoXmlElement & element,XMLLoaderObject & status)1050 bool Resource::loadCalendarIntervalsCache(const KoXmlElement &element, XMLLoaderObject &status)
1051 {
1052     KoXmlElement e = element.namedItem("work-intervals-cache").toElement();
1053     if (e.isNull()) {
1054         errorPlan<<"No 'work-intervals-cache' element";
1055         return true;
1056     }
1057     m_workinfocache.load(e, status);
1058     return true;
1059 }
1060 
saveCalendarIntervalsCache(QDomElement & element) const1061 void Resource::saveCalendarIntervalsCache(QDomElement &element) const
1062 {
1063     QDomElement me = element.ownerDocument().createElement("work-intervals-cache");
1064     element.appendChild(me);
1065     m_workinfocache.save(me);
1066 }
1067 
firstAvailableAfter(const DateTime & time,const DateTime & limit,Calendar * cal,Schedule * sch) const1068 DateTime Resource::WorkInfoCache::firstAvailableAfter(const DateTime &time, const DateTime &limit, Calendar *cal, Schedule *sch) const
1069 {
1070     QMultiMap<QDate, AppointmentInterval>::const_iterator it = intervals.map().constEnd();
1071     if (start.isValid() && start <= time) {
1072         // possibly useful cache
1073         it = intervals.map().lowerBound(time.date());
1074     }
1075     if (it == intervals.map().constEnd()) {
1076         // nothing cached, check the old way
1077         DateTime t = cal ? cal->firstAvailableAfter(time, limit, sch) : DateTime();
1078         return t;
1079     }
1080     AppointmentInterval inp(time, limit);
1081     for (; it != intervals.map().constEnd() && it.key() <= limit.date(); ++it) {
1082         if (! it.value().intersects(inp) && it.value() < inp) {
1083             continue;
1084         }
1085         if (sch) {
1086             DateTimeInterval ti = sch->available(DateTimeInterval(it.value().startTime(), it.value().endTime()));
1087             if (ti.isValid() && ti.second > time && ti.first < limit) {
1088                 ti.first = qMax(ti.first, time);
1089                 return ti.first;
1090             }
1091         } else {
1092             DateTime t = qMax(it.value().startTime(), time);
1093             return t;
1094         }
1095     }
1096     if (it == intervals.map().constEnd()) {
1097         // ran out of cache, check the old way
1098         DateTime t = cal ? cal->firstAvailableAfter(time, limit, sch) : DateTime();
1099         return t;
1100     }
1101     return DateTime();
1102 }
1103 
firstAvailableBefore(const DateTime & time,const DateTime & limit,Calendar * cal,Schedule * sch) const1104 DateTime Resource::WorkInfoCache::firstAvailableBefore(const DateTime &time, const DateTime &limit, Calendar *cal, Schedule *sch) const
1105 {
1106     if (time <= limit) {
1107         return DateTime();
1108     }
1109     QMultiMap<QDate, AppointmentInterval>::const_iterator it = intervals.map().constBegin();
1110     if (time.isValid() && limit.isValid() && end.isValid() && end >= time && ! intervals.isEmpty()) {
1111         // possibly useful cache
1112         it = intervals.map().upperBound(time.date());
1113     }
1114     if (it == intervals.map().constBegin()) {
1115         // nothing cached, check the old way
1116         DateTime t = cal ? cal->firstAvailableBefore(time, limit, sch) : DateTime();
1117         return t;
1118     }
1119     AppointmentInterval inp(limit, time);
1120     for (--it; it != intervals.map().constBegin() && it.key() >= limit.date(); --it) {
1121         if (! it.value().intersects(inp) && inp < it.value()) {
1122             continue;
1123         }
1124         if (sch) {
1125             DateTimeInterval ti = sch->available(DateTimeInterval(it.value().startTime(), it.value().endTime()));
1126             if (ti.isValid() && ti.second > limit) {
1127                 ti.second = qMin(ti.second, time);
1128                 return ti.second;
1129             }
1130         } else {
1131             DateTime t = qMin(it.value().endTime(), time);
1132             return t;
1133         }
1134     }
1135     if (it == intervals.map().constBegin()) {
1136         // ran out of cache, check the old way
1137         DateTime t = cal ? cal->firstAvailableBefore(time, limit, sch) : DateTime();
1138         return t;
1139     }
1140     return DateTime();
1141 }
1142 
load(const KoXmlElement & element,XMLLoaderObject & status)1143 bool Resource::WorkInfoCache::load(const KoXmlElement &element, XMLLoaderObject &status)
1144 {
1145     clear();
1146     version = element.attribute("version").toInt();
1147     effort = Duration::fromString(element.attribute("effort"));
1148     // DateTime should always be saved in the projects timezone,
1149     // but due to a bug (fixed) this did not always happen.
1150     // This code takes care of this situation.
1151     start = QDateTime::fromString(element.attribute("start"), Qt::ISODate).toTimeZone(status.projectTimeZone());
1152     end = QDateTime::fromString(element.attribute("end"), Qt::ISODate).toTimeZone(status.projectTimeZone());
1153     KoXmlElement e = element.namedItem("intervals").toElement();
1154     if (! e.isNull()) {
1155         intervals.loadXML(e, status);
1156     }
1157     //debugPlan<<*this;
1158     return true;
1159 }
1160 
save(QDomElement & element) const1161 void Resource::WorkInfoCache::save(QDomElement &element) const
1162 {
1163     element.setAttribute("version", QString::number(version));
1164     element.setAttribute("effort", effort.toString());
1165     element.setAttribute("start", start.toString(Qt::ISODate));
1166     element.setAttribute("end", end.toString(Qt::ISODate));
1167     QDomElement me = element.ownerDocument().createElement("intervals");
1168     element.appendChild(me);
1169 
1170     intervals.saveXML(me);
1171 }
1172 
effort(const DateTime & start,const Duration & duration,int units,bool backward,const QList<Resource * > & required) const1173 Duration Resource::effort(const DateTime& start, const Duration& duration, int units, bool backward, const QList< Resource* >& required) const
1174 {
1175     return effort(m_currentSchedule, start, duration, units, backward, required);
1176 }
1177 
1178 // the amount of effort we can do within the duration
effort(Schedule * sch,const DateTime & start,const Duration & duration,int units,bool backward,const QList<Resource * > & required) const1179 Duration Resource::effort(Schedule *sch, const DateTime &start, const Duration &duration, int units, bool backward, const QList<Resource*> &required) const
1180 {
1181     //debugPlan<<m_name<<": ("<<(backward?"B)":"F)")<<start<<" for duration"<<duration.toString(Duration::Format_Day);
1182 #if 0
1183     if (sch) sch->logDebug(QString("Check effort in interval %1: %2, %3").arg(backward?"backward":"forward").arg(start.toString()).arg((backward?start-duration:start+duration).toString()));
1184 #endif
1185     Duration e;
1186     if (duration == 0 || m_units == 0 || units == 0) {
1187         warnPlan<<"zero duration or zero units";
1188         return e;
1189     }
1190     if (m_type == Type_Team) {
1191         errorPlan<<"A team resource cannot deliver any effort";
1192         return e;
1193     }
1194     Calendar *cal = calendar();
1195     if (cal == 0) {
1196         if (sch) sch->logWarning(i18n("Resource %1 has no calendar defined", m_name));
1197         return e;
1198     }
1199     DateTime from;
1200     DateTime until;
1201     if (backward) {
1202         from = availableAfter(start - duration, start, sch);
1203         until = availableBefore(start, start - duration, sch);
1204     } else {
1205         from = availableAfter(start, start + duration, sch);
1206         until = availableBefore(start + duration, start, sch);
1207     }
1208     if (! (from.isValid() && until.isValid())) {
1209 #ifndef PLAN_NLOGDEBUG
1210         if (sch) sch->logDebug("Resource not available in interval:" + start.toString() + ',' + (start+duration).toString());
1211 #endif
1212     } else {
1213         foreach (Resource *r, required) {
1214             from = r->availableAfter(from, until);
1215             until = r->availableBefore(until, from);
1216             if (! (from.isValid() && until.isValid())) {
1217 #ifndef PLAN_NLOGDEBUG
1218                 if (sch) sch->logDebug("The required resource '" + r->name() + "'is not available in interval:" + start.toString() + ',' + (start+duration).toString());
1219 #endif
1220                     break;
1221             }
1222         }
1223     }
1224     if (from.isValid() && until.isValid()) {
1225 #ifndef PLAN_NLOGDEBUG
1226         if (sch && until < from) sch->logDebug(" until < from: until=" + until.toString() + " from=" + from.toString());
1227 #endif
1228         e = workIntervals(from, until).effort(from, until) * units / 100;
1229         if (sch && (! sch->allowOverbooking() || sch->allowOverbookingState() == Schedule::OBS_Deny)) {
1230             Duration avail = workIntervals(from, until, sch).effort(from, until);
1231             if (avail < e) {
1232                 e = avail;
1233             }
1234         }
1235 //        e = (cal->effort(from, until, sch)) * m_units / 100;
1236     }
1237     //debugPlan<<m_name<<start<<" e="<<e.toString(Duration::Format_Day)<<" ("<<m_units<<")";
1238 #ifndef PLAN_NLOGDEBUG
1239     if (sch) sch->logDebug(QString("effort: %1 for %2 hours = %3").arg(start.toString()).arg(duration.toString(Duration::Format_HourFraction)).arg(e.toString(Duration::Format_HourFraction)));
1240 #endif
1241     return e;
1242 }
1243 
availableAfter(const DateTime & time,const DateTime & limit) const1244 DateTime Resource::availableAfter(const DateTime &time, const DateTime &limit) const {
1245     return availableAfter(time, limit, m_currentSchedule);
1246 }
1247 
availableBefore(const DateTime & time,const DateTime & limit) const1248 DateTime Resource::availableBefore(const DateTime &time, const DateTime &limit) const {
1249     return availableBefore(time, limit, m_currentSchedule);
1250 }
1251 
availableAfter(const DateTime & time,const DateTime & limit,Schedule * sch) const1252 DateTime Resource::availableAfter(const DateTime &time, const DateTime &limit, Schedule *sch) const {
1253 //     debugPlan<<time<<limit;
1254     DateTime t;
1255     if (m_units == 0) {
1256         debugPlan<<this<<"zero units";
1257         return t;
1258     }
1259     DateTime lmt = m_availableUntil.isValid() ? m_availableUntil : (m_project ? m_project->constraintEndTime() : DateTime());
1260     if (limit.isValid() && limit < lmt) {
1261         lmt = limit;
1262     }
1263     if (time >= lmt) {
1264         debugPlan<<this<<"time >= limit"<<time<<lmt<<m_project;
1265         return t;
1266     }
1267     Calendar *cal = calendar();
1268     if (cal == 0) {
1269         if (sch) sch->logWarning(i18n("Resource %1 has no calendar defined", m_name));
1270         debugPlan<<this<<"No calendar";
1271         return t;
1272     }
1273     DateTime availableFrom = m_availableFrom.isValid() ? m_availableFrom : (m_project ? m_project->constraintStartTime() : DateTime());
1274     t = availableFrom > time ? availableFrom : time;
1275     if (t >= lmt) {
1276         debugPlan<<this<<t<<lmt;
1277         return DateTime();
1278     }
1279     QTimeZone tz = cal->timeZone();
1280     t = t.toTimeZone(tz);
1281     lmt = lmt.toTimeZone(tz);
1282     t = m_workinfocache.firstAvailableAfter(t, lmt, cal, sch);
1283 //    t = cal->firstAvailableAfter(t, lmt, sch);
1284     //if (sch) debugPlan<<sch<<""<<m_name<<" id="<<sch->id()<<" mode="<<sch->calculationMode()<<" returns:"<<time.toString()<<"="<<t.toString()<<""<<lmt.toString();
1285     return t;
1286 }
1287 
availableBefore(const DateTime & time,const DateTime & limit,Schedule * sch) const1288 DateTime Resource::availableBefore(const DateTime &time, const DateTime &limit, Schedule *sch) const {
1289     DateTime t;
1290     if (m_units == 0) {
1291         return t;
1292     }
1293     DateTime lmt = m_availableFrom.isValid() ? m_availableFrom : (m_project ? m_project->constraintStartTime() : DateTime());
1294     if (limit.isValid() && limit > lmt) {
1295         lmt = limit;
1296     }
1297     if (time <= lmt) {
1298         return t;
1299     }
1300     Calendar *cal = calendar();
1301     if (cal == 0) {
1302         return t;
1303     }
1304     DateTime availableUntil = m_availableUntil.isValid() ? m_availableUntil : (m_project ? m_project->constraintEndTime() : DateTime());
1305     if (! availableUntil.isValid()) {
1306 #ifndef PLAN_NLOGDEBUG
1307         if (sch) sch->logDebug("availableUntil is invalid");
1308 #endif
1309         t = time;
1310     } else {
1311         t = availableUntil < time ? availableUntil : time;
1312     }
1313 #ifndef PLAN_NLOGDEBUG
1314     if (sch && t < lmt) sch->logDebug("t < lmt: " + t.toString() + " < " + lmt.toString());
1315 #endif
1316     QTimeZone tz = cal->timeZone();
1317     t = t.toTimeZone(tz);
1318     lmt = lmt.toTimeZone(tz);
1319     t = m_workinfocache.firstAvailableBefore(t, lmt, cal, sch);
1320 //    t = cal->firstAvailableBefore(t, lmt, sch);
1321 #ifndef PLAN_NLOGDEBUG
1322     if (sch && t.isValid() && t < lmt) sch->logDebug(" t < lmt: t=" + t.toString() + " lmt=" + lmt.toString());
1323 #endif
1324     return t;
1325 }
1326 
findId(const QString & id) const1327 Resource *Resource::findId(const QString &id) const {
1328     return m_project ? m_project->findResource(id) : 0;
1329 }
1330 
removeId(const QString & id)1331 bool Resource::removeId(const QString &id) {
1332     return m_project ? m_project->removeResourceId(id) : false;
1333 }
1334 
insertId(const QString & id)1335 void Resource::insertId(const QString &id) {
1336     //debugPlan;
1337     if (m_project)
1338         m_project->insertResourceId(id, this);
1339 }
1340 
findCalendar(const QString & id) const1341 Calendar *Resource::findCalendar(const QString &id) const {
1342     return (m_project ? m_project->findCalendar(id) : 0);
1343 }
1344 
isOverbooked() const1345 bool Resource::isOverbooked() const {
1346     return isOverbooked(DateTime(), DateTime());
1347 }
1348 
isOverbooked(const QDate & date) const1349 bool Resource::isOverbooked(const QDate &date) const {
1350     return isOverbooked(DateTime(date), DateTime(date.addDays(1)));
1351 }
1352 
isOverbooked(const DateTime & start,const DateTime & end) const1353 bool Resource::isOverbooked(const DateTime &start, const DateTime &end) const {
1354     //debugPlan<<m_name<<":"<<start.toString()<<" -"<<end.toString()<<" cs=("<<m_currentSchedule<<")";
1355     return m_currentSchedule ? m_currentSchedule->isOverbooked(start, end) : false;
1356 }
1357 
appointmentIntervals(long id) const1358 Appointment Resource::appointmentIntervals(long id) const {
1359     Appointment a;
1360     Schedule *s = findSchedule(id);
1361     if (s == 0) {
1362         return a;
1363     }
1364     foreach (Appointment *app, static_cast<ResourceSchedule*>(s)->appointments()) {
1365         a += *app;
1366     }
1367     return a;
1368 }
1369 
appointmentIntervals() const1370 Appointment Resource::appointmentIntervals() const {
1371     Appointment a;
1372     if (m_currentSchedule == 0)
1373         return a;
1374     foreach (Appointment *app, m_currentSchedule->appointments()) {
1375         a += *app;
1376     }
1377     return a;
1378 }
1379 
plannedEffortCostPrDay(const QDate & start,const QDate & end,long id,EffortCostCalculationType typ)1380 EffortCostMap Resource::plannedEffortCostPrDay(const QDate &start, const QDate &end, long id, EffortCostCalculationType typ)
1381 {
1382     EffortCostMap ec;
1383     Schedule *s = findSchedule(id);
1384     if (s == 0) {
1385         return ec;
1386     }
1387     ec = s->plannedEffortCostPrDay(start, end, typ);
1388     return ec;
1389 }
1390 
plannedEffort(const QDate & date,EffortCostCalculationType typ) const1391 Duration Resource::plannedEffort(const QDate &date, EffortCostCalculationType typ) const
1392 {
1393     return m_currentSchedule ? m_currentSchedule->plannedEffort(date, typ) : Duration::zeroDuration;
1394 }
1395 
setProject(Project * project)1396 void Resource::setProject(Project *project)
1397 {
1398     if (project != m_project) {
1399         if (m_project) {
1400             removeId();
1401         }
1402     }
1403     m_project = project;
1404 }
1405 
addExternalAppointment(const QString & id,Appointment * a)1406 void Resource::addExternalAppointment(const QString& id, Appointment* a)
1407 {
1408     int row = -1;
1409     if (m_externalAppointments.contains(id)) {
1410         int row = m_externalAppointments.keys().indexOf(id); // clazy:exclude=container-anti-pattern
1411         emit externalAppointmentToBeRemoved(this, row);
1412         delete m_externalAppointments.take(id);
1413         emit externalAppointmentRemoved();
1414     }
1415     if (row == -1) {
1416         m_externalAppointments[ id ] = a;
1417         row = m_externalAppointments.keys().indexOf(id); // clazy:exclude=container-anti-pattern
1418         m_externalAppointments.remove(id);
1419     }
1420     emit externalAppointmentToBeAdded(this, row);
1421     m_externalAppointments[ id ] = a;
1422     emit externalAppointmentAdded(this, a);
1423 }
1424 
addExternalAppointment(const QString & id,const QString & name,const DateTime & from,const DateTime & end,double load)1425 void Resource::addExternalAppointment(const QString &id, const QString &name, const DateTime &from, const DateTime &end, double load)
1426 {
1427     Appointment *a = m_externalAppointments.value(id);
1428     if (a == 0) {
1429         a = new Appointment();
1430         a->setAuxcilliaryInfo(name);
1431         a->addInterval(from, end, load);
1432         //debugPlan<<m_name<<name<<"new appointment:"<<a<<from<<end<<load;
1433         m_externalAppointments[ id ] = a;
1434         int row = m_externalAppointments.keys().indexOf(id); // clazy:exclude=container-anti-pattern
1435         m_externalAppointments.remove(id);
1436         emit externalAppointmentToBeAdded(this, row);
1437         m_externalAppointments[ id ] = a;
1438         emit externalAppointmentAdded(this, a);
1439     } else {
1440         //debugPlan<<m_name<<name<<"new interval:"<<a<<from<<end<<load;
1441         a->addInterval(from, end, load);
1442         emit externalAppointmentChanged(this, a);
1443     }
1444 }
1445 
subtractExternalAppointment(const QString & id,const DateTime & start,const DateTime & end,double load)1446 void Resource::subtractExternalAppointment(const QString &id, const DateTime &start, const DateTime &end, double load)
1447 {
1448     Appointment *a = m_externalAppointments.value(id);
1449     if (a) {
1450         //debugPlan<<m_name<<name<<"new interval:"<<a<<from<<end<<load;
1451         Appointment app;
1452         app.addInterval(start, end, load);
1453         *a -= app;
1454         emit externalAppointmentChanged(this, a);
1455     }
1456 }
1457 
clearExternalAppointments()1458 void Resource::clearExternalAppointments()
1459 {
1460     const QStringList keys = m_externalAppointments.keys();
1461     foreach (const QString &id, keys) {
1462         clearExternalAppointments(id);
1463     }
1464 }
1465 
clearExternalAppointments(const QString & projectId)1466 void Resource::clearExternalAppointments(const QString &projectId)
1467 {
1468     while (m_externalAppointments.contains(projectId)) {
1469         int row = m_externalAppointments.keys().indexOf(projectId); // clazy:exclude=container-anti-pattern
1470         emit externalAppointmentToBeRemoved(this, row);
1471         Appointment *a  = m_externalAppointments.take(projectId);
1472         emit externalAppointmentRemoved();
1473         delete a;
1474     }
1475 }
1476 
takeExternalAppointment(const QString & id)1477 Appointment *Resource::takeExternalAppointment(const QString &id)
1478 {
1479     Appointment *a = 0;
1480     if (m_externalAppointments.contains(id)) {
1481         int row = m_externalAppointments.keys().indexOf(id); // clazy:exclude=container-anti-pattern
1482         emit externalAppointmentToBeRemoved(this, row);
1483         a = m_externalAppointments.take(id);
1484         emit externalAppointmentRemoved();
1485     }
1486     return a;
1487 }
1488 
externalAppointments(const QString & id)1489 AppointmentIntervalList Resource::externalAppointments(const QString &id)
1490 {
1491     if (! m_externalAppointments.contains(id)) {
1492         return AppointmentIntervalList();
1493     }
1494     return m_externalAppointments[ id ]->intervals();
1495 }
1496 
externalAppointments(const DateTimeInterval & interval) const1497 AppointmentIntervalList Resource::externalAppointments(const DateTimeInterval &interval) const
1498 {
1499     //debugPlan<<m_externalAppointments;
1500     Appointment app;
1501     foreach (Appointment *a, m_externalAppointments) {
1502         app += interval.isValid() ? a->extractIntervals(interval) : *a;
1503     }
1504     return app.intervals();
1505 }
1506 
externalProjects() const1507 QMap<QString, QString> Resource::externalProjects() const
1508 {
1509     QMap<QString, QString> map;
1510     for (QMapIterator<QString, Appointment*> it(m_externalAppointments); it.hasNext();) {
1511         it.next();
1512         if (! map.contains(it.key())) {
1513             map[ it.key() ] = it.value()->auxcilliaryInfo();
1514         }
1515     }
1516 //     debugPlan<<map;
1517     return map;
1518 }
1519 
allocationSuitability(const DateTime & time,const Duration & duration,bool backward)1520 long Resource::allocationSuitability(const DateTime &time, const Duration &duration, bool backward)
1521 {
1522     // FIXME: This is not *very* intelligent...
1523     Duration e;
1524     if (m_type == Type_Team) {
1525         foreach (Resource *r, teamMembers()) {
1526             e += r->effort(time, duration, 100, backward);
1527         }
1528     } else {
1529         e = effort(time, duration, 100, backward);
1530     }
1531     return e.minutes();
1532 }
1533 
startTime(long id) const1534 DateTime Resource::startTime(long id) const
1535 {
1536     DateTime dt;
1537     Schedule *s = schedule(id);
1538     if (s == 0) {
1539         return dt;
1540     }
1541     foreach (Appointment *a, s->appointments()) {
1542         DateTime t = a->startTime();
1543         if (! dt.isValid() || t < dt) {
1544             dt = t;
1545         }
1546     }
1547     return dt;
1548 }
1549 
endTime(long id) const1550 DateTime Resource::endTime(long id) const
1551 {
1552     DateTime dt;
1553     Schedule *s = schedule(id);
1554     if (s == 0) {
1555         return dt;
1556     }
1557     foreach (Appointment *a, s->appointments()) {
1558         DateTime t = a->endTime();
1559         if (! dt.isValid() || t > dt) {
1560             dt = t;
1561         }
1562     }
1563     return dt;
1564 }
1565 
teamMembers() const1566 QList<Resource*> Resource::teamMembers() const
1567 {
1568     QList<Resource*> lst;
1569     foreach (const QString &s, m_teamMembers) {
1570         Resource *r = findId(s);
1571         if (r) {
1572             lst << r;
1573         }
1574     }
1575     return lst;
1576 
1577 }
1578 
teamMemberIds() const1579 QStringList Resource::teamMemberIds() const
1580 {
1581     return m_teamMembers;
1582 }
1583 
addTeamMemberId(const QString & id)1584 void Resource::addTeamMemberId(const QString &id)
1585 {
1586     if (! id.isEmpty() && ! m_teamMembers.contains(id)) {
1587         m_teamMembers.append(id);
1588     }
1589 }
1590 
removeTeamMemberId(const QString & id)1591 void Resource::removeTeamMemberId(const QString &id)
1592 {
1593     if (m_teamMembers.contains(id)) {
1594         m_teamMembers.removeAt(m_teamMembers.indexOf(id));
1595     }
1596 }
1597 
setTeamMemberIds(const QStringList & ids)1598 void Resource::setTeamMemberIds(const QStringList &ids)
1599 {
1600     m_teamMembers = ids;
1601 }
1602 
isShared() const1603 bool Resource::isShared() const
1604 {
1605     return m_shared;
1606 }
1607 
setShared(bool on)1608 void Resource::setShared(bool on)
1609 {
1610     m_shared = on;
1611 }
1612 
operator <<(QDebug dbg,const KPlato::Resource::WorkInfoCache & c)1613 QDebug operator<<(QDebug dbg, const KPlato::Resource::WorkInfoCache &c)
1614 {
1615     dbg.nospace()<<"WorkInfoCache: ["<<" version="<<c.version<<" start="<<c.start.toString(Qt::ISODate)<<" end="<<c.end.toString(Qt::ISODate)<<" intervals="<<c.intervals.map().count();
1616     if (! c.intervals.isEmpty()) {
1617         foreach (const AppointmentInterval &i, c.intervals.map()) {
1618         dbg<<endl<<"   "<<i;
1619         }
1620     }
1621     dbg<<"]";
1622     return dbg;
1623 }
1624 
1625 /////////   Risk   /////////
Risk(Node * n,Resource * r,RiskType rt)1626 Risk::Risk(Node *n, Resource *r, RiskType rt) {
1627     m_node=n;
1628     m_resource=r;
1629     m_riskType=rt;
1630 }
1631 
~Risk()1632 Risk::~Risk() {
1633 }
1634 
ResourceRequest(Resource * resource,int units)1635 ResourceRequest::ResourceRequest(Resource *resource, int units)
1636     : m_resource(resource),
1637       m_units(units),
1638       m_parent(0),
1639       m_dynamic(false)
1640 {
1641     if (resource) {
1642         m_required = resource->requiredResources();
1643     }
1644     //debugPlan<<"("<<this<<") Request to:"<<(resource ? resource->name() : QString("None"));
1645 }
1646 
ResourceRequest(const ResourceRequest & r)1647 ResourceRequest::ResourceRequest(const ResourceRequest &r)
1648     : m_resource(r.m_resource),
1649       m_units(r.m_units),
1650       m_parent(0),
1651       m_dynamic(r.m_dynamic),
1652       m_required(r.m_required)
1653 {
1654 }
1655 
~ResourceRequest()1656 ResourceRequest::~ResourceRequest() {
1657     //debugPlan<<"("<<this<<") resource:"<<(m_resource ? m_resource->name() : QString("None"));
1658     if (m_resource)
1659         m_resource->unregisterRequest(this);
1660     m_resource = 0;
1661     qDeleteAll(m_teamMembers);
1662 }
1663 
load(KoXmlElement & element,Project & project)1664 bool ResourceRequest::load(KoXmlElement &element, Project &project) {
1665     //debugPlan;
1666     m_resource = project.resource(element.attribute("resource-id"));
1667     if (m_resource == 0) {
1668         warnPlan<<"The referenced resource does not exist: resource id="<<element.attribute("resource-id");
1669         return false;
1670     }
1671     m_units  = element.attribute("units").toInt();
1672 
1673     KoXmlElement parent = element.namedItem("required-resources").toElement();
1674     KoXmlElement e;
1675     forEachElement(e, parent) {
1676         if (e.nodeName() == "resource") {
1677             QString id = e.attribute("id");
1678             if (id.isEmpty()) {
1679                 errorPlan<<"Missing project id";
1680                 continue;
1681             }
1682             Resource *r = project.resource(id);
1683             if (r == 0) {
1684                 warnPlan<<"The referenced resource does not exist: resource id="<<element.attribute("resource-id");
1685             } else {
1686                 if (r != m_resource) {
1687                     m_required << r;
1688                 }
1689             }
1690         }
1691     }
1692     return true;
1693 }
1694 
save(QDomElement & element) const1695 void ResourceRequest::save(QDomElement &element) const {
1696     QDomElement me = element.ownerDocument().createElement("resource-request");
1697     element.appendChild(me);
1698     me.setAttribute("resource-id", m_resource->id());
1699     me.setAttribute("units", QString::number(m_units));
1700     if (! m_required.isEmpty()) {
1701         QDomElement e = me.ownerDocument().createElement("required-resources");
1702         me.appendChild(e);
1703         foreach (Resource *r, m_required) {
1704             QDomElement el = e.ownerDocument().createElement("resource");
1705             e.appendChild(el);
1706             el.setAttribute("id", r->id());
1707         }
1708     }
1709 }
1710 
units() const1711 int ResourceRequest::units() const {
1712     //debugPlan<<m_resource->name()<<": units="<<m_units;
1713     return m_units;
1714 }
1715 
setUnits(int value)1716 void ResourceRequest::setUnits(int value)
1717 {
1718     m_units = value; changed();
1719 }
1720 
task() const1721 Task *ResourceRequest::task() const {
1722     return m_parent ? m_parent->task() : 0;
1723 }
1724 
changed()1725 void ResourceRequest::changed()
1726 {
1727     if (task()) {
1728         task()->changed(Node::ResourceRequestProperty);
1729     }
1730 }
1731 
setCurrentSchedulePtr(Schedule * ns)1732 void ResourceRequest::setCurrentSchedulePtr(Schedule *ns)
1733 {
1734     setCurrentSchedulePtr(m_resource, ns);
1735 }
1736 
setCurrentSchedulePtr(Resource * resource,Schedule * ns)1737 void ResourceRequest::setCurrentSchedulePtr(Resource *resource, Schedule *ns)
1738 {
1739     resource->setCurrentSchedulePtr(resourceSchedule(ns, resource));
1740     if(resource->type() == Resource::Type_Team) {
1741         foreach (Resource *member, resource->teamMembers()) {
1742             member->setCurrentSchedulePtr(resourceSchedule(ns, member));
1743         }
1744     }
1745     foreach (Resource *r, m_required) {
1746         r->setCurrentSchedulePtr(resourceSchedule(ns, r));
1747     }
1748 }
1749 
resourceSchedule(Schedule * ns,Resource * res)1750 Schedule *ResourceRequest::resourceSchedule(Schedule *ns, Resource *res)
1751 {
1752     if (ns == 0) {
1753         return 0;
1754     }
1755     Resource *r = res == 0 ? resource() : res;
1756     Schedule *s = r->findSchedule(ns->id());
1757     if (s == 0) {
1758         s = r->createSchedule(ns->parent());
1759     }
1760     s->setCalculationMode(ns->calculationMode());
1761     s->setAllowOverbookingState(ns->allowOverbookingState());
1762     static_cast<ResourceSchedule*>(s)->setNodeSchedule(ns);
1763     //debugPlan<<s->name()<<": id="<<s->id()<<" mode="<<s->calculationMode();
1764     return s;
1765 }
1766 
workTimeAfter(const DateTime & dt,Schedule * ns)1767 DateTime ResourceRequest::workTimeAfter(const DateTime &dt, Schedule *ns) {
1768     if (m_resource->type() == Resource::Type_Work) {
1769         DateTime t = availableAfter(dt, ns);
1770         foreach (Resource *r, m_required) {
1771             if (! t.isValid()) {
1772                 break;
1773             }
1774             t = r->availableAfter(t, DateTime(), resourceSchedule(ns, r));
1775         }
1776         return t;
1777     } else if (m_resource->type() == Resource::Type_Team) {
1778         return availableAfter(dt, ns);
1779     }
1780     return DateTime();
1781 }
1782 
workTimeBefore(const DateTime & dt,Schedule * ns)1783 DateTime ResourceRequest::workTimeBefore(const DateTime &dt, Schedule *ns) {
1784     if (m_resource->type() == Resource::Type_Work) {
1785         DateTime t = availableBefore(dt, ns);
1786         foreach (Resource *r, m_required) {
1787             if (! t.isValid()) {
1788                 break;
1789             }
1790             t = r->availableBefore(t, DateTime(), resourceSchedule(ns, r));
1791         }
1792         return t;
1793     } else if (m_resource->type() == Resource::Type_Team) {
1794         return availableBefore(dt, ns);
1795     }
1796     return DateTime();
1797 }
1798 
availableFrom()1799 DateTime ResourceRequest::availableFrom()
1800 {
1801     DateTime dt = m_resource->availableFrom();
1802     if (! dt.isValid()) {
1803         dt = m_resource->project()->constraintStartTime();
1804     }
1805     return dt;
1806 }
1807 
availableUntil()1808 DateTime ResourceRequest::availableUntil()
1809 {
1810     DateTime dt = m_resource->availableUntil();
1811     if (! dt.isValid()) {
1812         dt = m_resource->project()->constraintEndTime();
1813     }
1814     return dt;
1815 }
1816 
availableAfter(const DateTime & time,Schedule * ns)1817 DateTime ResourceRequest::availableAfter(const DateTime &time, Schedule *ns) {
1818     if (m_resource->type() == Resource::Type_Team) {
1819         DateTime t;// = m_resource->availableFrom();
1820         foreach (Resource *r, m_resource->teamMembers()) {
1821             setCurrentSchedulePtr(r, ns);
1822             DateTime x = r->availableAfter(time);
1823             if (x.isValid()) {
1824                 t = t.isValid() ? qMin(t, x) : x;
1825             }
1826         }
1827         return t;
1828     }
1829     setCurrentSchedulePtr(ns);
1830     return m_resource->availableAfter(time);
1831 }
1832 
availableBefore(const DateTime & time,Schedule * ns)1833 DateTime ResourceRequest::availableBefore(const DateTime &time, Schedule *ns) {
1834     if (m_resource->type() == Resource::Type_Team) {
1835         DateTime t;
1836         foreach (Resource *r, m_resource->teamMembers()) {
1837             setCurrentSchedulePtr(r, ns);
1838             DateTime x = r->availableBefore(time);
1839             if (x.isValid()) {
1840                 t = t.isValid() ? qMax(t, x) : x;
1841             }
1842         }
1843         return t;
1844     }
1845     setCurrentSchedulePtr(ns);
1846     return resource()->availableBefore(time);
1847 }
1848 
effort(const DateTime & time,const Duration & duration,Schedule * ns,bool backward)1849 Duration ResourceRequest::effort(const DateTime &time, const Duration &duration, Schedule *ns, bool backward)
1850 {
1851     setCurrentSchedulePtr(ns);
1852     Duration e = m_resource->effort(time, duration, m_units, backward, m_required);
1853     //debugPlan<<m_resource->name()<<time<<duration.toString()<<"delivers:"<<e.toString()<<"request:"<<(m_units/100)<<"parts";
1854     return e;
1855 }
1856 
makeAppointment(Schedule * ns)1857 void ResourceRequest::makeAppointment(Schedule *ns)
1858 {
1859     if (m_resource) {
1860         setCurrentSchedulePtr(ns);
1861         m_resource->makeAppointment(ns, (m_resource->units() * m_units / 100), m_required);
1862     }
1863 }
1864 
makeAppointment(Schedule * ns,int amount)1865 void ResourceRequest::makeAppointment(Schedule *ns, int amount)
1866 {
1867     if (m_resource) {
1868         setCurrentSchedulePtr(ns);
1869         m_resource->makeAppointment(ns, amount, m_required);
1870     }
1871 }
1872 
allocationSuitability(const DateTime & time,const Duration & duration,Schedule * ns,bool backward)1873 long ResourceRequest::allocationSuitability(const DateTime &time, const Duration &duration, Schedule *ns, bool backward)
1874 {
1875     setCurrentSchedulePtr(ns);
1876     return resource()->allocationSuitability(time, duration, backward);
1877 }
1878 
teamMembers() const1879 QList<ResourceRequest*> ResourceRequest::teamMembers() const
1880 {
1881     qDeleteAll(m_teamMembers);
1882     m_teamMembers.clear();
1883     if (m_resource->type() == Resource::Type_Team) {
1884         foreach (Resource *r, m_resource->teamMembers()) {
1885             m_teamMembers << new ResourceRequest(r, m_units);
1886         }
1887     }
1888     return m_teamMembers;
1889 }
1890 
operator <<(QDebug & dbg,const KPlato::ResourceRequest * rr)1891 QDebug &operator<<(QDebug &dbg, const KPlato::ResourceRequest *rr)
1892 {
1893     if (rr) {
1894         dbg<<*rr;
1895     } else {
1896         dbg<<(void*)rr;
1897     }
1898     return dbg;
1899 }
operator <<(QDebug & dbg,const KPlato::ResourceRequest & rr)1900 QDebug &operator<<(QDebug &dbg, const KPlato::ResourceRequest &rr)
1901 {
1902     if (rr.resource()) {
1903         dbg<<"ResourceRequest["<<rr.resource()->name()<<']';
1904     } else {
1905         dbg<<"ResourceRequest[No resource]";
1906     }
1907     return dbg;
1908 }
1909 
1910 /////////
ResourceGroupRequest(ResourceGroup * group,int units)1911 ResourceGroupRequest::ResourceGroupRequest(ResourceGroup *group, int units)
1912     : m_group(group), m_units(units), m_parent(0) {
1913 
1914     //debugPlan<<"Request to:"<<(group ? group->name() : QString("None"));
1915     if (group)
1916         group->registerRequest(this);
1917 }
1918 
ResourceGroupRequest(const ResourceGroupRequest & g)1919 ResourceGroupRequest::ResourceGroupRequest(const ResourceGroupRequest &g)
1920     : m_group(g.m_group), m_units(g.m_units), m_parent(0)
1921 {
1922 }
1923 
~ResourceGroupRequest()1924 ResourceGroupRequest::~ResourceGroupRequest() {
1925     //debugPlan;
1926     if (m_group) {
1927         m_group->unregisterRequest(this);
1928     }
1929     while (!m_resourceRequests.isEmpty()) {
1930         delete m_resourceRequests.takeFirst();
1931     }
1932 }
1933 
addResourceRequest(ResourceRequest * request)1934 void ResourceGroupRequest::addResourceRequest(ResourceRequest *request) {
1935     //debugPlan<<"("<<request<<") to Group:"<<(void*)m_group;
1936     request->setParent(this);
1937     m_resourceRequests.append(request);
1938     request->registerRequest();
1939     changed();
1940 }
1941 
takeResourceRequest(ResourceRequest * request)1942 ResourceRequest *ResourceGroupRequest::takeResourceRequest(ResourceRequest *request) {
1943     if (request)
1944         request->unregisterRequest();
1945     ResourceRequest *r = 0;
1946     int i = m_resourceRequests.indexOf(request);
1947     if (i != -1) {
1948         r = m_resourceRequests.takeAt(i);
1949     }
1950     changed();
1951     return r;
1952 }
1953 
find(const Resource * resource) const1954 ResourceRequest *ResourceGroupRequest::find(const Resource *resource) const {
1955     foreach (ResourceRequest *gr, m_resourceRequests) {
1956         if (gr->resource() == resource) {
1957             return gr;
1958         }
1959     }
1960     return 0;
1961 }
1962 
resourceRequest(const QString & name)1963 ResourceRequest *ResourceGroupRequest::resourceRequest(const QString &name) {
1964     foreach (ResourceRequest *r, m_resourceRequests) {
1965         if (r->resource()->name() == name)
1966             return r;
1967     }
1968     return 0;
1969 }
1970 
requestNameList(bool includeGroup) const1971 QStringList ResourceGroupRequest::requestNameList(bool includeGroup) const {
1972     QStringList lst;
1973     if (includeGroup && m_units > 0 && m_group) {
1974         lst << m_group->name();
1975     }
1976     foreach (ResourceRequest *r, m_resourceRequests) {
1977         if (! r->isDynamicallyAllocated()) {
1978             Q_ASSERT(r->resource());
1979             lst << r->resource()->name();
1980         }
1981     }
1982     return lst;
1983 }
1984 
requestedResources() const1985 QList<Resource*> ResourceGroupRequest::requestedResources() const
1986 {
1987     QList<Resource*> lst;
1988     foreach (ResourceRequest *r, m_resourceRequests) {
1989         if (! r->isDynamicallyAllocated()) {
1990             Q_ASSERT(r->resource());
1991             lst << r->resource();
1992         }
1993     }
1994     return lst;
1995 }
1996 
resourceRequests(bool resolveTeam) const1997 QList<ResourceRequest*> ResourceGroupRequest::resourceRequests(bool resolveTeam) const
1998 {
1999     QList<ResourceRequest*> lst;
2000     foreach (ResourceRequest *rr, m_resourceRequests) {
2001         if (resolveTeam && rr->resource()->type() == Resource::Type_Team) {
2002             lst += rr->teamMembers();
2003         } else {
2004             lst << rr;
2005         }
2006     }
2007     return lst;
2008 }
2009 
load(KoXmlElement & element,XMLLoaderObject & status)2010 bool ResourceGroupRequest::load(KoXmlElement &element, XMLLoaderObject &status) {
2011     //debugPlan;
2012     m_group = status.project().findResourceGroup(element.attribute("group-id"));
2013     if (m_group == 0) {
2014         errorPlan<<"The referenced resource group does not exist: group id="<<element.attribute("group-id");
2015         return false;
2016     }
2017     m_group->registerRequest(this);
2018 
2019     KoXmlNode n = element.firstChild();
2020     for (; ! n.isNull(); n = n.nextSibling()) {
2021         if (! n.isElement()) {
2022             continue;
2023         }
2024         KoXmlElement e = n.toElement();
2025         if (e.tagName() == "resource-request") {
2026             ResourceRequest *r = new ResourceRequest();
2027             if (r->load(e, status.project()))
2028                 addResourceRequest(r);
2029             else {
2030                 errorPlan<<"Failed to load resource request";
2031                 delete r;
2032             }
2033         }
2034     }
2035     // meaning of m_units changed
2036     // Pre 0.6.6 the number *included* all requests, now it is in *addition* to resource requests
2037     m_units  = element.attribute("units").toInt();
2038     if (status.version() < "0.6.6") {
2039         int x = m_units - m_resourceRequests.count();
2040         m_units = x > 0 ? x : 0;
2041     }
2042     return true;
2043 }
2044 
save(QDomElement & element) const2045 void ResourceGroupRequest::save(QDomElement &element) const {
2046     QDomElement me = element.ownerDocument().createElement("resourcegroup-request");
2047     element.appendChild(me);
2048     me.setAttribute("group-id", m_group->id());
2049     me.setAttribute("units", QString::number(m_units));
2050     foreach (ResourceRequest *r, m_resourceRequests)
2051         r->save(me);
2052 }
2053 
units() const2054 int ResourceGroupRequest::units() const {
2055     return m_units;
2056 }
2057 
duration(const DateTime & time,const Duration & _effort,Schedule * ns,bool backward)2058 Duration ResourceGroupRequest::duration(const DateTime &time, const Duration &_effort, Schedule *ns, bool backward) {
2059     Duration dur;
2060     if (m_parent) {
2061         dur = m_parent->duration(m_resourceRequests, time, _effort, ns, backward);
2062     }
2063     return dur;
2064 }
2065 
workTimeAfter(const DateTime & time,Schedule * ns)2066 DateTime ResourceGroupRequest::workTimeAfter(const DateTime &time, Schedule *ns) {
2067     DateTime start;
2068     if (m_resourceRequests.isEmpty()) {
2069         return start;
2070     }
2071     foreach (ResourceRequest *r, m_resourceRequests) {
2072         DateTime t = r->workTimeAfter(time, ns);
2073         if (t.isValid() && (!start.isValid() || t < start))
2074             start = t;
2075     }
2076     if (start.isValid() && start < time)
2077         start = time;
2078     //debugPlan<<time.toString()<<"="<<start.toString();
2079     return start;
2080 }
2081 
workTimeBefore(const DateTime & time,Schedule * ns)2082 DateTime ResourceGroupRequest::workTimeBefore(const DateTime &time, Schedule *ns) {
2083     DateTime end;
2084     if (m_resourceRequests.isEmpty()) {
2085         return end;
2086     }
2087     foreach (ResourceRequest *r, m_resourceRequests) {
2088         DateTime t = r->workTimeBefore(time, ns);
2089         if (t.isValid() && (!end.isValid() ||t > end))
2090             end = t;
2091     }
2092     if (!end.isValid() || end > time)
2093         end = time;
2094     return end;
2095 }
2096 
availableAfter(const DateTime & time,Schedule * ns)2097 DateTime ResourceGroupRequest::availableAfter(const DateTime &time, Schedule *ns) {
2098     DateTime start;
2099     if (m_resourceRequests.isEmpty()) {
2100         return start;
2101     }
2102     foreach (ResourceRequest *r, m_resourceRequests) {
2103         DateTime t = r->availableAfter(time, ns);
2104         if (t.isValid() && (!start.isValid() || t < start))
2105             start = t;
2106     }
2107     if (start.isValid() && start < time)
2108         start = time;
2109     //debugPlan<<time.toString()<<"="<<start.toString()<<""<<m_group->name();
2110     return start;
2111 }
2112 
availableBefore(const DateTime & time,Schedule * ns)2113 DateTime ResourceGroupRequest::availableBefore(const DateTime &time, Schedule *ns) {
2114     DateTime end;
2115     if (m_resourceRequests.isEmpty()) {
2116         return end;
2117     }
2118     foreach (ResourceRequest *r, m_resourceRequests) {
2119         DateTime t = r->availableBefore(time, ns);
2120         if (t.isValid() && (!end.isValid() || t > end))
2121             end = t;
2122     }
2123     if (!end.isValid() || end > time)
2124         end = time;
2125     //debugPlan<<time.toString()<<"="<<end.toString()<<""<<m_group->name();
2126     return end;
2127 }
2128 
makeAppointments(Schedule * schedule)2129 void ResourceGroupRequest::makeAppointments(Schedule *schedule) {
2130     //debugPlan;
2131     foreach (ResourceRequest *r, m_resourceRequests) {
2132         r->makeAppointment(schedule);
2133     }
2134 }
2135 
reserve(const DateTime & start,const Duration & duration)2136 void ResourceGroupRequest::reserve(const DateTime &start, const Duration &duration) {
2137     m_start = start;
2138     m_duration = duration;
2139 }
2140 
isEmpty() const2141 bool ResourceGroupRequest::isEmpty() const {
2142     return m_resourceRequests.isEmpty() && m_units == 0;
2143 }
2144 
task() const2145 Task *ResourceGroupRequest::task() const {
2146     return m_parent ? m_parent->task() : 0;
2147 }
2148 
changed()2149 void ResourceGroupRequest::changed()
2150 {
2151      if (m_parent)
2152          m_parent->changed();
2153 }
2154 
deleteResourceRequest(ResourceRequest * request)2155 void ResourceGroupRequest::deleteResourceRequest(ResourceRequest *request)
2156 {
2157     int i = m_resourceRequests.indexOf(request);
2158     if (i != -1) {
2159         m_resourceRequests.removeAt(i);
2160     }
2161     delete request;
2162     changed();
2163 }
2164 
resetDynamicAllocations()2165 void ResourceGroupRequest::resetDynamicAllocations()
2166 {
2167     QList<ResourceRequest*> lst;
2168     foreach (ResourceRequest *r, m_resourceRequests) {
2169         if (r->isDynamicallyAllocated()) {
2170             lst << r;
2171         }
2172     }
2173     while (! lst.isEmpty()) {
2174         deleteResourceRequest(lst.takeFirst());
2175     }
2176 }
2177 
allocateDynamicRequests(const DateTime & time,const Duration & effort,Schedule * ns,bool backward)2178 void ResourceGroupRequest::allocateDynamicRequests(const DateTime &time, const Duration &effort, Schedule *ns, bool backward)
2179 {
2180     int num = m_units;
2181     if (num <= 0) {
2182         return;
2183     }
2184     if (num == m_group->numResources()) {
2185         // TODO: allocate all
2186     }
2187     Duration e = effort / m_units;
2188     QMap<long, ResourceRequest*> map;
2189     foreach (Resource *r, m_group->resources()) {
2190         if (r->type() == Resource::Type_Team) {
2191             continue;
2192         }
2193         ResourceRequest *rr = find(r);
2194         if (rr) {
2195             if (rr->isDynamicallyAllocated()) {
2196                 --num; // already allocated
2197             }
2198             continue;
2199         }
2200         rr = new ResourceRequest(r, r->units());
2201         long s = rr->allocationSuitability(time, e, ns, backward);
2202         if (s == 0) {
2203             // not suitable at all
2204             delete rr;
2205         } else {
2206             map.insertMulti(s, rr);
2207         }
2208     }
2209     for (--num; num >= 0 && ! map.isEmpty(); --num) {
2210         long key = map.lastKey();
2211         ResourceRequest *r = map.take(key);
2212         r->setAllocatedDynaically(true);
2213         addResourceRequest(r);
2214         debugPlan<<key<<r;
2215     }
2216     qDeleteAll(map); // delete the unused
2217 }
2218 
2219 /////////
ResourceRequestCollection(Task * task)2220 ResourceRequestCollection::ResourceRequestCollection(Task *task)
2221     : m_task(task) {
2222     //debugPlan<<this<<(void*)(&task);
2223 }
2224 
~ResourceRequestCollection()2225 ResourceRequestCollection::~ResourceRequestCollection() {
2226     //debugPlan<<this;
2227     while (!m_requests.empty()) {
2228         delete m_requests.takeFirst();
2229     }
2230 }
2231 
addRequest(ResourceGroupRequest * request)2232 void ResourceRequestCollection::addRequest(ResourceGroupRequest *request)
2233 {
2234     Q_ASSERT(request->group());
2235     foreach (ResourceGroupRequest *r, m_requests) {
2236         if (r->group() == request->group()) {
2237             errorPlan<<"Request to this group already exists";
2238             errorPlan<<"Task:"<<m_task->name()<<"Group:"<<request->group()->name();
2239             Q_ASSERT(false);
2240         }
2241     }
2242     m_requests.append(request);
2243     if (!request->group()->requests().contains(request)) {
2244         request->group()->registerRequest(request);
2245     }
2246     request->setParent(this);
2247     changed();
2248 }
2249 
find(const ResourceGroup * group) const2250 ResourceGroupRequest *ResourceRequestCollection::find(const ResourceGroup *group) const {
2251     foreach (ResourceGroupRequest *r, m_requests) {
2252         if (r->group() == group)
2253             return r; // we assume only one request to the same group
2254     }
2255     return 0;
2256 }
2257 
find(const Resource * resource) const2258 ResourceRequest *ResourceRequestCollection::find(const Resource *resource) const {
2259     ResourceRequest *req = 0;
2260     QListIterator<ResourceGroupRequest*> it(m_requests);
2261     while (req == 0 && it.hasNext()) {
2262         req = it.next()->find(resource);
2263     }
2264     return req;
2265 }
2266 
resourceRequest(const QString & name) const2267 ResourceRequest *ResourceRequestCollection::resourceRequest(const QString &name) const {
2268     ResourceRequest *req = 0;
2269     QListIterator<ResourceGroupRequest*> it(m_requests);
2270     while (req == 0 && it.hasNext()) {
2271         req = it.next()->resourceRequest(name);
2272     }
2273     return req;
2274 }
2275 
requestNameList(bool includeGroup) const2276 QStringList ResourceRequestCollection::requestNameList(bool includeGroup) const {
2277     QStringList lst;
2278     foreach (ResourceGroupRequest *r, m_requests) {
2279         lst << r->requestNameList(includeGroup);
2280     }
2281     return lst;
2282 }
2283 
requestedResources() const2284 QList<Resource*> ResourceRequestCollection::requestedResources() const {
2285     QList<Resource*> lst;
2286     foreach (ResourceGroupRequest *g, m_requests) {
2287         lst += g->requestedResources();
2288     }
2289     return lst;
2290 }
2291 
resourceRequests(bool resolveTeam) const2292 QList<ResourceRequest*> ResourceRequestCollection::resourceRequests(bool resolveTeam) const {
2293     QList<ResourceRequest*> lst;
2294     foreach (ResourceGroupRequest *g, m_requests) {
2295         foreach (ResourceRequest *r, g->resourceRequests(resolveTeam)) {
2296             lst << r;
2297         }
2298     }
2299     return lst;
2300 }
2301 
2302 
contains(const QString & identity) const2303 bool ResourceRequestCollection::contains(const QString &identity) const {
2304     QStringList lst = requestNameList();
2305     return lst.indexOf(QRegExp(identity, Qt::CaseSensitive, QRegExp::FixedString)) != -1;
2306 }
2307 
findGroupRequestById(const QString & id) const2308 ResourceGroupRequest *ResourceRequestCollection::findGroupRequestById(const QString &id) const
2309 {
2310     foreach (ResourceGroupRequest *r, m_requests) {
2311         if (r->group()->id() == id) {
2312             return r;
2313         }
2314     }
2315     return 0;
2316 }
2317 
2318 // bool ResourceRequestCollection::load(KoXmlElement &element, Project &project) {
2319 //     //debugPlan;
2320 //     return true;
2321 // }
2322 
save(QDomElement & element) const2323 void ResourceRequestCollection::save(QDomElement &element) const {
2324     //debugPlan;
2325     foreach (ResourceGroupRequest *r, m_requests) {
2326         r->save(element);
2327     }
2328 }
2329 
2330 // Returns the duration needed by the working resources
2331 // "Material type" of resourcegroups does not (atm) affect the duration.
duration(const DateTime & time,const Duration & effort,Schedule * ns,bool backward)2332 Duration ResourceRequestCollection::duration(const DateTime &time, const Duration &effort, Schedule *ns, bool backward) {
2333     //debugPlan<<"time="<<time.toString()<<" effort="<<effort.toString(Duration::Format_Day)<<" backward="<<backward;
2334     if (isEmpty()) {
2335         return effort;
2336     }
2337     Duration dur = effort;
2338     QList<ResourceRequest*> lst;
2339     foreach (ResourceGroupRequest *r, m_requests) {
2340         r->allocateDynamicRequests(time, effort, ns, backward);
2341         if (r->group()->type() == ResourceGroup::Type_Work) {
2342             lst << r->resourceRequests();
2343         } else if (r->group()->type() == ResourceGroup::Type_Material) {
2344             //TODO
2345         }
2346     }
2347     if (! lst.isEmpty()) {
2348         dur = duration(lst, time, effort, ns, backward);
2349     }
2350     return dur;
2351 }
2352 
workTimeAfter(const DateTime & time,Schedule * ns) const2353 DateTime ResourceRequestCollection::workTimeAfter(const DateTime &time, Schedule *ns) const {
2354     DateTime start;
2355     foreach (ResourceGroupRequest *r, m_requests) {
2356         DateTime t = r->workTimeAfter(time, ns);
2357         if (t.isValid() && (!start.isValid() || t < start))
2358             start = t;
2359     }
2360     if (start.isValid() && start < time)
2361         start = time;
2362     //debugPlan<<time.toString()<<"="<<start.toString();
2363     return start;
2364 }
2365 
workTimeBefore(const DateTime & time,Schedule * ns) const2366 DateTime ResourceRequestCollection::workTimeBefore(const DateTime &time, Schedule *ns) const {
2367     DateTime end;
2368     foreach (ResourceGroupRequest *r, m_requests) {
2369         DateTime t = r->workTimeBefore(time, ns);
2370         if (t.isValid() && (!end.isValid() ||t > end))
2371             end = t;
2372     }
2373     if (!end.isValid() || end > time)
2374         end = time;
2375     return end;
2376 }
2377 
availableAfter(const DateTime & time,Schedule * ns)2378 DateTime ResourceRequestCollection::availableAfter(const DateTime &time, Schedule *ns) {
2379     DateTime start;
2380     foreach (ResourceGroupRequest *r, m_requests) {
2381         DateTime t = r->availableAfter(time, ns);
2382         if (t.isValid() && (!start.isValid() || t < start))
2383             start = t;
2384     }
2385     if (start.isValid() && start < time)
2386         start = time;
2387     //debugPlan<<time.toString()<<"="<<start.toString();
2388     return start;
2389 }
2390 
availableBefore(const DateTime & time,Schedule * ns)2391 DateTime ResourceRequestCollection::availableBefore(const DateTime &time, Schedule *ns) {
2392     DateTime end;
2393     foreach (ResourceGroupRequest *r, m_requests) {
2394         DateTime t = r->availableBefore(time, ns);
2395         if (t.isValid() && (!end.isValid() ||t > end))
2396             end = t;
2397     }
2398     if (!end.isValid() || end > time)
2399         end = time;
2400     return end;
2401 }
2402 
workStartAfter(const DateTime & time,Schedule * ns)2403 DateTime ResourceRequestCollection::workStartAfter(const DateTime &time, Schedule *ns) {
2404     DateTime start;
2405     foreach (ResourceGroupRequest *r, m_requests) {
2406         if (r->group()->type() != ResourceGroup::Type_Work) {
2407             continue;
2408         }
2409         DateTime t = r->availableAfter(time, ns);
2410         if (t.isValid() && (!start.isValid() || t < start))
2411             start = t;
2412     }
2413     if (start.isValid() && start < time)
2414         start = time;
2415     //debugPlan<<time.toString()<<"="<<start.toString();
2416     return start;
2417 }
2418 
workFinishBefore(const DateTime & time,Schedule * ns)2419 DateTime ResourceRequestCollection::workFinishBefore(const DateTime &time, Schedule *ns) {
2420     DateTime end;
2421     foreach (ResourceGroupRequest *r, m_requests) {
2422         if (r->group()->type() != ResourceGroup::Type_Work) {
2423             continue;
2424         }
2425         DateTime t = r->availableBefore(time, ns);
2426         if (t.isValid() && (!end.isValid() ||t > end))
2427             end = t;
2428     }
2429     if (!end.isValid() || end > time)
2430         end = time;
2431     return end;
2432 }
2433 
2434 
makeAppointments(Schedule * schedule)2435 void ResourceRequestCollection::makeAppointments(Schedule *schedule) {
2436     //debugPlan;
2437     foreach (ResourceGroupRequest *r, m_requests) {
2438         r->makeAppointments(schedule);
2439     }
2440 }
2441 
reserve(const DateTime & start,const Duration & duration)2442 void ResourceRequestCollection::reserve(const DateTime &start, const Duration &duration) {
2443     //debugPlan;
2444     foreach (ResourceGroupRequest *r, m_requests) {
2445         r->reserve(start, duration);
2446     }
2447 }
2448 
isEmpty() const2449 bool ResourceRequestCollection::isEmpty() const {
2450     foreach (ResourceGroupRequest *r, m_requests) {
2451         if (!r->isEmpty())
2452             return false;
2453     }
2454     return true;
2455 }
2456 
changed()2457 void ResourceRequestCollection::changed()
2458 {
2459     //debugPlan<<m_task;
2460     if (m_task) {
2461         m_task->changed(Node::ResourceRequestProperty);
2462     }
2463 }
2464 
resetDynamicAllocations()2465 void ResourceRequestCollection::resetDynamicAllocations()
2466 {
2467     foreach (ResourceGroupRequest *g, m_requests) {
2468         g->resetDynamicAllocations();
2469     }
2470 }
2471 
effort(const QList<ResourceRequest * > & lst,const DateTime & time,const Duration & duration,Schedule * ns,bool backward) const2472 Duration ResourceRequestCollection::effort(const QList<ResourceRequest*> &lst, const DateTime &time, const Duration &duration, Schedule *ns, bool backward) const {
2473     Duration e;
2474     foreach (ResourceRequest *r, lst) {
2475         e += r->effort(time, duration, ns, backward);
2476         //debugPlan<<(backward?"(B)":"(F)")<<r<<": time="<<time<<" dur="<<duration.toString()<<"gave e="<<e.toString();
2477     }
2478     //debugPlan<<time.toString()<<"d="<<duration.toString()<<": e="<<e.toString();
2479     return e;
2480 }
2481 
numDays(const QList<ResourceRequest * > & lst,const DateTime & time,bool backward) const2482 int ResourceRequestCollection::numDays(const QList<ResourceRequest*> &lst, const DateTime &time, bool backward) const {
2483     DateTime t1, t2 = time;
2484     if (backward) {
2485         foreach (ResourceRequest *r, lst) {
2486             t1 = r->availableFrom();
2487             if (!t2.isValid() || t2 > t1)
2488                 t2 = t1;
2489         }
2490         //debugPlan<<"bw"<<time.toString()<<":"<<t2.daysTo(time);
2491         return t2.daysTo(time);
2492     }
2493     foreach (ResourceRequest *r, lst) {
2494         t1 = r->availableUntil();
2495         if (!t2.isValid() || t2 < t1)
2496             t2 = t1;
2497     }
2498     //debugPlan<<"fw"<<time.toString()<<":"<<time.daysTo(t2);
2499     return time.daysTo(t2);
2500 }
2501 
duration(const QList<ResourceRequest * > & lst,const DateTime & time,const Duration & _effort,Schedule * ns,bool backward)2502 Duration ResourceRequestCollection::duration(const QList<ResourceRequest*> &lst, const DateTime &time, const Duration &_effort, Schedule *ns, bool backward) {
2503     //debugPlan<<"--->"<<(backward?"(B)":"(F)")<<time.toString()<<": effort:"<<_effort.toString(Duration::Format_Day)<<" ("<<_effort.milliseconds()<<")";
2504 #if 0
2505     if (ns) {
2506         QStringList nl;
2507         foreach (ResourceRequest *r, lst) { nl << r->resource()->name(); }
2508         ns->logDebug("Match effort:" + time.toString() + "," + _effort.toString());
2509         ns->logDebug("Resources: " + (nl.isEmpty() ? QString("None") : nl.join(", ")));
2510     }
2511 #endif
2512     QLocale locale;
2513     Duration e;
2514     if (_effort == Duration::zeroDuration) {
2515         return e;
2516     }
2517     DateTime logtime = time;
2518     bool match = false;
2519     DateTime start = time;
2520     int inc = backward ? -1 : 1;
2521     DateTime end = start;
2522     Duration e1;
2523     int nDays = numDays(lst, time, backward) + 1;
2524     int day = 0;
2525     for (day=0; !match && day <= nDays; ++day) {
2526         // days
2527         end = end.addDays(inc);
2528         e1 = effort(lst, start, backward ? start - end : end - start, ns, backward);
2529         //debugPlan<<"["<<i<<"of"<<nDays<<"]"<<(backward?"(B)":"(F):")<<"  start="<<start<<" e+e1="<<(e+e1).toString()<<" match"<<_effort.toString();
2530         if (e + e1 < _effort) {
2531             e += e1;
2532             start = end;
2533         } else if (e + e1 == _effort) {
2534             e += e1;
2535             match = true;
2536         } else {
2537             end = start;
2538             break;
2539         }
2540     }
2541     if (! match && day <= nDays) {
2542 #ifndef PLAN_NLOGDEBUG
2543         if (ns) ns->logDebug("Days: duration " + logtime.toString() + " - " + end.toString() + " e=" + e.toString() + " (" + (_effort - e).toString() + ')');
2544 #endif
2545         logtime = start;
2546         for (int i=0; !match && i < 24; ++i) {
2547             // hours
2548             end = end.addSecs(inc*60*60);
2549             e1 = effort(lst, start, backward ? start - end : end - start, ns, backward);
2550             if (e + e1 < _effort) {
2551                 e += e1;
2552                 start = end;
2553             } else if (e + e1 == _effort) {
2554                 e += e1;
2555                 match = true;
2556             } else {
2557                 if (false/*roundToHour*/ && (_effort - e) <  (e + e1 - _effort)) {
2558                     end = start;
2559                     match = true;
2560                 } else {
2561                     end = start;
2562                 }
2563                 break;
2564             }
2565             //debugPlan<<"duration(h)["<<i<<"]"<<(backward?"backward":"forward:")<<" time="<<start.time()<<" e="<<e.toString()<<" ("<<e.milliseconds()<<")";
2566         }
2567         //debugPlan<<"duration"<<(backward?"backward":"forward:")<<start.toString()<<" e="<<e.toString()<<" ("<<e.milliseconds()<<")  match="<<match<<" sts="<<sts;
2568     }
2569     if (! match && day <= nDays) {
2570 #ifndef PLAN_NLOGDEBUG
2571         if (ns) ns->logDebug("Hours: duration " + logtime.toString() + " - " + end.toString() + " e=" + e.toString() + " (" + (_effort - e).toString() + ')');
2572 #endif
2573         logtime = start;
2574         for (int i=0; !match && i < 60; ++i) {
2575             //minutes
2576             end = end.addSecs(inc*60);
2577             e1 = effort(lst, start, backward ? start - end : end - start, ns, backward);
2578             if (e + e1 < _effort) {
2579                 e += e1;
2580                 start = end;
2581             } else if (e + e1 == _effort) {
2582                 e += e1;
2583                 match = true;
2584             } else if (e + e1 > _effort) {
2585                 end = start;
2586                 break;
2587             }
2588             //debugPlan<<"duration(m)"<<(backward?"backward":"forward:")<<"  time="<<start.time().toString()<<" e="<<e.toString()<<" ("<<e.milliseconds()<<")";
2589         }
2590         //debugPlan<<"duration"<<(backward?"backward":"forward:")<<"  start="<<start.toString()<<" e="<<e.toString()<<" match="<<match<<" sts="<<sts;
2591     }
2592     // FIXME: better solution
2593     // If effort to match is reasonably large, accept a match if deviation <= 1 min
2594     if (! match && _effort > 5 * 60000) {
2595         if ((_effort - e) <= 60000){
2596             match = true;
2597 #ifndef PLAN_NLOGDEBUG
2598             if (ns) ns->logDebug("Deviation match:" + logtime.toString() + " - " + end.toString() + " e=" + e.toString() + " (" + (_effort - e).toString() + ')');
2599 #endif
2600         }
2601     }
2602     if (! match && day <= nDays) {
2603 #ifndef PLAN_NLOGDEBUG
2604         if (ns) ns->logDebug("Minutes: duration " + logtime.toString() + " - " + end.toString() + " e=" + e.toString() + " (" + (_effort - e).toString() + ')');
2605 #endif
2606         logtime = start;
2607         for (int i=0; !match && i < 60; ++i) {
2608             //seconds
2609             end = end.addSecs(inc);
2610             e1 = effort(lst, start, backward ? start - end : end - start, ns, backward);
2611             if (e + e1 < _effort) {
2612                 e += e1;
2613                 start = end;
2614             } else if (e + e1 == _effort) {
2615                 e += e1;
2616                 match = true;
2617             } else if (e + e1 > _effort) {
2618                 end = start;
2619                 break;
2620             }
2621             //debugPlan<<"duration(s)["<<i<<"]"<<(backward?"backward":"forward:")<<" time="<<start.time().toString()<<" e="<<e.toString()<<" ("<<e.milliseconds()<<")";
2622         }
2623     }
2624     if (! match && day <= nDays) {
2625 #ifndef PLAN_NLOGDEBUG
2626         if (ns) ns->logDebug("Seconds: duration " + logtime.toString() + " - " + end.toString() + " e=" + e.toString() + " (" + (_effort - e).toString() + ')');
2627 #endif
2628         for (int i=0; !match && i < 1000; ++i) {
2629             //milliseconds
2630             end.setTime(end.time().addMSecs(inc));
2631             e1 = effort(lst, start, backward ? start - end : end - start, ns, backward);
2632             if (e + e1 < _effort) {
2633                 e += e1;
2634                 start = end;
2635             } else if (e + e1 == _effort) {
2636                 e += e1;
2637                 match = true;
2638             } else if (e + e1 > _effort) {
2639                 break;
2640             }
2641             //debugPlan<<"duration(ms)["<<i<<"]"<<(backward?"backward":"forward:")<<" time="<<start.time().toString()<<" e="<<e.toString()<<" ("<<e.milliseconds()<<")";
2642         }
2643     }
2644     if (!match && ns) {
2645         ns->logError(i18n("Could not match effort. Want: %1 got: %2", _effort.toString(Duration::Format_Hour), e.toString(Duration::Format_Hour)));
2646         foreach (ResourceRequest *r, lst) {
2647             Resource *res = r->resource();
2648             ns->logInfo(i18n("Resource %1 available from %2 to %3", res->name(), locale.toString(r->availableFrom(), QLocale::ShortFormat), locale.toString(r->availableUntil(), QLocale::ShortFormat)));
2649         }
2650 
2651     }
2652     DateTime t;
2653     if (e != Duration::zeroDuration) {
2654         foreach (ResourceRequest *r, lst) {
2655             DateTime tt;
2656             if (backward) {
2657                 tt = r->availableAfter(end, ns);
2658                 if (tt.isValid() && (! t.isValid() || tt < t)) {
2659                     t = tt;
2660                 }
2661             } else {
2662                 tt = r->availableBefore(end, ns);
2663                 if (tt.isValid() && (! t.isValid() || tt > t)) {
2664                     t = tt;
2665                 }
2666             }
2667         }
2668     }
2669     end = t.isValid() ? t : time;
2670     //debugPlan<<"<---"<<(backward?"(B)":"(F)")<<":"<<end.toString()<<"-"<<time.toString()<<"="<<(end - time).toString()<<" effort:"<<_effort.toString(Duration::Format_Day);
2671     return (end>time?end-time:time-end);
2672 }
2673 
2674 
2675 }  //KPlato namespace
2676 
operator <<(QDebug dbg,KPlato::Resource * r)2677 QDebug operator<<(QDebug dbg, KPlato::Resource *r)
2678 {
2679     if (!r) { return dbg << "Resource[0x0]"; }
2680     dbg << "Resource[" << r->type();
2681     dbg << (r->name().isEmpty() ? r->id() : r->name());
2682     dbg << ']';
2683     return dbg;
2684 }
2685