1 /* This file is part of the KDE project
2    Copyright (C) 2001 Thomas zander <zander@kde.org>
3    Copyright (C) 2004 - 2007 Dag Andersen <danders@get2net.dk>
4    Copyright (C) 2007 Florian Piquemal <flotueur@yahoo.fr>
5    Copyright (C) 2007 Alexis Ménard <darktears31@gmail.com>
6 
7    This library is free software; you can redistribute it and/or
8    modify it under the terms of the GNU Library General Public
9    License as published by the Free Software Foundation; either
10    version 2 of the License, or (at your option) any later version.
11 
12    This library is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15    Library General Public License for more details.
16 
17    You should have received a copy of the GNU Library General Public License
18    along with this library; see the file COPYING.LIB.  If not, write to
19    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21 */
22 
23 // clazy:excludeall=qstring-arg
24 #include "kpttask.h"
25 #include "kptappointment.h"
26 #include "kptproject.h"
27 #include "kptduration.h"
28 #include "kptrelation.h"
29 #include "kptdatetime.h"
30 #include "kptcalendar.h"
31 #include "kpteffortcostmap.h"
32 #include "kptschedule.h"
33 #include "kptxmlloaderobject.h"
34 #include "XmlSaveContext.h"
35 #include <kptdebug.h>
36 
37 #include <KoXmlReader.h>
38 
39 #include <KLocalizedString>
40 
41 
42 namespace KPlato
43 {
44 
Task(Node * parent)45 Task::Task(Node *parent)
46     : Node(parent),
47       m_resource(),
48       m_workPackage(this)
49 {
50     //debugPlan<<"("<<this<<')';
51     m_requests.setTask(this);
52     Duration d(1, 0, 0);
53     m_estimate = new Estimate();
54     m_estimate->setOptimisticRatio(-10);
55     m_estimate->setPessimisticRatio(20);
56     m_estimate->setParentNode(this);
57 
58     if (m_parent)
59         m_leader = m_parent->leader();
60 }
61 
Task(const Task & task,Node * parent)62 Task::Task(const Task &task, Node *parent)
63     : Node(task, parent),
64       m_resource(),
65       m_workPackage(this)
66 {
67     //debugPlan<<"("<<this<<')';
68     m_requests.setTask(this);
69     delete m_estimate;
70     if (task.estimate()) {
71         m_estimate = new Estimate(*(task.estimate()));
72     } else {
73         m_estimate = new Estimate();
74     }
75     m_estimate->setParentNode(this);
76 }
77 
78 
~Task()79 Task::~Task() {
80     while (!m_resource.isEmpty()) {
81         delete m_resource.takeFirst();
82     }
83     while (!m_parentProxyRelations.isEmpty()) {
84         delete m_parentProxyRelations.takeFirst();
85     }
86     while (!m_childProxyRelations.isEmpty()) {
87         delete m_childProxyRelations.takeFirst();
88     }
89 }
90 
type() const91 int Task::type() const {
92     if (numChildren() > 0) {
93         return Node::Type_Summarytask;
94     } else if (m_constraint == Node::FixedInterval) {
95         if (m_constraintEndTime == m_constraintStartTime) {
96             return Node::Type_Milestone;
97         }
98     } else if (m_estimate->expectedEstimate() == 0.0) {
99         return Node::Type_Milestone;
100     }
101     return Node::Type_Task;
102 }
103 
getRandomDuration()104 Duration *Task::getRandomDuration() {
105     return 0L;
106 }
107 
resourceGroupRequest(const ResourceGroup * group) const108 ResourceGroupRequest *Task::resourceGroupRequest(const ResourceGroup *group) const {
109     return m_requests.find(group);
110 }
111 
clearResourceRequests()112 void Task::clearResourceRequests() {
113     m_requests.clear();
114     changed(this, ResourceRequestProperty);
115 }
116 
addRequest(ResourceGroup * group,int numResources)117 void Task::addRequest(ResourceGroup *group, int numResources) {
118     addRequest(new ResourceGroupRequest(group, numResources));
119 }
120 
addRequest(ResourceGroupRequest * request)121 void Task::addRequest(ResourceGroupRequest *request) {
122     //debugPlan<<m_name<<request<<request->group()<<request->group()->id()<<request->group()->name();
123     m_requests.addRequest(request);
124     changed(this, Node::ResourceRequestProperty);
125 }
126 
takeRequest(ResourceGroupRequest * request)127 void Task::takeRequest(ResourceGroupRequest *request) {
128     //debugPlan<<request;
129     m_requests.takeRequest(request);
130     changed(this, Node::ResourceRequestProperty);
131 }
132 
requestNameList() const133 QStringList Task::requestNameList() const {
134     return m_requests.requestNameList();
135 }
136 
requestedResources() const137 QList<Resource*> Task::requestedResources() const {
138     return m_requests.requestedResources();
139 }
140 
containsRequest(const QString & identity) const141 bool Task::containsRequest(const QString &identity) const {
142     return m_requests.contains(identity);
143 }
144 
resourceRequest(const QString & name) const145 ResourceRequest *Task::resourceRequest(const QString &name) const {
146     return m_requests.resourceRequest(name);
147 }
148 
assignedNameList(long id) const149 QStringList Task::assignedNameList(long id) const {
150     Schedule *s = schedule(id);
151     if (s == 0) {
152         return QStringList();
153     }
154     return s->resourceNameList();
155 }
156 
makeAppointments()157 void Task::makeAppointments() {
158     if (m_currentSchedule == 0)
159         return;
160     if (type() == Node::Type_Task) {
161         //debugPlan<<m_name<<":"<<m_currentSchedule->startTime<<","<<m_currentSchedule->endTime<<";"<<m_currentSchedule->duration.toString();
162         m_requests.makeAppointments(m_currentSchedule);
163         //debugPlan<<m_name<<":"<<m_currentSchedule->startTime<<","<<m_currentSchedule->endTime<<";"<<m_currentSchedule->duration.toString();
164     } else if (type() == Node::Type_Summarytask) {
165         foreach (Node *n, m_nodes) {
166             n->makeAppointments();
167         }
168     } else if (type() == Node::Type_Milestone) {
169         //debugPlan<<"Milestone not implemented";
170         // Well, shouldn't have resources anyway...
171     }
172 }
173 
copySchedule()174 void Task::copySchedule()
175 {
176     if (m_currentSchedule == 0 || type() != Node::Type_Task) {
177         return;
178     }
179     int id = m_currentSchedule->parentScheduleId();
180     NodeSchedule *ns = static_cast<NodeSchedule*>(findSchedule(id));
181     if (ns == 0) {
182         return;
183     }
184     if (type() == Node::Type_Task) {
185         copyAppointments(ns->startTime, ns->endTime);
186     }
187     m_currentSchedule->startTime = ns->startTime;
188     m_currentSchedule->earlyStart = ns->earlyStart;
189     m_currentSchedule->endTime = ns->endTime;
190     m_currentSchedule->lateFinish = ns->lateFinish;
191     m_currentSchedule->duration = ns->duration;
192     // TODO: status flags, etc
193     //debugPlan;
194 }
195 
copyAppointments()196 void Task::copyAppointments()
197 {
198     if (!isStarted() || completion().isFinished()) {
199         return;
200     }
201     int id = m_currentSchedule->parentScheduleId();
202     NodeSchedule *ns = static_cast<NodeSchedule*>(findSchedule(id));
203     if (ns == nullptr) {
204         return;
205     }
206     DateTime time = m_currentSchedule->recalculateFrom();
207     qreal plannedEffort = ns->plannedEffortTo(time).toDouble();
208     if (plannedEffort == 0.0) {
209         return; // nothing to do, should not happen but...
210     }
211     // Problem is that actual effort is pr day so try to be smart...
212     QDate date = time.time() == QTime() ? time.date() : time.date().addDays(1); // hmmm, review
213     qreal actualEffort = actualEffortTo(date).toDouble();
214     qreal scale = actualEffort / plannedEffort;
215     if (scale == 0.0) {
216         // can happen if task is started but nobody has done any work yet
217         scale = 1.0;
218     }
219     // Copy and adjust for performance to get reasonable bcws/bcwp/acwp values.
220     // This means the historical *planned* values will not match parent schedule,
221     // but then this *is* a new schedule, so more important to get totals etc to work.
222     copyAppointments(ns->startTime, time, scale);
223 }
224 
copyAppointments(const DateTime & start,const DateTime & end,qreal factor)225 void Task::copyAppointments(const DateTime &start, const DateTime &end, qreal factor)
226 {
227     if (m_currentSchedule == 0 || type() != Node::Type_Task) {
228         return;
229     }
230     int id = m_currentSchedule->parentScheduleId();
231     NodeSchedule *ns = static_cast<NodeSchedule*>(findSchedule(id));
232     if (ns == 0) {
233         return;
234     }
235     DateTime st = start.isValid() ? start : ns->startTime;
236     DateTime et = end.isValid() ? end : ns->endTime;
237     //debugPlan<<m_name<<st.toString()<<et.toString()<<m_currentSchedule->calculationMode();
238     foreach (const Appointment *a, ns->appointments()) {
239         Resource *r = a->resource() == 0 ? 0 : a->resource()->resource();
240         if (r == 0) {
241             errorPlan<<"No resource";
242             continue;
243         }
244         AppointmentIntervalList lst;
245         for (AppointmentInterval i : a->intervals(st, et).map()) {
246             i.setLoad(i.load() * factor);
247             lst.add(i);
248         }
249         if (lst.isEmpty()) {
250             //debugPlan<<"No intervals to copy from"<<a;
251             continue;
252         }
253         Appointment *curr = 0;
254         foreach (Appointment *c, m_currentSchedule->appointments()) {
255             if (c->resource()->resource() == r) {
256                 //debugPlan<<"Found current appointment to"<<a->resource()->resource()->name()<<c;
257                 curr = c;
258                 break;
259             }
260         }
261         if (curr == 0) {
262             curr = new Appointment();
263             m_currentSchedule->add(curr);
264             curr->setNode(m_currentSchedule);
265             //debugPlan<<"Created new appointment"<<curr;
266         }
267         ResourceSchedule *rs = static_cast<ResourceSchedule*>(r->findSchedule(m_currentSchedule->id()));
268         if (rs == 0) {
269             rs = r->createSchedule(m_currentSchedule->parent());
270             rs->setId(m_currentSchedule->id());
271             rs->setName(m_currentSchedule->name());
272             rs->setType(m_currentSchedule->type());
273             //debugPlan<<"Resource schedule not found, id="<<m_currentSchedule->id();
274         }
275         rs->setCalculationMode(m_currentSchedule->calculationMode());
276         if (! rs->appointments().contains(curr)) {
277             //debugPlan<<"add to resource"<<rs<<curr;
278             rs->add(curr);
279             curr->setResource(rs);
280         }
281         Appointment app;
282         app.setIntervals(lst);
283         //foreach (AppointmentInterval *i, curr->intervals()) { debugPlan<<i->startTime().toString()<<i->endTime().toString(); }
284         curr->merge(app);
285         //debugPlan<<"Appointments added";
286     }
287     m_currentSchedule->startTime = ns->startTime;
288     m_currentSchedule->earlyStart = ns->earlyStart;
289 }
290 
calcResourceOverbooked()291 void Task::calcResourceOverbooked() {
292     if (m_currentSchedule)
293         m_currentSchedule->calcResourceOverbooked();
294 }
295 
load(KoXmlElement & element,XMLLoaderObject & status)296 bool Task::load(KoXmlElement &element, XMLLoaderObject &status) {
297     QString s;
298     bool ok = false;
299     m_id = element.attribute(QStringLiteral("id"));
300     m_priority = element.attribute(QStringLiteral("priority"), "0").toInt();
301 
302     setName(element.attribute(QStringLiteral("name")));
303     m_leader = element.attribute(QStringLiteral("leader"));
304     m_description = element.attribute(QStringLiteral("description"));
305     //debugPlan<<m_name<<": id="<<m_id;
306 
307     // Allow for both numeric and text
308     QString constraint = element.attribute(QStringLiteral("scheduling"),QStringLiteral("0"));
309     m_constraint = (Node::ConstraintType)constraint.toInt(&ok);
310     if (!ok)
311         Node::setConstraint(constraint); // hmmm, why do I need Node::?
312 
313     s = element.attribute(QStringLiteral("constraint-starttime"));
314     if (!s.isEmpty())
315         m_constraintStartTime = DateTime::fromString(s, status.projectTimeZone());
316     s = element.attribute(QStringLiteral("constraint-endtime"));
317     if (!s.isEmpty())
318         m_constraintEndTime = DateTime::fromString(s, status.projectTimeZone());
319 
320     m_startupCost = element.attribute(QStringLiteral("startup-cost"), QStringLiteral("0.0")).toDouble();
321     m_shutdownCost = element.attribute(QStringLiteral("shutdown-cost"), QStringLiteral("0.0")).toDouble();
322 
323     // Load the task children
324     KoXmlNode n = element.firstChild();
325     for (; ! n.isNull(); n = n.nextSibling()) {
326         if (! n.isElement()) {
327             continue;
328         }
329         KoXmlElement e = n.toElement();
330         if (e.tagName() == QLatin1String("project")) {
331             // Load the subproject
332 /*                Project *child = new Project(this, status);
333             if (child->load(e)) {
334                 if (!project.addSubTask(child, this)) {
335                     delete child;  // TODO: Complain about this
336                 }
337             } else {
338                 // TODO: Complain about this
339                 delete child;
340             }*/
341         } else if (e.tagName() == QLatin1String("task")) {
342             if (status.loadTaskChildren()) {
343                 // Load the task
344                 Task *child = new Task(this);
345                 if (child->load(e, status)) {
346                     if (!status.project().addSubTask(child, this)) {
347                         delete child;  // TODO: Complain about this
348                     }
349                 } else {
350                     // TODO: Complain about this
351                     delete child;
352                 }
353             }
354         } else if (e.tagName() == QLatin1String("resource")) {
355             // TODO: Load the resource (projects don't have resources yet)
356         } else if (e.tagName() == QLatin1String("estimate") ||
357                    (/*status.version() < "0.6" &&*/ e.tagName() == QLatin1String("effort"))) {
358             //  Load the estimate
359             m_estimate->load(e, status);
360         } else if (e.tagName() == QLatin1String("resourcegroup-request")) {
361             // Load the resource request
362             // Handle multiple requests to same group gracefully (Not really allowed)
363             ResourceGroupRequest *r = m_requests.findGroupRequestById(e.attribute(QStringLiteral("group-id")));
364             if (r) {
365                 warnPlan<<"Multiple requests to same group, loading into existing group";
366                 if (! r->load(e, status)) {
367                     errorPlan<<"Failed to load resource request";
368                 }
369             } else {
370                 r = new ResourceGroupRequest();
371                 if (r->load(e, status)) {
372                     addRequest(r);
373                 } else {
374                     errorPlan<<"Failed to load resource request";
375                     delete r;
376                 }
377             }
378         } else if (e.tagName() == QLatin1String("workpackage")) {
379             m_workPackage.loadXML(e, status);
380         } else if (e.tagName() == QLatin1String("progress")) {
381             completion().loadXML(e, status);
382         } else if (e.tagName() == QLatin1String("schedules")) {
383             KoXmlNode n = e.firstChild();
384             for (; ! n.isNull(); n = n.nextSibling()) {
385                 if (! n.isElement()) {
386                     continue;
387                 }
388                 KoXmlElement el = n.toElement();
389                 if (el.tagName() == QLatin1String("schedule")) {
390                     NodeSchedule *sch = new NodeSchedule();
391                     if (sch->loadXML(el, status)) {
392                         sch->setNode(this);
393                         addSchedule(sch);
394                     } else {
395                         errorPlan<<"Failed to load schedule";
396                         delete sch;
397                     }
398                 }
399             }
400         } else if (e.tagName() == QLatin1String("documents")) {
401             m_documents.load(e, status);
402         } else if (e.tagName() == QLatin1String("workpackage-log")) {
403             KoXmlNode n = e.firstChild();
404             for (; ! n.isNull(); n = n.nextSibling()) {
405                 if (! n.isElement()) {
406                     continue;
407                 }
408                 KoXmlElement el = n.toElement();
409                 if (el.tagName() == QLatin1String("workpackage")) {
410                     WorkPackage *wp = new WorkPackage(this);
411                     if (wp->loadLoggedXML(el, status)) {
412                         m_packageLog << wp;
413                     } else {
414                         errorPlan<<"Failed to load logged workpackage";
415                         delete wp;
416                     }
417                 }
418             }
419         }
420     }
421     //debugPlan<<m_name<<" loaded";
422     return true;
423 }
424 
save(QDomElement & element,const XmlSaveContext & context) const425 void Task::save(QDomElement &element, const XmlSaveContext &context)  const
426 {
427     if (!context.saveNode(this)) {
428         return;
429     }
430     QDomElement me = element.ownerDocument().createElement(QStringLiteral("task"));
431     element.appendChild(me);
432 
433     me.setAttribute(QStringLiteral("id"), m_id);
434     me.setAttribute("priority", QString::number(m_priority));
435     me.setAttribute(QStringLiteral("name"), m_name);
436     me.setAttribute(QStringLiteral("leader"), m_leader);
437     me.setAttribute(QStringLiteral("description"), m_description);
438 
439     me.setAttribute(QStringLiteral("scheduling"),constraintToString());
440     me.setAttribute(QStringLiteral("constraint-starttime"),m_constraintStartTime.toString(Qt::ISODate));
441     me.setAttribute(QStringLiteral("constraint-endtime"),m_constraintEndTime.toString(Qt::ISODate));
442 
443     me.setAttribute(QStringLiteral("startup-cost"), QString::number(m_startupCost));
444     me.setAttribute(QStringLiteral("shutdown-cost"), QString::number(m_shutdownCost));
445 
446     me.setAttribute(QStringLiteral("wbs"), wbsCode()); //NOTE: included for information
447 
448     m_estimate->save(me);
449 
450     m_documents.save(me);
451 
452     if (! m_requests.isEmpty()) {
453         m_requests.save(me);
454     }
455 
456     if (context.saveAll(this)) {
457         if (!m_schedules.isEmpty()) {
458             QDomElement schs = me.ownerDocument().createElement(QStringLiteral("schedules"));
459             me.appendChild(schs);
460             foreach (const Schedule *s, m_schedules) {
461                 if (!s->isDeleted()) {
462                     s->saveXML(schs);
463                 }
464             }
465         }
466         completion().saveXML(me);
467 
468         m_workPackage.saveXML(me);
469         // The workpackage log
470         if (!m_packageLog.isEmpty()) {
471             QDomElement log = me.ownerDocument().createElement(QStringLiteral("workpackage-log"));
472             me.appendChild(log);
473             foreach (const WorkPackage *wp, m_packageLog) {
474                 wp->saveLoggedXML(log);
475             }
476         }
477     }
478     if (context.saveChildren(this)) {
479         for (int i=0; i<numChildren(); i++) {
480             childNode(i)->save(me, context);
481         }
482     }
483 }
484 
saveAppointments(QDomElement & element,long id) const485 void Task::saveAppointments(QDomElement &element, long id) const {
486     //debugPlan<<m_name<<" id="<<id;
487     Schedule *sch = findSchedule(id);
488     if (sch) {
489         sch->saveAppointments(element);
490     }
491     foreach (const Node *n, m_nodes) {
492         n->saveAppointments(element, id);
493     }
494 }
495 
saveWorkPackageXML(QDomElement & element,long id) const496 void Task::saveWorkPackageXML(QDomElement &element, long id)  const
497 {
498     QDomElement me = element.ownerDocument().createElement(QStringLiteral("task"));
499     element.appendChild(me);
500 
501     me.setAttribute(QStringLiteral("id"), m_id);
502     me.setAttribute(QStringLiteral("name"), m_name);
503     me.setAttribute(QStringLiteral("leader"), m_leader);
504     me.setAttribute(QStringLiteral("description"), m_description);
505 
506     me.setAttribute(QStringLiteral("scheduling"),constraintToString());
507     me.setAttribute(QStringLiteral("constraint-starttime"),m_constraintStartTime.toString(Qt::ISODate));
508     me.setAttribute(QStringLiteral("constraint-endtime"),m_constraintEndTime.toString(Qt::ISODate));
509 
510     me.setAttribute(QStringLiteral("startup-cost"), QString::number(m_startupCost));
511     me.setAttribute(QStringLiteral("shutdown-cost"), QString::number(m_shutdownCost));
512 
513     me.setAttribute(QStringLiteral("wbs"), wbsCode()); // NOTE: included for information
514 
515     m_estimate->save(me);
516 
517     completion().saveXML(me);
518 
519     if (m_schedules.contains(id) && ! m_schedules[ id ]->isDeleted()) {
520         QDomElement schs = me.ownerDocument().createElement(QStringLiteral("schedules"));
521         me.appendChild(schs);
522         m_schedules[ id ]->saveXML(schs);
523     }
524     m_documents.save(me); // TODO: copying documents
525 }
526 
isStarted() const527 bool Task::isStarted() const
528 {
529     return completion().isStarted();
530 }
531 
plannedEffortCostPrDay(QDate start,QDate end,long id,EffortCostCalculationType typ) const532 EffortCostMap Task::plannedEffortCostPrDay(QDate start, QDate end, long id, EffortCostCalculationType typ) const {
533     //debugPlan<<m_name;
534     if (type() == Node::Type_Summarytask) {
535         EffortCostMap ec;
536         QListIterator<Node*> it(childNodeIterator());
537         while (it.hasNext()) {
538             ec += it.next() ->plannedEffortCostPrDay(start, end, id, typ);
539         }
540         return ec;
541     }
542     Schedule *s = schedule(id);
543     if (s) {
544         return s->plannedEffortCostPrDay(start, end, typ);
545     }
546     return EffortCostMap();
547 }
548 
plannedEffortCostPrDay(const Resource * resource,QDate start,QDate end,long id,EffortCostCalculationType typ) const549 EffortCostMap Task::plannedEffortCostPrDay(const Resource *resource, QDate start, QDate end, long id, EffortCostCalculationType typ) const {
550     //debugPlan<<m_name;
551     if (type() == Node::Type_Summarytask) {
552         EffortCostMap ec;
553         QListIterator<Node*> it(childNodeIterator());
554         while (it.hasNext()) {
555             ec += it.next() ->plannedEffortCostPrDay(resource, start, end, id, typ);
556         }
557         return ec;
558     }
559     Schedule *s = schedule(id);
560     if (s) {
561         return s->plannedEffortCostPrDay(resource, start, end, typ);
562     }
563     return EffortCostMap();
564 }
565 
actualEffortCostPrDay(QDate start,QDate end,long id,EffortCostCalculationType typ) const566 EffortCostMap Task::actualEffortCostPrDay(QDate start, QDate end, long id, EffortCostCalculationType typ) const {
567     //debugPlan<<m_name;
568     if (type() == Node::Type_Summarytask) {
569         EffortCostMap ec;
570         QListIterator<Node*> it(childNodeIterator());
571         while (it.hasNext()) {
572             ec += it.next() ->actualEffortCostPrDay(start, end, id, typ);
573         }
574         return ec;
575     }
576     switch (completion().entrymode()) {
577         case Completion::FollowPlan:
578             return plannedEffortCostPrDay(start, end, id, typ);
579         default:
580             return completion().effortCostPrDay(start, end, id);
581     }
582     return EffortCostMap();
583 }
584 
actualEffortCostPrDay(const Resource * resource,QDate start,QDate end,long id,EffortCostCalculationType typ) const585 EffortCostMap Task::actualEffortCostPrDay(const Resource *resource, QDate start, QDate end, long id, EffortCostCalculationType typ) const {
586     //debugPlan<<m_name;
587     if (type() == Node::Type_Summarytask) {
588         EffortCostMap ec;
589         QListIterator<Node*> it(childNodeIterator());
590         while (it.hasNext()) {
591             ec += it.next() ->actualEffortCostPrDay(resource, start, end, id, typ);
592         }
593         return ec;
594     }
595     switch (completion().entrymode()) {
596         case Completion::FollowPlan:
597             return plannedEffortCostPrDay(resource, start, end, id, typ);
598         default:
599             return completion().effortCostPrDay(resource, start, end);
600     }
601     return EffortCostMap();
602 }
603 
604 // Returns the total planned effort for this task (or subtasks)
plannedEffort(const Resource * resource,long id,EffortCostCalculationType typ) const605 Duration Task::plannedEffort(const Resource *resource, long id, EffortCostCalculationType typ) const {
606    //debugPlan;
607     Duration eff;
608     if (type() == Node::Type_Summarytask) {
609         foreach (const Node *n, childNodeIterator()) {
610             eff += n->plannedEffort(resource, id, typ);
611         }
612         return eff;
613     }
614     Schedule *s = schedule(id);
615     if (s) {
616         eff = s->plannedEffort(resource, typ);
617     }
618     return eff;
619 }
620 
621 // Returns the total planned effort for this task (or subtasks)
plannedEffort(long id,EffortCostCalculationType typ) const622 Duration Task::plannedEffort(long id, EffortCostCalculationType typ) const {
623    //debugPlan;
624     Duration eff;
625     if (type() == Node::Type_Summarytask) {
626         foreach (const Node *n, childNodeIterator()) {
627             eff += n->plannedEffort(id, typ);
628         }
629         return eff;
630     }
631     Schedule *s = schedule(id);
632     if (s) {
633         eff = s->plannedEffort(typ);
634     }
635     return eff;
636 }
637 
638 // Returns the total planned effort for this task (or subtasks) on date
plannedEffort(const Resource * resource,QDate date,long id,EffortCostCalculationType typ) const639 Duration Task::plannedEffort(const Resource *resource, QDate date, long id, EffortCostCalculationType typ) const {
640    //debugPlan;
641     Duration eff;
642     if (type() == Node::Type_Summarytask) {
643         foreach (const Node *n, childNodeIterator()) {
644             eff += n->plannedEffort(resource, date, id, typ);
645         }
646         return eff;
647     }
648     Schedule *s = schedule(id);
649     if (s) {
650         eff = s->plannedEffort(resource, date, typ);
651     }
652     return eff;
653 }
654 
655 // Returns the total planned effort for this task (or subtasks) on date
plannedEffort(QDate date,long id,EffortCostCalculationType typ) const656 Duration Task::plannedEffort(QDate date, long id, EffortCostCalculationType typ) const {
657    //debugPlan;
658     Duration eff;
659     if (type() == Node::Type_Summarytask) {
660         foreach (const Node *n, childNodeIterator()) {
661             eff += n->plannedEffort(date, id, typ);
662         }
663         return eff;
664     }
665     Schedule *s = schedule(id);
666     if (s) {
667         eff = s->plannedEffort(date, typ);
668     }
669     return eff;
670 }
671 
672 // Returns the total planned effort for this task (or subtasks) upto and including date
plannedEffortTo(QDate date,long id,EffortCostCalculationType typ) const673 Duration Task::plannedEffortTo(QDate date, long id, EffortCostCalculationType typ) const {
674     //debugPlan;
675     Duration eff;
676     if (type() == Node::Type_Summarytask) {
677         foreach (const Node *n, childNodeIterator()) {
678             eff += n->plannedEffortTo(date, id, typ);
679         }
680         return eff;
681     }
682     Schedule *s = schedule(id);
683     if (s) {
684         eff = s->plannedEffortTo(date, typ);
685     }
686     return eff;
687 }
688 
689 // Returns the total planned effort for this task (or subtasks) upto and including date
plannedEffortTo(const Resource * resource,QDate date,long id,EffortCostCalculationType typ) const690 Duration Task::plannedEffortTo(const Resource *resource, QDate date, long id, EffortCostCalculationType typ) const {
691     //debugPlan;
692     Duration eff;
693     if (type() == Node::Type_Summarytask) {
694         foreach (const Node *n, childNodeIterator()) {
695             eff += n->plannedEffortTo(resource, date, id, typ);
696         }
697         return eff;
698     }
699     Schedule *s = schedule(id);
700     if (s) {
701         eff = s->plannedEffortTo(resource, date, typ);
702     }
703     return eff;
704 }
705 
706 // Returns the total actual effort for this task (or subtasks)
actualEffort() const707 Duration Task::actualEffort() const {
708    //debugPlan;
709     Duration eff;
710     if (type() == Node::Type_Summarytask) {
711         foreach (const Node *n, childNodeIterator()) {
712             eff += n->actualEffort();
713         }
714     }
715     return completion().actualEffort();
716 }
717 
718 // Returns the total actual effort for this task (or subtasks) on date
actualEffort(QDate date) const719 Duration Task::actualEffort(QDate date) const {
720    //debugPlan;
721     Duration eff;
722     if (type() == Node::Type_Summarytask) {
723         foreach (const Node *n, childNodeIterator()) {
724             eff += n->actualEffort(date);
725         }
726         return eff;
727     }
728     return completion().actualEffort(date);
729 }
730 
731 // Returns the total actual effort for this task (or subtasks) to date
actualEffortTo(QDate date) const732 Duration Task::actualEffortTo(QDate date) const {
733    //debugPlan;
734     Duration eff;
735     if (type() == Node::Type_Summarytask) {
736         foreach (const Node *n, childNodeIterator()) {
737             eff += n->actualEffortTo(date);
738         }
739         return eff;
740     }
741     return completion().actualEffortTo(date);
742 }
743 
plannedCost(long id,EffortCostCalculationType typ) const744 EffortCost Task::plannedCost(long id, EffortCostCalculationType typ) const {
745     //debugPlan;
746     if (type() == Node::Type_Summarytask) {
747         return Node::plannedCost(id, typ);
748     }
749     EffortCost c;
750     Schedule *s = schedule(id);
751     if (s) {
752         c = s->plannedCost(typ);
753     }
754     return c;
755 }
756 
plannedCostTo(QDate date,long id,EffortCostCalculationType typ) const757 double Task::plannedCostTo(QDate date, long id, EffortCostCalculationType typ) const {
758     //debugPlan;
759     double c = 0;
760     if (type() == Node::Type_Summarytask) {
761         foreach (const Node *n, childNodeIterator()) {
762             c += n->plannedCostTo(date, id, typ);
763         }
764         return c;
765     }
766     Schedule *s = schedule(id);
767     if (s == 0) {
768         return c;
769     }
770     c = s->plannedCostTo(date, typ);
771     if (date >= s->startTime.date()) {
772         c += m_startupCost;
773     }
774     if (date >= s->endTime.date()) {
775         c += m_shutdownCost;
776     }
777     return c;
778 }
779 
actualCostTo(long int id,QDate date) const780 EffortCost Task::actualCostTo(long int id, QDate date) const {
781     //debugPlan;
782     EffortCostMap ecm = acwp(id);
783     return EffortCost(ecm.effortTo(date), ecm.costTo(date));
784 }
785 
bcws(QDate date,long id) const786 double Task::bcws(QDate date, long id) const
787 {
788     //debugPlan;
789     double c = plannedCostTo(date, id);
790     //debugPlan<<c;
791     return c;
792 }
793 
bcwsPrDay(long int id,EffortCostCalculationType typ)794 EffortCostMap Task::bcwsPrDay(long int id, EffortCostCalculationType typ)
795 {
796     //debugPlan;
797     if (type() == Node::Type_Summarytask) {
798         return Node::bcwsPrDay(id);
799     }
800     Schedule *s = schedule(id);
801     if (s == 0) {
802         return EffortCostMap();
803     }
804     EffortCostCache &cache = s->bcwsPrDayCache(typ);
805     if (! cache.cached) {
806         EffortCostMap ec = s->bcwsPrDay(typ);
807         if (typ != ECCT_Work) {
808             if (m_startupCost > 0.0) {
809                 ec.add(s->startTime.date(), Duration::zeroDuration, m_startupCost);
810             }
811             if (m_shutdownCost > 0.0) {
812                 ec.add(s->endTime.date(), Duration::zeroDuration, m_shutdownCost);
813             }
814             cache.effortcostmap = ec;
815             cache.cached = true;
816         }
817     }
818     return cache.effortcostmap;
819 }
820 
bcwpPrDay(long int id,EffortCostCalculationType typ)821 EffortCostMap Task::bcwpPrDay(long int id, EffortCostCalculationType typ)
822 {
823     //debugPlan;
824     if (type() == Node::Type_Summarytask) {
825         return Node::bcwpPrDay(id, typ);
826     }
827     Schedule *s = schedule(id);
828     if (s == 0) {
829         return EffortCostMap();
830     }
831     EffortCostCache cache = s->bcwpPrDayCache(typ);
832     if (! cache.cached) {
833         // do not use bcws cache, it includes startup/shutdown cost
834         EffortCostMap e = s->plannedEffortCostPrDay(s->appointmentStartTime().date(), s->appointmentEndTime().date(), typ);
835         if (completion().isStarted() && ! e.isEmpty()) {
836             // calculate bcwp on bases of bcws *without* startup/shutdown cost
837             double totEff = e.totalEffort().toDouble(Duration::Unit_h);
838             double totCost = e.totalCost();
839             QDate sd = completion().entries().keys().value(0);
840             if (! sd.isValid() || e.startDate() < sd) {
841                 sd = e.startDate();
842             }
843             QDate ed = qMax(e.endDate(), completion().entryDate());
844             for (QDate d = sd; d <= ed; d = d.addDays(1)) {
845                 double p = (double)(completion().percentFinished(d)) / 100.0;
846                 EffortCost ec = e.days()[ d ];
847                 ec.setBcwpEffort(totEff  * p);
848                 ec.setBcwpCost(totCost  * p);
849                 e.insert(d, ec);
850             }
851         }
852         if (typ != ECCT_Work) {
853             // add bcws startup/shutdown cost
854             if (m_startupCost > 0.0) {
855                 e.add(s->startTime.date(), Duration::zeroDuration, m_startupCost);
856             }
857             if (m_shutdownCost > 0.0) {
858                 e.add(s->endTime.date(), Duration::zeroDuration, m_shutdownCost);
859             }
860             // add bcwp startup/shutdown cost
861             if (m_shutdownCost > 0.0 && completion().finishIsValid()) {
862                 QDate finish = completion().finishTime().date();
863                 e.addBcwpCost(finish, m_shutdownCost);
864                 debugPlan<<"addBcwpCost:"<<finish<<m_shutdownCost;
865                 // bcwp is cumulative so add to all entries after finish (in case task finished early)
866                 for (EffortCostDayMap::const_iterator it = e.days().constBegin(); it != e.days().constEnd(); ++it) {
867                     const QDate date = it.key();
868                     if (date > finish) {
869                         e.addBcwpCost(date, m_shutdownCost);
870                         debugPlan<<"addBcwpCost:"<<date<<m_shutdownCost;
871                     }
872                 }
873             }
874             if (m_startupCost > 0.0 && completion().startIsValid()) {
875                 QDate start = completion().startTime().date();
876                 e.addBcwpCost(start, m_startupCost);
877                 // bcwp is cumulative so add to all entries after start
878                 for (EffortCostDayMap::const_iterator it = e.days().constBegin(); it != e.days().constEnd(); ++it) {
879                     const QDate date = it.key();
880                     if (date > start) {
881                         e.addBcwpCost(date, m_startupCost);
882                     }
883                 }
884             }
885         }
886         cache.effortcostmap = e;
887         cache.cached = true;
888     }
889     return cache.effortcostmap;
890 }
891 
budgetedWorkPerformed(QDate date,long id) const892 Duration Task::budgetedWorkPerformed(QDate date, long id) const
893 {
894     //debugPlan;
895     Duration e;
896     if (type() == Node::Type_Summarytask) {
897         foreach (const Node *n, childNodeIterator()) {
898             e += n->budgetedWorkPerformed(date, id);
899         }
900         return e;
901     }
902 
903     e = plannedEffort(id) * (double)completion().percentFinished(date) / 100.0;
904     //debugPlan<<m_name<<"("<<id<<")"<<date<<"="<<e.toString();
905     return e;
906 }
907 
budgetedCostPerformed(QDate date,long id) const908 double Task::budgetedCostPerformed(QDate date, long id) const
909 {
910     //debugPlan;
911     double c = 0.0;
912     if (type() == Node::Type_Summarytask) {
913         foreach (const Node *n, childNodeIterator()) {
914             c += n->budgetedCostPerformed(date, id);
915         }
916         return c;
917     }
918 
919     c = plannedCost(id).cost() * (double)completion().percentFinished(date) / 100.0;
920     if (completion().isStarted() && date >= completion().startTime().date()) {
921         c += m_startupCost;
922     }
923     if (completion().isFinished() && date >= completion().finishTime().date()) {
924         c += m_shutdownCost;
925     }
926     //debugPlan<<m_name<<"("<<id<<")"<<date<<"="<<e.toString();
927     return c;
928 }
929 
bcwp(long id) const930 double Task::bcwp(long id) const
931 {
932     return bcwp(QDate::currentDate(), id);
933 }
934 
bcwp(QDate date,long id) const935 double Task::bcwp(QDate date, long id) const
936 {
937     return budgetedCostPerformed(date, id);
938 }
939 
acwp(long int id,KPlato::EffortCostCalculationType typ)940 EffortCostMap Task::acwp(long int id, KPlato::EffortCostCalculationType typ)
941 {
942     if (type() == Node::Type_Summarytask) {
943         return Node::acwp(id, typ);
944     }
945     Schedule *s = schedule(id);
946     if (s == 0) {
947         return EffortCostMap();
948     }
949     EffortCostCache ec = s->acwpCache(typ);
950     if (! ec.cached) {
951         //debugPlan<<m_name<<completion().entrymode();
952         EffortCostMap m;
953         switch (completion().entrymode()) {
954             case Completion::FollowPlan:
955                 //TODO
956                 break;
957             case Completion::EnterCompleted:
958                 //hmmm
959             default: {
960                 m = completion().actualEffortCost(id);
961                 if (completion().isStarted()) {
962                     EffortCost e;
963                     e.setCost(m_startupCost);
964                     m.add(completion().startTime().date(), e);
965                 }
966                 if (completion().isFinished()) {
967                     EffortCost e;
968                     e.setCost(m_shutdownCost);
969                     m.add(completion().finishTime().date(), e);
970                 }
971             }
972         }
973         ec.effortcostmap = m;
974         ec.cached = true;
975     }
976     return ec.effortcostmap;
977 }
978 
acwp(QDate date,long id) const979 EffortCost Task::acwp(QDate date, long id) const
980 {
981     //debugPlan;
982     if (type() == Node::Type_Summarytask) {
983         return Node::acwp(date, id);
984     }
985     EffortCost c;
986     c = completion().actualCostTo(id, date);
987     if (completion().isStarted() && date >= completion().startTime().date()) {
988         c.add(Duration::zeroDuration, m_startupCost);
989     }
990     if (completion().isFinished() && date >= completion().finishTime().date()) {
991         c.add(Duration::zeroDuration, m_shutdownCost);
992     }
993     return c;
994 }
995 
schedulePerformanceIndex(QDate date,long id) const996 double Task::schedulePerformanceIndex(QDate date, long id) const {
997     //debugPlan;
998     double r = 1.0;
999     double s = bcws(date, id);
1000     double p = bcwp(date, id);
1001     if (s > 0.0) {
1002         r = p / s;
1003     }
1004     return r;
1005 }
1006 
effortPerformanceIndex(QDate date,long id) const1007 double Task::effortPerformanceIndex(QDate date, long id) const {
1008     //debugPlan;
1009     double r = 1.0;
1010     Duration a, b;
1011     if (m_estimate->type() == Estimate::Type_Effort) {
1012         Duration b = budgetedWorkPerformed(date, id);
1013         if (b == Duration::zeroDuration) {
1014             return r;
1015         }
1016         Duration a = actualEffortTo(date);
1017         if (b == Duration::zeroDuration) {
1018             return 1.0;
1019         }
1020         r = b.toDouble() / a.toDouble();
1021     } else if (m_estimate->type() == Estimate::Type_Duration) {
1022         //TODO
1023     }
1024     return r;
1025 }
1026 
1027 
1028 //FIXME Handle summarytasks
costPerformanceIndex(long int id,QDate date,bool * error) const1029 double Task::costPerformanceIndex(long int id, QDate date, bool *error) const
1030 {
1031     double res = 0.0;
1032     double ac = actualCostTo(id, date).cost();
1033 
1034     bool e = (ac == 0.0 || completion().percentFinished() == 0);
1035     if (error) {
1036         *error = e;
1037     }
1038     if (!e) {
1039         res = (plannedCostTo(date, id) * completion().percentFinished()) / (100 * ac);
1040     }
1041     return res;
1042 }
1043 
initiateCalculation(MainSchedule & sch)1044 void Task::initiateCalculation(MainSchedule &sch) {
1045     //debugPlan<<m_name<<" schedule:"<<sch.name()<<" id="<<sch.id();
1046     m_currentSchedule = createSchedule(&sch);
1047     m_currentSchedule->initiateCalculation();
1048     clearProxyRelations();
1049     Node::initiateCalculation(sch);
1050     m_calculateForwardRun = false;
1051     m_calculateBackwardRun = false;
1052     m_scheduleForwardRun = false;
1053     m_scheduleBackwardRun = false;
1054 }
1055 
1056 
initiateCalculationLists(MainSchedule & sch)1057 void Task::initiateCalculationLists(MainSchedule &sch) {
1058     //debugPlan<<m_name;
1059     if (type() == Node::Type_Summarytask) {
1060         sch.insertSummaryTask(this);
1061         // propagate my relations to my children and dependent nodes
1062         foreach (Node *n, m_nodes) {
1063             if (!dependParentNodes().isEmpty()) {
1064                 n->addParentProxyRelations(dependParentNodes());
1065             }
1066             if (!dependChildNodes().isEmpty()) {
1067                 n->addChildProxyRelations(dependChildNodes());
1068             }
1069             n->initiateCalculationLists(sch);
1070         }
1071     } else {
1072         if (isEndNode()) {
1073             sch.insertEndNode(this);
1074             //debugPlan<<"endnodes append:"<<m_name;
1075         }
1076         if (isStartNode()) {
1077             sch.insertStartNode(this);
1078             //debugPlan<<"startnodes append:"<<m_name;
1079         }
1080         if ((m_constraint == Node::MustStartOn) ||
1081             (m_constraint == Node::MustFinishOn) ||
1082             (m_constraint == Node::FixedInterval))
1083         {
1084             sch.insertHardConstraint(this);
1085         }
1086         else if ((m_constraint == Node::StartNotEarlier) ||
1087                   (m_constraint == Node::FinishNotLater))
1088         {
1089             sch.insertSoftConstraint(this);
1090         }
1091     }
1092 }
1093 
calculatePredeccessors(const QList<Relation * > & list_,int use)1094 DateTime Task::calculatePredeccessors(const QList<Relation*> &list_, int use) {
1095     DateTime time;
1096     // do them forward
1097     QMultiMap<int, Relation*> lst;
1098     for (Relation* r : list_) {
1099         lst.insert(-r->parent()->priority(), r);
1100     }
1101     const QList<Relation*> list = lst.values();
1102     foreach (Relation *r, list) {
1103         if (r->parent()->type() == Type_Summarytask) {
1104             //debugPlan<<"Skip summarytask:"<<it.current()->parent()->name();
1105             continue; // skip summarytasks
1106         }
1107         DateTime t = r->parent()->calculateForward(use); // early finish
1108         switch (r->type()) {
1109             case Relation::StartStart:
1110                 // I can't start earlier than my predesseccor
1111                 t = r->parent()->earlyStart() + r->lag();
1112                 break;
1113             case Relation::FinishFinish: {
1114                 // I can't finish earlier than my predeccessor, so
1115                 // I can't start earlier than it's (earlyfinish+lag)- my duration
1116                 t += r->lag();
1117                 Schedule::OBState obs = m_currentSchedule->allowOverbookingState();
1118                 m_currentSchedule->setAllowOverbookingState(Schedule::OBS_Allow);
1119 #ifndef PLAN_NLOGDEBUG
1120                 m_currentSchedule->logDebug(QStringLiteral("FinishFinish: get duration to calculate early finish"));
1121 #endif
1122                 t -= duration(t, use, true);
1123                 m_currentSchedule->setAllowOverbookingState(obs);
1124                 break;
1125             }
1126             default:
1127                 t += r->lag();
1128                 break;
1129         }
1130         if (!time.isValid() || t > time)
1131             time = t;
1132     }
1133     //debugPlan<<time.toString()<<""<<m_name<<" calculatePredeccessors() ("<<list.count()<<")";
1134     return time;
1135 }
1136 
calculateForward(int use)1137 DateTime Task::calculateForward(int use)
1138 {
1139     if (m_calculateForwardRun) {
1140         return m_currentSchedule->earlyFinish;
1141     }
1142     if (m_currentSchedule == 0) {
1143         return DateTime();
1144     }
1145     Schedule *cs = m_currentSchedule;
1146     cs->setCalculationMode(Schedule::CalculateForward);
1147     //cs->logDebug("calculateForward: earlyStart=" + cs->earlyStart.toString());
1148     // calculate all predecessors
1149     if (!dependParentNodes().isEmpty()) {
1150         DateTime time = calculatePredeccessors(dependParentNodes(), use);
1151         if (time.isValid() && time > cs->earlyStart) {
1152             cs->earlyStart = time;
1153             //cs->logDebug(QString("calculate forward: early start moved to: %1").arg(cs->earlyStart.toString()));
1154         }
1155     }
1156     if (!m_parentProxyRelations.isEmpty()) {
1157         DateTime time = calculatePredeccessors(m_parentProxyRelations, use);
1158         if (time.isValid() && time > cs->earlyStart) {
1159             cs->earlyStart = time;
1160             //cs->logDebug(QString("calculate forward: early start moved to: %1").arg(cs->earlyStart.toString()));
1161         }
1162     }
1163     m_calculateForwardRun = true;
1164     //cs->logDebug("calculateForward: earlyStart=" + cs->earlyStart.toString());
1165     return calculateEarlyFinish(use);
1166 }
1167 
calculateEarlyFinish(int use)1168 DateTime Task::calculateEarlyFinish(int use) {
1169     //debugPlan<<m_name;
1170     if (m_currentSchedule == 0) {
1171         return DateTime();
1172     }
1173     Schedule *cs = m_currentSchedule;
1174     if (m_visitedForward) {
1175         //debugPlan<<earliestStart.toString()<<" +"<<m_durationBackward.toString()<<""<<m_name<<" calculateForward() (visited)";
1176         return m_earlyFinish;
1177     }
1178     bool pert = cs->usePert();
1179     cs->setCalculationMode(Schedule::CalculateForward);
1180 #ifndef PLAN_NLOGDEBUG
1181     QTime timer;
1182     timer.start();
1183     cs->logDebug(QStringLiteral("Start calculate forward: %1 ").arg(constraintToString(true)));
1184 #endif
1185     QLocale locale;
1186     cs->logInfo(i18n("Calculate early finish "));
1187     //debugPlan<<"------>"<<m_name<<""<<cs->earlyStart;
1188     if (type() == Node::Type_Task) {
1189         m_durationForward = m_estimate->value(use, pert);
1190         switch (constraint()) {
1191             case Node::ASAP:
1192             case Node::ALAP:
1193             {
1194                 //debugPlan<<m_name<<" ASAP/ALAP:"<<cs->earlyStart;
1195                 cs->earlyStart = workTimeAfter(cs->earlyStart);
1196                 m_durationForward = duration(cs->earlyStart, use, false);
1197                 m_earlyFinish = cs->earlyStart + m_durationForward;
1198 #ifndef PLAN_NLOGDEBUG
1199                 cs->logDebug("ASAP/ALAP: " + cs->earlyStart.toString() + '+' + m_durationForward.toString() + '=' + m_earlyFinish.toString());
1200 #endif
1201                 if (!cs->allowOverbooking()) {
1202                     cs->startTime = cs->earlyStart;
1203                     cs->endTime = m_earlyFinish;
1204                     makeAppointments();
1205 
1206                     // calculate duration wo checking booking = the earliest finish possible
1207                     Schedule::OBState obs = cs->allowOverbookingState();
1208                     cs->setAllowOverbookingState(Schedule::OBS_Allow);
1209                     m_durationForward = duration(cs->earlyStart, use, false);
1210                     cs->setAllowOverbookingState(obs);
1211 #ifndef PLAN_NLOGDEBUG
1212                     cs->logDebug("ASAP/ALAP earliest possible: " + cs->earlyStart.toString() + '+' + m_durationForward.toString() + '=' + (cs->earlyStart+m_durationForward).toString());
1213 #endif
1214                 }
1215                 break;
1216             }
1217             case Node::MustFinishOn:
1218             {
1219                 cs->earlyStart = workTimeAfter(cs->earlyStart);
1220                 m_durationForward = duration(cs->earlyStart, use, false);
1221                 cs->earlyFinish = cs->earlyStart + m_durationForward;
1222                 //debugPlan<<"MustFinishOn:"<<m_constraintEndTime<<cs->earlyStart<<cs->earlyFinish;
1223                 if (cs->earlyFinish > m_constraintEndTime) {
1224                     cs->logWarning(i18nc("1=type of constraint", "%1: Failed to meet constraint", constraintToString(true)));
1225                 }
1226                 cs->earlyFinish = qMax(cs->earlyFinish, m_constraintEndTime);
1227                 if (!cs->allowOverbooking()) {
1228                     cs->endTime = cs->earlyFinish;
1229                     cs->startTime = cs->earlyFinish - duration(cs->earlyFinish, use, true);
1230                     makeAppointments();
1231                 }
1232                 m_earlyFinish = cs->earlyFinish;
1233                 m_durationForward = m_earlyFinish - cs->earlyStart;
1234                 break;
1235             }
1236             case Node::FinishNotLater:
1237             {
1238                 m_durationForward = duration(cs->earlyStart, use, false);
1239                 cs->earlyFinish = cs->earlyStart + m_durationForward;
1240                 //debugPlan<<"FinishNotLater:"<<m_constraintEndTime<<cs->earlyStart<<cs->earlyFinish;
1241                 if (cs->earlyFinish > m_constraintEndTime) {
1242                     cs->logWarning(i18nc("1=type of constraint", "%1: Failed to meet constraint", constraintToString(true)));
1243                 }
1244                 if (!cs->allowOverbooking()) {
1245                     cs->startTime = cs->earlyStart;
1246                     cs->endTime = cs->earlyFinish;
1247                     makeAppointments();
1248                 }
1249                 m_earlyFinish = cs->earlyStart + m_durationForward;
1250                 break;
1251             }
1252             case Node::MustStartOn:
1253             case Node::StartNotEarlier:
1254             {
1255                 //debugPlan<<"MSO/SNE:"<<m_constraintStartTime<<cs->earlyStart;
1256                 cs->logDebug(constraintToString() + ": " + m_constraintStartTime.toString() + ' ' + cs->earlyStart.toString());
1257                 cs->earlyStart = workTimeAfter(qMax(cs->earlyStart, m_constraintStartTime));
1258                 if (cs->earlyStart < m_constraintStartTime) {
1259                     cs->logWarning(i18nc("1=type of constraint", "%1: Failed to meet constraint", constraintToString(true)));
1260                 }
1261                 m_durationForward = duration(cs->earlyStart, use, false);
1262                 m_earlyFinish = cs->earlyStart + m_durationForward;
1263                 if (!cs->allowOverbooking()) {
1264                     cs->startTime = cs->earlyStart;
1265                     cs->endTime = m_earlyFinish;
1266                     makeAppointments();
1267 
1268                     // calculate duration wo checking booking = the earliest finish possible
1269                     Schedule::OBState obs = cs->allowOverbookingState();
1270                     cs->setAllowOverbookingState(Schedule::OBS_Allow);
1271                     m_durationForward = duration(cs->startTime, use, false);
1272                     cs->setAllowOverbookingState(obs);
1273                     m_earlyFinish = cs->earlyStart + m_durationForward;
1274 #ifndef PLAN_NLOGDEBUG
1275                     cs->logDebug("MSO/SNE earliest possible: " + cs->earlyStart.toString() + '+' + m_durationForward.toString() + '=' + (cs->earlyStart+m_durationForward).toString());
1276 #endif
1277                 }
1278                 break;
1279             }
1280             case Node::FixedInterval: {
1281                 if (cs->earlyStart > m_constraintStartTime) {
1282                     cs->logWarning(i18nc("1=type of constraint", "%1: Failed to meet constraint", constraintToString(true)));
1283                 }
1284                 //cs->earlyStart = m_constraintStartTime;
1285                 m_durationForward = m_constraintEndTime - m_constraintStartTime;
1286                 if (cs->earlyStart < m_constraintStartTime) {
1287                     m_durationForward = m_constraintEndTime - cs->earlyStart;
1288                 }
1289                 if (!cs->allowOverbooking()) {
1290                     cs->startTime = m_constraintStartTime;
1291                     cs->endTime = m_constraintEndTime;
1292                     makeAppointments();
1293                 }
1294                 m_earlyFinish = cs->earlyStart + m_durationForward;
1295                 break;
1296             }
1297         }
1298     } else if (type() == Node::Type_Milestone) {
1299         m_durationForward = Duration::zeroDuration;
1300         switch (constraint()) {
1301             case Node::MustFinishOn:
1302                 //debugPlan<<"MustFinishOn:"<<m_constraintEndTime<<cs->earlyStart;
1303                 //cs->logDebug(QString("%1: %2, early start: %3").arg(constraintToString()).arg(m_constraintEndTime.toString()).arg(cs->earlyStart.toString()));
1304                 if (cs->earlyStart < m_constraintEndTime) {
1305                     m_durationForward = m_constraintEndTime - cs->earlyStart;
1306                 }
1307                 if (cs->earlyStart > m_constraintEndTime) {
1308                     cs->logWarning(i18nc("1=type of constraint", "%1: Failed to meet constraint", constraintToString(true)));
1309                 }
1310                 m_earlyFinish = cs->earlyStart + m_durationForward;
1311                 break;
1312             case Node::FinishNotLater:
1313                 //debugPlan<<"FinishNotLater:"<<m_constraintEndTime<<cs->earlyStart;
1314                 if (cs->earlyStart > m_constraintEndTime) {
1315                     cs->logWarning(i18nc("1=type of constraint", "%1: Failed to meet constraint", constraintToString(true)));
1316                 }
1317                 m_earlyFinish = cs->earlyStart;
1318                 break;
1319             case Node::MustStartOn:
1320                 //debugPlan<<"MustStartOn:"<<m_constraintStartTime<<cs->earlyStart;
1321                 if (cs->earlyStart < m_constraintStartTime) {
1322                     m_durationForward = m_constraintStartTime - cs->earlyStart;
1323                 }
1324                 if (cs->earlyStart > m_constraintStartTime) {
1325                     cs->logWarning(i18nc("1=type of constraint", "%1: Failed to meet constraint", constraintToString(true)));
1326                 }
1327                 m_earlyFinish = cs->earlyStart + m_durationForward;
1328                 break;
1329             case Node::StartNotEarlier:
1330                 //debugPlan<<"StartNotEarlier:"<<m_constraintStartTime<<cs->earlyStart;
1331                 if (cs->earlyStart < m_constraintStartTime) {
1332                     m_durationForward = m_constraintStartTime - cs->earlyStart;
1333                 }
1334                 m_earlyFinish = cs->earlyStart + m_durationForward;
1335                 break;
1336             case Node::FixedInterval:
1337                 m_earlyFinish = cs->earlyStart + m_durationForward;
1338                 break;
1339             default:
1340                 m_earlyFinish = cs->earlyStart + m_durationForward;
1341                 break;
1342         }
1343         //debugPlan<<m_name<<""<<earliestStart.toString();
1344     } else if (type() == Node::Type_Summarytask) {
1345         warnPlan<<"Summarytasks should not be calculated here: "<<m_name;
1346     } else { // ???
1347         m_durationForward = Duration::zeroDuration;
1348     }
1349     m_visitedForward = true;
1350     cs->insertForwardNode(this);
1351     cs->earlyFinish = cs->earlyStart + m_durationForward;
1352     foreach (const Appointment *a, cs->appointments(Schedule::CalculateForward)) {
1353         cs->logInfo(i18n("Resource %1 booked from %2 to %3", a->resource()->resource()->name(), locale.toString(a->startTime(), QLocale::ShortFormat), locale.toString(a->endTime(), QLocale::ShortFormat)));
1354     }
1355     // clean up temporary usage
1356     cs->startTime = DateTime();
1357     cs->endTime = DateTime();
1358     cs->duration = Duration::zeroDuration;
1359     cs->logInfo(i18n("Early finish calculated: %1", locale.toString(cs->earlyFinish, QLocale::ShortFormat)));
1360     cs->incProgress();
1361 #ifndef PLAN_NLOGDEBUG
1362     cs->logDebug(QStringLiteral("Finished calculate forward: %1 ms").arg(timer.elapsed()));
1363 #endif
1364     return m_earlyFinish;
1365 }
1366 
calculateSuccessors(const QList<Relation * > & list_,int use)1367 DateTime Task::calculateSuccessors(const QList<Relation*> &list_, int use) {
1368     DateTime time;
1369     QMultiMap<int, Relation*> lst;
1370     for (Relation* r : list_) {
1371         lst.insert(-r->child()->priority(), r);
1372     }
1373     const QList<Relation*> list = lst.values();
1374     foreach (Relation *r, list) {
1375         if (r->child()->type() == Type_Summarytask) {
1376             //debugPlan<<"Skip summarytask:"<<r->parent()->name();
1377             continue; // skip summarytasks
1378         }
1379         DateTime t = r->child()->calculateBackward(use);
1380         switch (r->type()) {
1381             case Relation::StartStart: {
1382                 // I must start before my successor, so
1383                 // I can't finish later than it's (starttime-lag) + my duration
1384                 t -= r->lag();
1385                 Schedule::OBState obs = m_currentSchedule->allowOverbookingState();
1386                 m_currentSchedule->setAllowOverbookingState(Schedule::OBS_Allow);
1387 #ifndef PLAN_NLOGDEBUG
1388                 m_currentSchedule->logDebug(QStringLiteral("StartStart: get duration to calculate late start"));
1389 #endif
1390                 t += duration(t, use, false);
1391                 m_currentSchedule->setAllowOverbookingState(obs);
1392                 break;
1393             }
1394             case Relation::FinishFinish:
1395                 // My successor cannot finish before me, so
1396                 // I can't finish later than it's latest finish - lag
1397                 t = r->child()->lateFinish() -  r->lag();
1398                 break;
1399             default:
1400                 t -= r->lag();
1401                 break;
1402         }
1403         if (!time.isValid() || t < time)
1404             time = t;
1405     }
1406     //debugPlan<<time.toString()<<""<<m_name<<" calculateSuccessors() ("<<list.count()<<")";
1407     return time;
1408 }
1409 
calculateBackward(int use)1410 DateTime Task::calculateBackward(int use) {
1411     //debugPlan<<m_name;
1412     if (m_calculateBackwardRun) {
1413         return m_currentSchedule->lateStart;
1414     }
1415     if (m_currentSchedule == 0) {
1416         return DateTime();
1417     }
1418     Schedule *cs = m_currentSchedule;
1419     cs->setCalculationMode(Schedule::CalculateBackward);
1420     //cs->lateFinish = projectNode()->constraintEndTime();
1421     // calculate all successors
1422     if (!dependChildNodes().isEmpty()) {
1423         DateTime time = calculateSuccessors(dependChildNodes(), use);
1424         if (time.isValid() && time < cs->lateFinish) {
1425             cs->lateFinish = time;
1426         }
1427     }
1428     if (!m_childProxyRelations.isEmpty()) {
1429         DateTime time = calculateSuccessors(m_childProxyRelations, use);
1430         if (time.isValid() && time < cs->lateFinish) {
1431             cs->lateFinish = time;
1432         }
1433     }
1434     m_calculateBackwardRun = true;
1435     return calculateLateStart(use);
1436 }
1437 
calculateLateStart(int use)1438 DateTime Task::calculateLateStart(int use) {
1439     //debugPlan<<m_name;
1440     if (m_currentSchedule == 0) {
1441         return DateTime();
1442     }
1443     Schedule *cs = m_currentSchedule;
1444     if (m_visitedBackward) {
1445         //debugPlan<<latestFinish.toString()<<" -"<<m_durationBackward.toString()<<""<<m_name<<" calculateBackward() (visited)";
1446         return cs->lateStart;
1447     }
1448     bool pert = cs->usePert();
1449     cs->setCalculationMode(Schedule::CalculateBackward);
1450 #ifndef PLAN_NLOGDEBUG
1451     QTime timer;
1452     timer.start();
1453     cs->logDebug(QStringLiteral("Start calculate backward: %1 ").arg(constraintToString(true)));
1454 #endif
1455     QLocale locale;
1456     cs->logInfo(i18n("Calculate late start"));
1457     cs->logDebug(QStringLiteral("%1: late finish= %2").arg(constraintToString()).arg(cs->lateFinish.toString()));
1458     //debugPlan<<m_name<<" id="<<cs->id()<<" mode="<<cs->calculationMode()<<": latestFinish="<<cs->lateFinish;
1459     if (type() == Node::Type_Task) {
1460         m_durationBackward = m_estimate->value(use, pert);
1461         switch (constraint()) {
1462             case Node::ASAP:
1463             case Node::ALAP:
1464                 //debugPlan<<m_name<<" ASAP/ALAP:"<<cs->lateFinish;
1465                 cs->lateFinish = workTimeBefore(cs->lateFinish);
1466                 m_durationBackward = duration(cs->lateFinish, use, true);
1467                 cs->lateStart = cs->lateFinish - m_durationBackward;
1468 #ifndef PLAN_NLOGDEBUG
1469                 cs->logDebug("ASAP/ALAP: " + cs->lateFinish.toString() + '-' + m_durationBackward.toString() + '=' + cs->lateStart.toString());
1470 #endif
1471                 if (!cs->allowOverbooking()) {
1472                     cs->startTime = cs->lateStart;
1473                     cs->endTime = cs->lateFinish;
1474                     makeAppointments();
1475 
1476                     // calculate wo checking bookings = latest start possible
1477                     Schedule::OBState obs = cs->allowOverbookingState();
1478                     cs->setAllowOverbookingState(Schedule::OBS_Allow);
1479                     m_durationBackward = duration(cs->lateFinish, use, true);
1480                     cs->setAllowOverbookingState(obs);
1481 #ifndef PLAN_NLOGDEBUG
1482                     cs->logDebug("ASAP/ALAP latest start possible: " + cs->lateFinish.toString() + '-' + m_durationBackward.toString() + '=' + (cs->lateFinish-m_durationBackward).toString());
1483 #endif
1484                 }
1485                 break;
1486             case Node::MustStartOn:
1487             case Node::StartNotEarlier:
1488             {
1489                 //debugPlan<<"MustStartOn:"<<m_constraintStartTime<<cs->lateFinish;
1490                 cs->lateFinish = workTimeBefore(cs->lateFinish);
1491                 m_durationBackward = duration(cs->lateFinish, use, true);
1492                 cs->lateStart = cs->lateFinish - m_durationBackward;
1493                 if (cs->lateStart < m_constraintStartTime) {
1494                     cs->logWarning(i18nc("1=type of constraint", "%1: Failed to meet constraint", constraintToString(true)));
1495                 } else {
1496                     cs->lateStart = qMax(cs->earlyStart, m_constraintStartTime);
1497                 }
1498                 if (!cs->allowOverbooking()) {
1499                     if (constraint() == MustStartOn) {
1500                         cs->startTime = m_constraintStartTime;
1501                         cs->endTime = m_constraintStartTime + duration(m_constraintStartTime, use, false);
1502                     } else {
1503                         cs->startTime = qMax(cs->lateStart, m_constraintStartTime);
1504                         cs->endTime = qMax(cs->lateFinish, cs->startTime); // safety
1505                     }
1506                     makeAppointments();
1507                 }
1508                 cs->lateStart = cs->lateFinish - m_durationBackward;
1509                 break;
1510             }
1511             case Node::MustFinishOn:
1512             case Node::FinishNotLater:
1513                 //debugPlan<<"MustFinishOn:"<<m_constraintEndTime<<cs->lateFinish;
1514                 cs->lateFinish = workTimeBefore(cs->lateFinish);
1515                 cs->endTime = cs->lateFinish;
1516                 if (cs->lateFinish < m_constraintEndTime) {
1517                     cs->logWarning(i18nc("1=type of constraint", "%1: Failed to meet constraint", constraintToString(true)));
1518                 } else {
1519                     cs->endTime = qMax(cs->earlyFinish, m_constraintEndTime);
1520                 }
1521                 m_durationBackward = duration(cs->endTime, use, true);
1522                 cs->startTime = cs->endTime - m_durationBackward;
1523                 if (!cs->allowOverbooking()) {
1524                     makeAppointments();
1525                 }
1526                 m_durationBackward = cs->lateFinish - cs->startTime;
1527                 cs->lateStart = cs->lateFinish - m_durationBackward;
1528                 break;
1529             case Node::FixedInterval: {
1530                 //cs->lateFinish = m_constraintEndTime;
1531                 if (cs->lateFinish < m_constraintEndTime) {
1532                     cs->logWarning(i18nc("1=type of constraint", "%1: Failed to meet constraint", constraintToString(true)));
1533                 }
1534                 m_durationBackward = m_constraintEndTime - m_constraintStartTime;
1535                 if (cs->lateFinish > m_constraintEndTime) {
1536                     m_durationBackward = cs->lateFinish - m_constraintStartTime;
1537                 }
1538                 if (!cs->allowOverbooking()) {
1539                     cs->startTime = m_constraintStartTime;
1540                     cs->endTime = m_constraintEndTime;
1541                     makeAppointments();
1542                 }
1543                 cs->lateStart = cs->lateFinish - m_durationBackward;
1544                 break;
1545             }
1546         }
1547     } else if (type() == Node::Type_Milestone) {
1548         m_durationBackward = Duration::zeroDuration;
1549         switch (constraint()) {
1550             case Node::MustFinishOn:
1551                 //debugPlan<<"MustFinishOn:"<<m_constraintEndTime<<cs->lateFinish;
1552                 if (m_constraintEndTime < cs->lateFinish) {
1553                     m_durationBackward = cs->lateFinish - m_constraintEndTime;
1554                 } else if (m_constraintEndTime > cs->lateFinish) {
1555                     cs->logWarning(i18nc("1=type of constraint", "%1: Failed to meet constraint", constraintToString(true)));
1556                 }
1557                 cs->lateStart = cs->lateFinish - m_durationBackward;
1558                 break;
1559             case Node::FinishNotLater:
1560                 //debugPlan<<"FinishNotLater:"<<m_constraintEndTime<<cs->lateFinish;
1561                 if (m_constraintEndTime < cs->lateFinish) {
1562                     m_durationBackward = cs->lateFinish - m_constraintEndTime;
1563                 } else if (m_constraintEndTime > cs->lateFinish) {
1564                     cs->logWarning(i18nc("1=type of constraint", "%1: Failed to meet constraint", constraintToString(true)));
1565                 }
1566                 cs->lateStart = cs->lateFinish - m_durationBackward;
1567                 break;
1568             case Node::MustStartOn:
1569                 //debugPlan<<"MustStartOn:"<<m_constraintStartTime<<cs->lateFinish;
1570                 if (m_constraintStartTime < cs->lateFinish) {
1571                     m_durationBackward = cs->lateFinish - m_constraintStartTime;
1572                 } else if (m_constraintStartTime > cs->lateFinish) {
1573                     cs->logWarning(i18nc("1=type of constraint", "%1: Failed to meet constraint", constraintToString(true)));
1574                 }
1575                 cs->lateStart = cs->lateFinish - m_durationBackward;
1576                 //cs->logDebug(QString("%1: constraint:%2, start=%3, finish=%4").arg(constraintToString()).arg(m_constraintStartTime.toString()).arg(cs->lateStart.toString()).arg(cs->lateFinish.toString()));
1577                 break;
1578             case Node::StartNotEarlier:
1579                 //debugPlan<<"MustStartOn:"<<m_constraintStartTime<<cs->lateFinish;
1580                 if (m_constraintStartTime > cs->lateFinish) {
1581                     cs->logWarning(i18nc("1=type of constraint", "%1: Failed to meet constraint", constraintToString(true)));
1582                 }
1583                 cs->lateStart = cs->lateFinish;
1584                 break;
1585             case Node::FixedInterval:
1586                 cs->lateStart = cs->lateFinish - m_durationBackward;
1587                 break;
1588             default:
1589                 cs->lateStart = cs->lateFinish - m_durationBackward;
1590                 break;
1591         }
1592         //debugPlan<<m_name<<""<<cs->lateFinish;
1593     } else if (type() == Node::Type_Summarytask) {
1594         warnPlan<<"Summarytasks should not be calculated here: "<<m_name;
1595     } else { // ???
1596         m_durationBackward = Duration::zeroDuration;
1597     }
1598     m_visitedBackward = true;
1599     cs->insertBackwardNode(this);
1600     cs->lateStart = cs->lateFinish - m_durationBackward;
1601     foreach (const Appointment *a, cs->appointments(Schedule::CalculateBackward)) {
1602         cs->logInfo(i18n("Resource %1 booked from %2 to %3", a->resource()->resource()->name(), locale.toString(a->startTime(), QLocale::ShortFormat), locale.toString(a->endTime(), QLocale::ShortFormat)));
1603     }
1604     // clean up temporary usage
1605     cs->startTime = DateTime();
1606     cs->endTime = DateTime();
1607     cs->duration = Duration::zeroDuration;
1608     cs->logInfo(i18n("Late start calculated: %1", locale.toString(cs->lateStart, QLocale::ShortFormat)));
1609     cs->incProgress();
1610 #ifndef PLAN_NLOGDEBUG
1611     cs->logDebug(QStringLiteral("Finished calculate backward: %1 ms").arg(timer.elapsed()));
1612 #endif
1613     return cs->lateStart;
1614 }
1615 
schedulePredeccessors(const QList<Relation * > & list_,int use)1616 DateTime Task::schedulePredeccessors(const QList<Relation*> &list_, int use) {
1617     DateTime time;
1618     QMultiMap<int, Relation*> lst;
1619     for (Relation* r : list_) {
1620         lst.insert(-r->parent()->priority(), r);
1621     }
1622     const QList<Relation*> list = lst.values();
1623     foreach (Relation *r, list) {
1624         if (r->parent()->type() == Type_Summarytask) {
1625             //debugPlan<<"Skip summarytask:"<<r->parent()->name();
1626             continue; // skip summarytasks
1627         }
1628         // schedule the predecessors
1629         DateTime earliest = r->parent()->earlyStart();
1630         DateTime t = r->parent()->scheduleForward(earliest, use);
1631         switch (r->type()) {
1632             case Relation::StartStart:
1633                 // I can't start before my predesseccor
1634                 t = r->parent()->startTime() + r->lag();
1635                 break;
1636             case Relation::FinishFinish:
1637                 // I can't end before my predecessor, so
1638                 // I can't start before it's endtime - my duration
1639 #ifndef PLAN_NLOGDEBUG
1640                 m_currentSchedule->logDebug(QStringLiteral("FinishFinish: get duration to calculate earliest start"));
1641 #endif
1642                 t -= duration(t + r->lag(), use, true);
1643                 break;
1644             default:
1645                 t += r->lag();
1646                 break;
1647         }
1648         if (!time.isValid() || t > time)
1649             time = t;
1650     }
1651     //debugPlan<<time.toString()<<""<<m_name<<" schedulePredeccessors()";
1652     return time;
1653 }
1654 
scheduleForward(const DateTime & earliest,int use)1655 DateTime Task::scheduleForward(const DateTime &earliest, int use) {
1656     if (m_scheduleForwardRun) {
1657         return m_currentSchedule->endTime;
1658     }
1659     if (m_currentSchedule == 0) {
1660         return DateTime();
1661     }
1662     Schedule *cs = m_currentSchedule;
1663     //cs->logDebug(QString("Schedule forward (early start: %1)").arg(cs->earlyStart.toString()));
1664     cs->setCalculationMode(Schedule::Scheduling);
1665     DateTime startTime = earliest > cs->earlyStart ? earliest : cs->earlyStart;
1666     // First, calculate all my own predecessors
1667     DateTime time = schedulePredeccessors(dependParentNodes(), use);
1668     if (time > startTime) {
1669         startTime = time;
1670         //debugPlan<<m_name<<" new startime="<<cs->startTime;
1671     }
1672     // Then my parents
1673     time = schedulePredeccessors(m_parentProxyRelations, use);
1674     if (time > startTime) {
1675         startTime = time;
1676     }
1677     if (! m_visitedForward) {
1678         cs->startTime = startTime;
1679     }
1680     m_scheduleForwardRun = true;
1681     return scheduleFromStartTime(use);
1682 }
1683 
scheduleFromStartTime(int use)1684 DateTime Task::scheduleFromStartTime(int use) {
1685     //debugPlan<<m_name;
1686     if (m_currentSchedule == 0) {
1687         return DateTime();
1688     }
1689     Schedule *cs = m_currentSchedule;
1690     cs->setCalculationMode(Schedule::Scheduling);
1691     bool pert = cs->usePert();
1692     if (m_visitedForward) {
1693         return cs->endTime;
1694     }
1695     cs->notScheduled = false;
1696     if (!cs->startTime.isValid()) {
1697         //cs->logDebug(QString("Schedule from start time: no start time use early start: %1").arg(cs->earlyStart.toString()));
1698         cs->startTime = cs->earlyStart;
1699     }
1700     QTime timer;
1701     timer.start();
1702     cs->logInfo(i18n("Start schedule forward: %1 ", constraintToString(true)));
1703     QLocale locale;
1704     cs->logInfo(i18n("Schedule from start %1", locale.toString(cs->startTime, QLocale::ShortFormat)));
1705     //debugPlan<<m_name<<" startTime="<<cs->startTime;
1706     if(type() == Node::Type_Task) {
1707         if (cs->recalculate() && completion().isFinished()) {
1708             copySchedule();
1709             m_visitedForward = true;
1710             return cs->endTime;
1711         }
1712         cs->duration = m_estimate->value(use, pert);
1713         switch (m_constraint) {
1714         case Node::ASAP:
1715             // cs->startTime calculated above
1716             //debugPlan<<m_name<<"ASAP:"<<cs->startTime<<"earliest:"<<cs->earlyStart;
1717             if (false/*useCalculateForwardAppointments*/
1718                     && m_estimate->type() == Estimate::Type_Effort
1719                     && ! cs->allowOverbooking()
1720                     && cs->hasAppointments(Schedule::CalculateForward)
1721                 ) {
1722 #ifndef PLAN_NLOGDEBUG
1723                 cs->logDebug("ASAP: " + cs->startTime.toString() + " earliest: " + cs->earlyStart.toString());
1724 #endif
1725                 cs->copyAppointments(Schedule::CalculateForward, Schedule::Scheduling);
1726                 if (cs->recalculate() && completion().isStarted()) {
1727                     // copy start times + appointments from parent schedule
1728                     copyAppointments();
1729                 }
1730                 cs->startTime = cs->appointmentStartTime();
1731                 cs->endTime = cs->appointmentEndTime();
1732                 Q_ASSERT(cs->startTime.isValid());
1733                 Q_ASSERT(cs->endTime.isValid());
1734                 cs->duration = cs->endTime - cs->startTime;
1735                 if (cs->lateFinish > cs->endTime) {
1736                     cs->positiveFloat = workTimeBefore(cs->lateFinish) - cs->endTime;
1737                 } else {
1738                     cs->positiveFloat = Duration::zeroDuration;
1739                 }
1740                 cs->logInfo(i18n("Scheduled: %1 to %2", locale.toString(cs->startTime, QLocale::ShortFormat), locale.toString(cs->endTime, QLocale::ShortFormat)));
1741                 return cs->endTime;
1742             }
1743             cs->startTime = workTimeAfter(cs->startTime, cs);
1744 #ifndef PLAN_NLOGDEBUG
1745             cs->logDebug("ASAP: " + cs->startTime.toString() + " earliest: " + cs->earlyStart.toString());
1746 #endif
1747             cs->duration = duration(cs->startTime, use, false);
1748             cs->endTime = cs->startTime + cs->duration;
1749             makeAppointments();
1750             if (cs->recalculate() && completion().isStarted()) {
1751                 // copy start times + appointments from parent schedule
1752                 copyAppointments();
1753                 cs->duration = cs->endTime - cs->startTime;
1754             }
1755             if (cs->lateFinish > cs->endTime) {
1756                 cs->positiveFloat = workTimeBefore(cs->lateFinish) - cs->endTime;
1757             } else {
1758                 cs->positiveFloat = Duration::zeroDuration;
1759             }
1760             break;
1761         case Node::ALAP:
1762             // cs->startTime calculated above
1763             //debugPlan<<m_name<<"ALAP:"<<cs->startTime<<cs->endTime<<" latest="<<cs->lateFinish;
1764             cs->endTime = workTimeBefore(cs->lateFinish, cs);
1765             cs->duration = duration(cs->endTime, use, true);
1766             cs->startTime = cs->endTime - cs->duration;
1767             //debugPlan<<m_name<<" endTime="<<cs->endTime<<" latest="<<cs->lateFinish;
1768             makeAppointments();
1769             if (cs->plannedEffort() == 0 && cs->lateFinish < cs->earlyFinish) {
1770                 // the backward pass failed to calculate sane values, try to handle it
1771                 //TODO add an error indication
1772                 cs->logWarning(i18n("%1: Scheduling failed using late finish, trying early finish instead.", constraintToString()));
1773                 cs->endTime = workTimeBefore(cs->earlyFinish, cs);
1774                 cs->duration = duration(cs->endTime, use, true);
1775                 cs->startTime = cs->endTime - cs->duration;
1776                 makeAppointments();
1777             }
1778             if (cs->lateFinish > cs->endTime) {
1779                 cs->positiveFloat = workTimeBefore(cs->lateFinish) - cs->endTime;
1780             } else {
1781                 cs->positiveFloat = Duration::zeroDuration;
1782             }
1783             if (cs->recalculate() && completion().isStarted()) {
1784                 cs->earlyStart = cs->startTime = completion().startTime();
1785             }
1786             break;
1787         case Node::StartNotEarlier:
1788             // cs->startTime calculated above
1789             //debugPlan<<"StartNotEarlier:"<<m_constraintStartTime<<cs->startTime<<cs->lateStart;
1790             cs->startTime = workTimeAfter(qMax(cs->startTime, m_constraintStartTime), cs);
1791             cs->duration = duration(cs->startTime, use, false);
1792             cs->endTime = cs->startTime + cs->duration;
1793             makeAppointments();
1794             if (cs->recalculate() && completion().isStarted()) {
1795                 // copy start times + appointments from parent schedule
1796                 copyAppointments();
1797                 cs->duration = cs->endTime - cs->startTime;
1798             }
1799             if (cs->lateFinish > cs->endTime) {
1800                 cs->positiveFloat = workTimeBefore(cs->lateFinish) - cs->endTime;
1801             } else {
1802                 cs->positiveFloat = Duration::zeroDuration;
1803             }
1804             if (cs->startTime < m_constraintStartTime) {
1805                 cs->constraintError = true;
1806                 cs->negativeFloat = cs->startTime - m_constraintStartTime;
1807                 cs->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString(true), cs->negativeFloat.toString(Duration::Format_i18nHour)));
1808             }
1809             break;
1810         case Node::FinishNotLater:
1811             // cs->startTime calculated above
1812             //debugPlan<<"FinishNotLater:"<<m_constraintEndTime<<cs->startTime;
1813             cs->startTime = workTimeAfter(cs->startTime, cs);
1814             cs->duration = duration(cs->startTime, use, false);
1815             cs->endTime = cs->startTime + cs->duration;
1816             makeAppointments();
1817             if (cs->recalculate() && completion().isStarted()) {
1818                 // copy start times + appointments from parent schedule
1819                 copyAppointments();
1820                 cs->duration = cs->endTime - cs->startTime;
1821             }
1822             if (cs->lateFinish > cs->endTime) {
1823                 cs->positiveFloat = workTimeBefore(cs->lateFinish) - cs->endTime;
1824             } else {
1825                 cs->positiveFloat = Duration::zeroDuration;
1826             }
1827             if (cs->endTime > m_constraintEndTime) {
1828                 //warnPlan<<"cs->endTime > m_constraintEndTime";
1829                 cs->constraintError = true;
1830                 cs->negativeFloat = cs->endTime - m_constraintEndTime;
1831                 cs->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString(true), cs->negativeFloat.toString(Duration::Format_i18nHour)));
1832             }
1833             break;
1834         case Node::MustStartOn:
1835             // Always try to put it on time
1836             cs->startTime = workTimeAfter(m_constraintStartTime, cs);
1837             //debugPlan<<"MustStartOn="<<m_constraintStartTime<<"<"<<cs->startTime;
1838             cs->duration = duration(cs->startTime, use, false);
1839             cs->endTime = cs->startTime + cs->duration;
1840 #ifndef PLAN_NLOGDEBUG
1841             cs->logDebug(QStringLiteral("%1: Schedule from %2 to %3").arg(constraintToString()).arg(cs->startTime.toString()).arg(cs->endTime.toString()));
1842 #endif
1843             makeAppointments();
1844             if (cs->recalculate() && completion().isStarted()) {
1845                 // copy start times + appointments from parent schedule
1846                 copyAppointments();
1847                 cs->duration = cs->endTime - cs->startTime;
1848             }
1849             if (cs->lateFinish > cs->endTime) {
1850                 cs->positiveFloat = workTimeBefore(cs->lateFinish) - cs->endTime;
1851             } else {
1852                 cs->positiveFloat = Duration::zeroDuration;
1853             }
1854             if (m_constraintStartTime < cs->startTime) {
1855                 cs->constraintError = true;
1856                 cs->negativeFloat = cs->startTime - m_constraintStartTime;
1857                 cs->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString(true), cs->negativeFloat.toString(Duration::Format_i18nHour)));
1858             }
1859             break;
1860         case Node::MustFinishOn:
1861             // Just try to schedule on time
1862             cs->endTime = workTimeBefore(m_constraintEndTime, cs);
1863             cs->duration = duration(cs->endTime, use, true);
1864             cs->startTime = cs->endTime - cs->duration;
1865 
1866             //debugPlan<<"MustFinishOn:"<<m_constraintEndTime<<","<<cs->lateFinish<<":"<<cs->startTime<<cs->endTime;
1867             makeAppointments();
1868             if (cs->recalculate() && completion().isStarted()) {
1869                 // copy start times + appointments from parent schedule
1870                 copyAppointments();
1871                 cs->duration = cs->endTime - cs->startTime;
1872             }
1873             if (cs->lateFinish > cs->endTime) {
1874                 cs->positiveFloat = workTimeBefore(cs->lateFinish) - cs->endTime;
1875             } else {
1876                 cs->positiveFloat = Duration::zeroDuration;
1877             }
1878             if (cs->endTime != m_constraintEndTime) {
1879                 cs->constraintError = true;
1880                 cs->negativeFloat = cs->endTime - m_constraintEndTime;
1881                 cs->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString(true), cs->negativeFloat.toString(Duration::Format_i18nHour)));
1882             }
1883             break;
1884         case Node::FixedInterval: {
1885             // cs->startTime calculated above
1886             //debugPlan<<"FixedInterval="<<m_constraintStartTime<<""<<cs->startTime;
1887             cs->duration = m_constraintEndTime - m_constraintStartTime;
1888             if (m_constraintStartTime >= cs->earlyStart) {
1889                 cs->startTime = m_constraintStartTime;
1890                 cs->endTime = m_constraintEndTime;
1891             } else {
1892                 cs->startTime = cs->earlyStart;
1893                 cs->endTime = cs->startTime + cs->duration;
1894                 cs->constraintError = true;
1895             }
1896             if (m_constraintStartTime < cs->startTime) {
1897                 cs->constraintError = true;
1898                 cs->negativeFloat = cs->startTime - m_constraintStartTime;
1899                 cs->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString(true), cs->negativeFloat.toString(Duration::Format_i18nHour)));
1900             }
1901             if (cs->lateFinish > cs->endTime) {
1902                 cs->positiveFloat = workTimeBefore(cs->lateFinish) - cs->endTime;
1903             } else {
1904                 cs->positiveFloat = Duration::zeroDuration;
1905             }
1906             cs->workStartTime = m_constraintStartTime;
1907             cs->workEndTime = m_constraintEndTime;
1908             //debugPlan<<"FixedInterval="<<cs->startTime<<","<<cs->endTime;
1909             makeAppointments();
1910             break;
1911         }
1912         default:
1913             break;
1914         }
1915         if (m_estimate->type() == Estimate::Type_Effort) {
1916             // HACK scheduling may accept deviation less than 5 mins to improve performance
1917             cs->effortNotMet = (m_estimate->value(use, cs->usePert()) - cs->plannedEffort()) > (5 * 60000);
1918             if (cs->effortNotMet) {
1919                 cs->logError(i18n("Effort not met. Estimate: %1, planned: %2", estimate()->value(use, cs->usePert()).toHours(), cs->plannedEffort().toHours()));
1920             }
1921         }
1922     } else if (type() == Node::Type_Milestone) {
1923         if (cs->recalculate() && completion().isFinished()) {
1924             cs->startTime = completion().startTime();
1925             cs->endTime = completion().finishTime();
1926             m_visitedForward = true;
1927             return cs->endTime;
1928         }
1929         switch (m_constraint) {
1930         case Node::ASAP: {
1931             cs->endTime = cs->startTime;
1932             // TODO check, do we need to check successors earliestStart?
1933             cs->positiveFloat = cs->lateFinish - cs->endTime;
1934             break;
1935         }
1936         case Node::ALAP: {
1937             cs->startTime = qMax(cs->lateFinish, cs->earlyFinish);
1938             cs->endTime = cs->startTime;
1939             cs->positiveFloat = Duration::zeroDuration;
1940             break;
1941         }
1942         case Node::MustStartOn:
1943         case Node::MustFinishOn:
1944         case Node::FixedInterval: {
1945             //debugPlan<<"MustStartOn:"<<m_constraintStartTime<<cs->startTime;
1946             DateTime contime = m_constraint == Node::MustFinishOn ? m_constraintEndTime : m_constraintStartTime;
1947 #ifndef PLAN_NLOGDEBUG
1948             cs->logDebug(QStringLiteral("%1: constraint time=%2, start time=%3").arg(constraintToString()).arg(contime.toString()).arg(cs->startTime.toString()));
1949 #endif
1950             if (cs->startTime < contime) {
1951                 if (contime <= cs->lateFinish || contime <= cs->earlyFinish) {
1952                     cs->startTime = contime;
1953                 }
1954             }
1955             cs->negativeFloat = cs->startTime > contime ? cs->startTime - contime :  contime - cs->startTime;
1956             if (cs->negativeFloat != 0) {
1957                 cs->constraintError = true;
1958                 cs->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString(true), cs->negativeFloat.toString(Duration::Format_i18nHour)));
1959             }
1960             cs->endTime = cs->startTime;
1961             if (cs->negativeFloat == Duration::zeroDuration) {
1962                 cs->positiveFloat = cs->lateFinish - cs->endTime;
1963             }
1964             break;
1965         }
1966         case Node::StartNotEarlier:
1967             if (cs->startTime < m_constraintStartTime) {
1968                 if (m_constraintStartTime <= cs->lateFinish || m_constraintStartTime <= cs->earlyFinish) {
1969                     cs->startTime = m_constraintStartTime;
1970                 }
1971             }
1972             if (cs->startTime < m_constraintStartTime) {
1973                 cs->constraintError = true;
1974                 cs->negativeFloat = m_constraintStartTime - cs->startTime;
1975                 cs->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString(true), cs->negativeFloat.toString(Duration::Format_i18nHour)));
1976             }
1977             cs->endTime = cs->startTime;
1978             if (cs->negativeFloat == Duration::zeroDuration) {
1979                 cs->positiveFloat = cs->lateFinish - cs->endTime;
1980             }
1981             break;
1982         case Node::FinishNotLater:
1983             //debugPlan<<m_constraintEndTime<<cs->startTime;
1984             if (cs->startTime > m_constraintEndTime) {
1985                 cs->constraintError = true;
1986                 cs->negativeFloat = cs->startTime - m_constraintEndTime;
1987                 cs->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString(true), cs->negativeFloat.toString(Duration::Format_i18nHour)));
1988             }
1989             cs->endTime = cs->startTime;
1990             if (cs->negativeFloat == Duration::zeroDuration) {
1991                 cs->positiveFloat = cs->lateFinish - cs->endTime;
1992             }
1993             break;
1994         default:
1995             break;
1996         }
1997         cs->duration = Duration::zeroDuration;
1998         //debugPlan<<m_name<<":"<<cs->startTime<<","<<cs->endTime;
1999     } else if (type() == Node::Type_Summarytask) {
2000         //shouldn't come here
2001         cs->endTime = cs->startTime;
2002         cs->duration = cs->endTime - cs->startTime;
2003         warnPlan<<"Summarytasks should not be calculated here: "<<m_name;
2004     }
2005     //debugPlan<<cs->startTime<<" :"<<cs->endTime<<""<<m_name<<" scheduleForward()";
2006     if (cs->startTime < projectNode()->constraintStartTime() || cs->endTime > projectNode()->constraintEndTime()) {
2007         cs->logError(i18n("Failed to schedule within project target time"));
2008     }
2009     foreach (const Appointment *a, cs->appointments()) {
2010         cs->logInfo(i18n("Resource %1 booked from %2 to %3", a->resource()->resource()->name(), locale.toString(a->startTime(), QLocale::ShortFormat), locale.toString(a->endTime(), QLocale::ShortFormat)));
2011     }
2012     if (cs->startTime < cs->earlyStart) {
2013         cs->logWarning(i18n("Starting earlier than early start"));
2014     }
2015     if (cs->endTime > cs->lateFinish) {
2016         cs->logWarning(i18n("Finishing later than late finish"));
2017     }
2018     cs->logInfo(i18n("Scheduled: %1 to %2", locale.toString(cs->startTime, QLocale::ShortFormat), locale.toString(cs->endTime, QLocale::ShortFormat)));
2019     m_visitedForward = true;
2020     cs->incProgress();
2021     m_requests.resetDynamicAllocations();
2022     cs->logInfo(i18n("Finished schedule forward: %1 ms", timer.elapsed()));
2023     return cs->endTime;
2024 }
2025 
scheduleSuccessors(const QList<Relation * > & list_,int use)2026 DateTime Task::scheduleSuccessors(const QList<Relation*> &list_, int use) {
2027     DateTime time;
2028     QMultiMap<int, Relation*> lst;
2029     for (Relation* r : list_) {
2030         lst.insert(-r->child()->priority(), r);
2031     }
2032     const QList<Relation*> list = lst.values();
2033     foreach (Relation *r, list) {
2034         if (r->child()->type() == Type_Summarytask) {
2035             //debugPlan<<"Skip summarytask:"<<r->child()->name();
2036             continue;
2037         }
2038         // get the successors starttime
2039         DateTime latest = r->child()->lateFinish();
2040         DateTime t = r->child()->scheduleBackward(latest, use);
2041         switch (r->type()) {
2042             case Relation::StartStart:
2043                 // I can't start before my successor, so
2044                 // I can't finish later than it's starttime + my duration
2045 #ifndef PLAN_NLOGDEBUG
2046                 m_currentSchedule->logDebug(QStringLiteral("StartStart: get duration to calculate late finish"));
2047 #endif
2048                 t += duration(t - r->lag(), use, false);
2049                 break;
2050             case Relation::FinishFinish:
2051                 t = r->child()->endTime() - r->lag();
2052                 break;
2053             default:
2054                 t -= r->lag();
2055                 break;
2056         }
2057         if (!time.isValid() || t < time)
2058             time = t;
2059    }
2060    return time;
2061 }
2062 
scheduleBackward(const DateTime & latest,int use)2063 DateTime Task::scheduleBackward(const DateTime &latest, int use) {
2064     if (m_scheduleBackwardRun) {
2065         return m_currentSchedule->startTime;
2066     }
2067     if (m_currentSchedule == 0) {
2068         return DateTime();
2069     }
2070     Schedule *cs = m_currentSchedule;
2071     cs->setCalculationMode(Schedule::Scheduling);
2072 
2073     DateTime endTime = latest < cs->lateFinish ? latest : cs->lateFinish;
2074     // First, calculate all my own successors
2075     DateTime time = scheduleSuccessors(dependChildNodes(), use);
2076     if (time.isValid() && time < endTime) {
2077         endTime = time;
2078     }
2079     // Then my parents
2080     time = scheduleSuccessors(m_childProxyRelations, use);
2081     if (time.isValid() && time < endTime) {
2082         endTime = time;
2083     }
2084     if (! m_visitedBackward) {
2085         cs->endTime = endTime;
2086     }
2087     m_scheduleBackwardRun = true;
2088     return scheduleFromEndTime(use);
2089 }
2090 
scheduleFromEndTime(int use)2091 DateTime Task::scheduleFromEndTime(int use) {
2092     //debugPlan<<m_name;
2093     if (m_currentSchedule == 0) {
2094         return DateTime();
2095     }
2096     Schedule *cs = m_currentSchedule;
2097     cs->setCalculationMode(Schedule::Scheduling);
2098     bool pert = cs->usePert();
2099     if (m_visitedBackward) {
2100         return cs->startTime;
2101     }
2102     cs->notScheduled = false;
2103     if (!cs->endTime.isValid()) {
2104         cs->endTime = cs->lateFinish;
2105     }
2106 #ifndef PLAN_NLOGDEBUG
2107     QTime timer;
2108     timer.start();
2109     cs->logDebug(QStringLiteral("Start schedule backward: %1 ").arg(constraintToString(true)));
2110 #endif
2111     QLocale locale;
2112     cs->logInfo(i18n("Schedule from end time: %1", cs->endTime.toString()));
2113     if (type() == Node::Type_Task) {
2114         cs->duration = m_estimate->value(use, pert);
2115         switch (m_constraint) {
2116         case Node::ASAP: {
2117             // cs->endTime calculated above
2118             //debugPlan<<m_name<<": end="<<cs->endTime<<"  early="<<cs->earlyStart;
2119             //TODO: try to keep within projects constraint times
2120             cs->endTime = workTimeBefore(cs->endTime, cs);
2121             cs->startTime = workTimeAfter(cs->earlyStart, cs);
2122             DateTime e;
2123             if (cs->startTime < cs->endTime) {
2124                 cs->duration = duration(cs->startTime, use, false);
2125                 e = cs->startTime + cs->duration;
2126             } else {
2127 #ifndef PLAN_NLOGDEBUG
2128                 cs->logDebug(QStringLiteral("%1: Latest allowed end time earlier than early start").arg(constraintToString()));
2129 #endif
2130                 cs->duration = duration(cs->endTime, use, true);
2131                 e = cs->endTime;
2132                 cs->startTime = e - cs->duration;
2133             }
2134             if (e > cs->lateFinish) {
2135                 cs->schedulingError = true;
2136                 cs->logError(i18nc("1=type of constraint", "%1: Failed to schedule within late finish.", constraintToString()));
2137 #ifndef PLAN_NLOGDEBUG
2138                 cs->logDebug("ASAP: late finish=" + cs->lateFinish.toString() + " end time=" + e.toString());
2139 #endif
2140             } else if (e > cs->endTime) {
2141                 cs->schedulingError = true;
2142                 cs->logWarning(i18nc("1=type of constraint", "%1: Failed to schedule within successors start time",  constraintToString()));
2143 #ifndef PLAN_NLOGDEBUG
2144                 cs->logDebug("ASAP: succ. start=" + cs->endTime.toString() + " end time=" + e.toString());
2145 #endif
2146             }
2147             if (cs->lateFinish > e) {
2148                 DateTime w = workTimeBefore(cs->lateFinish);
2149                 if (w > e) {
2150                     cs->positiveFloat = w - e;
2151                 }
2152 #ifndef PLAN_NLOGDEBUG
2153                 cs->logDebug("ASAP: positiveFloat=" + cs->positiveFloat.toString());
2154 #endif
2155             }
2156             cs->endTime = e;
2157             makeAppointments();
2158             break;
2159         }
2160         case Node::ALAP:
2161         {
2162             // cs->endTime calculated above
2163             //debugPlan<<m_name<<": end="<<cs->endTime<<"  late="<<cs->lateFinish;
2164             cs->endTime = workTimeBefore(cs->endTime, cs);
2165             cs->duration = duration(cs->endTime, use, true);
2166             cs->startTime = cs->endTime - cs->duration;
2167             if (cs->startTime < cs->earlyStart) {
2168                 cs->schedulingError = true;
2169                 cs->logError(i18nc("1=type of constraint", "%1: Failed to schedule after early start.", constraintToString()));
2170 #ifndef PLAN_NLOGDEBUG
2171                 cs->logDebug("ALAP: earlyStart=" + cs->earlyStart.toString() + " cs->startTime=" + cs->startTime.toString());
2172 #endif
2173             } else if (cs->lateFinish > cs->endTime) {
2174                 cs->positiveFloat = workTimeBefore(cs->lateFinish) - cs->endTime;
2175 #ifndef PLAN_NLOGDEBUG
2176                 cs->logDebug("ALAP: positiveFloat=" + cs->positiveFloat.toString());
2177 #endif
2178             }
2179             //debugPlan<<m_name<<": lateStart="<<cs->startTime;
2180             makeAppointments();
2181             break;
2182         }
2183         case Node::StartNotEarlier:
2184             // cs->endTime calculated above
2185             //debugPlan<<"StartNotEarlier:"<<m_constraintStartTime<<cs->endTime;
2186             cs->endTime = workTimeBefore(cs->endTime, cs);
2187             cs->duration = duration(cs->endTime, use, true);
2188             cs->startTime = cs->endTime - cs->duration;
2189             if (cs->startTime < m_constraintStartTime) {
2190                 //warnPlan<<"m_constraintStartTime > cs->lateStart";
2191                 cs->constraintError = true;
2192                 cs->negativeFloat = m_constraintStartTime - cs->startTime;
2193                 cs->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString(true), cs->negativeFloat.toString(Duration::Format_i18nHour)));
2194             }
2195             makeAppointments();
2196             if (cs->lateFinish > cs->endTime) {
2197                 cs->positiveFloat = workTimeBefore(cs->lateFinish) - cs->endTime;
2198             }
2199             break;
2200         case Node::FinishNotLater:
2201             // cs->endTime calculated above
2202             //debugPlan<<"FinishNotLater:"<<m_constraintEndTime<<cs->endTime;
2203             if (cs->endTime > m_constraintEndTime) {
2204                 cs->endTime = qMax(qMin(m_constraintEndTime, cs->lateFinish), cs->earlyFinish);
2205             }
2206             cs->endTime = workTimeBefore(cs->endTime, cs);
2207             cs->duration = duration(cs->endTime, use, true);
2208             cs->startTime = cs->endTime - cs->duration;
2209             if (cs->endTime > m_constraintEndTime) {
2210                 cs->negativeFloat = cs->endTime - m_constraintEndTime;
2211                 cs->constraintError = true;
2212                 cs->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString(true), cs->negativeFloat.toString(Duration::Format_i18nHour)));
2213             }
2214             makeAppointments();
2215             if (cs->lateFinish > cs->endTime) {
2216                 cs->positiveFloat = workTimeBefore(cs->lateFinish) - cs->endTime;
2217             }
2218             break;
2219         case Node::MustStartOn:
2220             // Just try to schedule on time
2221             //debugPlan<<"MustStartOn="<<m_constraintStartTime.toString()<<""<<cs->startTime.toString();
2222             cs->startTime = workTimeAfter(m_constraintStartTime, cs);
2223             cs->duration = duration(cs->startTime, use, false);
2224             if (cs->endTime >= cs->startTime + cs->duration) {
2225                 cs->endTime = cs->startTime + cs->duration;
2226             } else {
2227                 cs->endTime = workTimeBefore(cs->endTime);
2228                 cs->duration = duration(cs->endTime, use, true);
2229                 cs->startTime = cs->endTime - cs->duration;
2230             }
2231             if (m_constraintStartTime != cs->startTime) {
2232                 cs->constraintError = true;
2233                 cs->negativeFloat = m_constraintStartTime - cs->startTime;
2234                 cs->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString(true), cs->negativeFloat.toString(Duration::Format_i18nHour)));
2235             }
2236             makeAppointments();
2237             if (cs->lateFinish > cs->endTime) {
2238                 cs->positiveFloat = workTimeBefore(cs->lateFinish) - cs->endTime;
2239             }
2240             break;
2241         case Node::MustFinishOn:
2242             // Just try to schedule on time
2243             //debugPlan<<m_name<<"MustFinishOn:"<<m_constraintEndTime<<cs->endTime<<cs->earlyFinish;
2244             cs->endTime = workTimeBefore(m_constraintEndTime, cs);
2245             cs->duration = duration(cs->endTime, use, true);
2246             cs->startTime = cs->endTime - cs->duration;
2247             if (m_constraintEndTime != cs->endTime) {
2248                 cs->negativeFloat = m_constraintEndTime - cs->endTime;
2249                 cs->constraintError = true;
2250                 cs->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString(true), cs->negativeFloat.toString(Duration::Format_i18nHour)));
2251                 //warnPlan<<"m_constraintEndTime > cs->endTime";
2252             }
2253             makeAppointments();
2254             if (cs->lateFinish > cs->endTime) {
2255                 cs->positiveFloat = workTimeBefore(cs->lateFinish) - cs->endTime;
2256             }
2257             break;
2258         case Node::FixedInterval: {
2259             // cs->endTime calculated above
2260             //debugPlan<<m_name<<"FixedInterval="<<m_constraintEndTime<<""<<cs->endTime;
2261             cs->duration = m_constraintEndTime - m_constraintStartTime;
2262             if (cs->endTime > m_constraintEndTime) {
2263                 cs->endTime = qMax(m_constraintEndTime, cs->earlyFinish);
2264             }
2265             cs->startTime = cs->endTime - cs->duration;
2266             if (m_constraintEndTime != cs->endTime) {
2267                 cs->negativeFloat = m_constraintEndTime - cs->endTime;
2268                 cs->constraintError = true;
2269                 cs->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString(true), cs->negativeFloat.toString(Duration::Format_i18nHour)));
2270             }
2271             cs->workStartTime = workTimeAfter(cs->startTime);
2272             cs->workEndTime = workTimeBefore(cs->endTime);
2273             makeAppointments();
2274             if (cs->negativeFloat == Duration::zeroDuration) {
2275                 cs->positiveFloat = workTimeBefore(cs->lateFinish) - cs->endTime;
2276             }
2277             break;
2278         }
2279         default:
2280             break;
2281         }
2282         m_requests.reserve(cs->startTime, cs->duration);
2283         if (m_estimate->type() == Estimate::Type_Effort) {
2284             // HACK scheduling may accept deviation less than 5 mins to improve performance
2285             cs->effortNotMet = (m_estimate->value(use, cs->usePert()) - cs->plannedEffort()) > (5 * 60000);
2286             if (cs->effortNotMet) {
2287                 cs->logError(i18n("Effort not met. Estimate: %1, planned: %2", estimate()->value(use, cs->usePert()).toHours(), cs->plannedEffort().toHours()));
2288             }
2289         }
2290     } else if (type() == Node::Type_Milestone) {
2291         switch (m_constraint) {
2292         case Node::ASAP:
2293             if (cs->endTime < cs->earlyStart) {
2294                 cs->schedulingError = true;
2295                 cs->logError(i18nc("1=type of constraint", "%1: Failed to schedule after early start.", constraintToString()));
2296                 cs->endTime = cs->earlyStart;
2297             } else {
2298                 cs->positiveFloat = cs->lateFinish - cs->endTime;
2299             }
2300             //cs->endTime = cs->earlyStart; FIXME need to follow predeccessors. Defer scheduling?
2301             cs->startTime = cs->endTime;
2302             break;
2303         case Node::ALAP:
2304             cs->startTime = cs->endTime;
2305             cs->positiveFloat = cs->lateFinish - cs->endTime;
2306             break;
2307         case Node::MustStartOn:
2308         case Node::MustFinishOn:
2309         case Node::FixedInterval: {
2310             DateTime contime = m_constraint == Node::MustFinishOn ? m_constraintEndTime : m_constraintStartTime;
2311             if (contime < cs->earlyStart) {
2312                 if (cs->earlyStart < cs->endTime) {
2313                     cs->endTime = cs->earlyStart;
2314                 }
2315             } else if (contime < cs->endTime) {
2316                 cs->endTime = contime;
2317             }
2318             cs->negativeFloat = cs->endTime > contime ? cs->endTime - contime : contime - cs->endTime;
2319             if (cs->negativeFloat != 0) {
2320                 cs->constraintError = true;
2321                 cs->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString(true), cs->negativeFloat.toString(Duration::Format_i18nHour)));
2322             }
2323             cs->startTime = cs->endTime;
2324             if (cs->negativeFloat == Duration::zeroDuration) {
2325                 cs->positiveFloat = cs->lateFinish - cs->endTime;
2326             }
2327             break;
2328         }
2329         case Node::StartNotEarlier:
2330             cs->startTime = cs->endTime;
2331             if (m_constraintStartTime > cs->startTime) {
2332                 cs->constraintError = true;
2333                 cs->negativeFloat = m_constraintStartTime - cs->startTime;
2334                 cs->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString(true), cs->negativeFloat.toString(Duration::Format_i18nHour)));
2335             }
2336             if (cs->negativeFloat == Duration::zeroDuration) {
2337                 cs->positiveFloat = cs->lateFinish - cs->endTime;
2338             }
2339             break;
2340         case Node::FinishNotLater:
2341             if (m_constraintEndTime < cs->earlyStart) {
2342                 if (cs->earlyStart < cs->endTime) {
2343                     cs->endTime = cs->earlyStart;
2344                 }
2345             } else if (m_constraintEndTime < cs->endTime) {
2346                 cs->endTime = m_constraintEndTime;
2347             }
2348             if (m_constraintEndTime > cs->endTime) {
2349                 cs->constraintError = true;
2350                 cs->negativeFloat = cs->endTime - m_constraintEndTime;
2351                 cs->logError(i18nc("1=type of constraint", "%1: Failed to meet constraint. Negative float=%2", constraintToString(true), cs->negativeFloat.toString(Duration::Format_i18nHour)));
2352             }
2353             cs->startTime = cs->endTime;
2354             if (cs->negativeFloat == Duration::zeroDuration) {
2355                 cs->positiveFloat = cs->lateFinish - cs->endTime;
2356             }
2357             break;
2358         default:
2359             break;
2360         }
2361         cs->duration = Duration::zeroDuration;
2362     } else if (type() == Node::Type_Summarytask) {
2363         //shouldn't come here
2364         cs->startTime = cs->endTime;
2365         cs->duration = cs->endTime - cs->startTime;
2366         warnPlan<<"Summarytasks should not be calculated here: "<<m_name;
2367     }
2368     if (cs->startTime < projectNode()->constraintStartTime() || cs->endTime > projectNode()->constraintEndTime()) {
2369         cs->logError(i18n("Failed to schedule within project target time"));
2370     }
2371     foreach (const Appointment *a, cs->appointments()) {
2372         cs->logInfo(i18n("Resource %1 booked from %2 to %3", a->resource()->resource()->name(), locale.toString(a->startTime(), QLocale::ShortFormat), locale.toString(a->endTime(), QLocale::ShortFormat)));
2373     }
2374     if (cs->startTime < cs->earlyStart) {
2375         cs->logWarning(i18n("Starting earlier than early start"));
2376     }
2377     if (cs->endTime > cs->lateFinish) {
2378         cs->logWarning(i18n("Finishing later than late finish"));
2379     }
2380     cs->logInfo(i18n("Scheduled: %1 to %2", locale.toString(cs->startTime, QLocale::ShortFormat), locale.toString(cs->endTime, QLocale::ShortFormat)));
2381     m_visitedBackward = true;
2382     cs->incProgress();
2383     m_requests.resetDynamicAllocations();
2384 #ifndef PLAN_NLOGDEBUG
2385     cs->logDebug(QStringLiteral("Finished schedule backward: %1 ms").arg(timer.elapsed()));
2386 #endif
2387     return cs->startTime;
2388 }
2389 
adjustSummarytask()2390 void Task::adjustSummarytask() {
2391     if (m_currentSchedule == 0)
2392         return;
2393     if (type() == Type_Summarytask) {
2394         DateTime start = m_currentSchedule->lateFinish;
2395         DateTime end = m_currentSchedule->earlyStart;
2396         foreach (Node *n, m_nodes) {
2397             n->adjustSummarytask();
2398             if (n->startTime() < start)
2399                 start = n->startTime();
2400             if (n->endTime() > end)
2401                 end = n->endTime();
2402         }
2403         m_currentSchedule->startTime = start;
2404         m_currentSchedule->endTime = end;
2405         m_currentSchedule->duration = end - start;
2406         m_currentSchedule->notScheduled = false;
2407         //debugPlan<<cs->name<<":"<<m_currentSchedule->startTime.toString()<<" :"<<m_currentSchedule->endTime.toString();
2408     }
2409 }
2410 
duration(const DateTime & time,int use,bool backward)2411 Duration Task::duration(const DateTime &time, int use, bool backward) {
2412     //debugPlan;
2413     // TODO: handle risc
2414     if (m_currentSchedule == 0) {
2415         errorPlan<<"No current schedule";
2416         return Duration::zeroDuration;
2417     }
2418     if (!time.isValid()) {
2419 #ifndef PLAN_NLOGDEBUG
2420         m_currentSchedule->logDebug(QStringLiteral("Calculate duration: Start time is not valid"));
2421 #endif
2422         return Duration::zeroDuration;
2423     }
2424     //debugPlan<<m_name<<": Use="<<use;
2425     Duration eff;
2426     if (m_currentSchedule->recalculate() && completion().isStarted()) {
2427         eff = completion().remainingEffort();
2428         //debugPlan<<m_name<<": recalculate, effort="<<eff.toDouble(Duration::Unit_h);
2429         if (eff == 0 || completion().isFinished()) {
2430             return eff;
2431         }
2432     } else {
2433         eff = m_estimate->value(use, m_currentSchedule->usePert());
2434     }
2435     return calcDuration(time, eff, backward);
2436 }
2437 
2438 
calcDuration(const DateTime & time,KPlato::Duration effort,bool backward)2439 Duration Task::calcDuration(const DateTime &time, KPlato::Duration effort, bool backward) {
2440     //debugPlan<<"--------> calcDuration"<<(backward?"(B)":"(F)")<<m_name<<" time="<<time<<" effort="<<effort.toString(Duration::Format_Day);
2441 
2442     // Already checked: m_currentSchedule and time.
2443     Duration dur = effort; // use effort as default duration
2444     if (m_estimate->type() == Estimate::Type_Effort) {
2445         if (m_requests.isEmpty()) {
2446             m_currentSchedule->resourceError = true;
2447             m_currentSchedule->logError(i18n("No resource has been allocated"));
2448             return effort;
2449         }
2450         dur = m_requests.duration(time, effort, m_currentSchedule, backward);
2451         if (dur == Duration::zeroDuration) {
2452             warnPlan<<"zero duration: Resource not available";
2453             m_currentSchedule->resourceNotAvailable = true;
2454             dur = effort; //???
2455         }
2456         return dur;
2457     }
2458     if (m_estimate->type() == Estimate::Type_Duration) {
2459         return length(time, dur, backward);
2460     }
2461     errorPlan<<"Unsupported estimate type: "<<m_estimate->type();
2462     return dur;
2463 }
2464 
length(const DateTime & time,KPlato::Duration duration,bool backward)2465 Duration Task::length(const DateTime &time, KPlato::Duration duration, bool backward)
2466 {
2467     return length(time, duration, m_currentSchedule, backward);
2468 }
2469 
length(const DateTime & time,KPlato::Duration duration,Schedule * sch,bool backward)2470 Duration Task::length(const DateTime &time, KPlato::Duration duration, Schedule *sch, bool backward) {
2471     //debugPlan<<"--->"<<(backward?"(B)":"(F)")<<m_name<<""<<time.toString()<<": duration:"<<duration.toString(Duration::Format_Day)<<" ("<<duration.milliseconds()<<")";
2472 
2473     Duration l;
2474     if (duration == Duration::zeroDuration) {
2475 #ifndef PLAN_NLOGDEBUG
2476         if (sch) sch->logDebug(QStringLiteral("Calculate length: estimate == 0"));
2477 #else
2478         Q_UNUSED(sch)
2479 #endif
2480         return l;
2481     }
2482     Calendar *cal = m_estimate->calendar();
2483     if (cal == 0) {
2484 #ifndef PLAN_NLOGDEBUG
2485         if (sch) sch->logDebug("Calculate length: No calendar, return estimate " + duration.toString());
2486 #endif
2487         return duration;
2488     }
2489 #ifndef PLAN_NLOGDEBUG
2490     if (sch) sch->logDebug("Calculate length from: " + time.toString());
2491 #endif
2492     DateTime logtime = time;
2493     bool sts=true;
2494     bool match = false;
2495     DateTime start = time;
2496     int inc = backward ? -1 : 1;
2497     DateTime end = start;
2498     Duration l1;
2499     int nDays = backward ? projectNode()->constraintStartTime().daysTo(time) : time.daysTo(projectNode()->constraintEndTime());
2500     for (int i=0; !match && i <= nDays; ++i) {
2501         // days
2502         end = end.addDays(inc);
2503         l1 = backward ? cal->effort(end, start) : cal->effort(start, end);
2504         //debugPlan<<"["<<i<<"of"<<nDays<<"]"<<(backward?"(B)":"(F):")<<"  start="<<start<<" l+l1="<<(l+l1).toString()<<" match"<<duration.toString();
2505         if (l + l1 < duration) {
2506             l += l1;
2507             start = end;
2508         } else if (l + l1 == duration) {
2509             l += l1;
2510             match = true;
2511         } else {
2512             end = start;
2513             break;
2514         }
2515     }
2516     if (! match) {
2517 #ifndef PLAN_NLOGDEBUG
2518         if (sch) sch->logDebug("Days: duration " + logtime.toString() + " - " + end.toString() + " = " + l.toString() + " (" + (duration - l).toString() + ')');
2519 #endif
2520         logtime = start;
2521         for (int i=0; !match && i < 24; ++i) {
2522             // hours
2523             end = end.addSecs(inc*60*60);
2524             l1 = backward ? cal->effort(end, start) : cal->effort(start, end);
2525             if (l + l1 < duration) {
2526                 l += l1;
2527                 start = end;
2528             } else if (l + l1 == duration) {
2529                 l += l1;
2530                 match = true;
2531             } else {
2532                 end = start;
2533                 break;
2534             }
2535             //debugPlan<<"duration(h)["<<i<<"]"<<(backward?"backward":"forward:")<<" time="<<start.time()<<" l="<<l.toString()<<" ("<<l.milliseconds()<<')';
2536         }
2537         //debugPlan<<"duration"<<(backward?"backward":"forward:")<<start.toString()<<" l="<<l.toString()<<" ("<<l.milliseconds()<<")  match="<<match<<" sts="<<sts;
2538     }
2539     if (! match) {
2540 #ifndef PLAN_NLOGDEBUG
2541         if (sch) sch->logDebug("Hours: duration " + logtime.toString() + " - " + end.toString() + " = " + l.toString() + " (" + (duration - l).toString() + ')');
2542 #endif
2543         logtime = start;
2544         for (int i=0; !match && i < 60; ++i) {
2545             //minutes
2546             end = end.addSecs(inc*60);
2547             l1 = backward ? cal->effort(end, start) : cal->effort(start, end);
2548             if (l + l1 < duration) {
2549                 l += l1;
2550                 start = end;
2551             } else if (l + l1 == duration) {
2552                 l += l1;
2553                 match = true;
2554             } else if (l + l1 > duration) {
2555                 end = start;
2556                 break;
2557             }
2558             //debugPlan<<"duration(m)"<<(backward?"backward":"forward:")<<"  time="<<start.time().toString()<<" l="<<l.toString()<<" ("<<l.milliseconds()<<')';
2559         }
2560         //debugPlan<<"duration"<<(backward?"backward":"forward:")<<"  start="<<start.toString()<<" l="<<l.toString()<<" match="<<match<<" sts="<<sts;
2561     }
2562     if (! match) {
2563 #ifndef PLAN_NLOGDEBUG
2564         if (sch) sch->logDebug("Minutes: duration " + logtime.toString() + " - " + end.toString() + " = " + l.toString() + " (" + (duration - l).toString() + ')');
2565 #endif
2566         logtime = start;
2567         for (int i=0; !match && i < 60 && sts; ++i) {
2568             //seconds
2569             end = end.addSecs(inc);
2570             l1 = backward ? cal->effort(end, start) : cal->effort(start, end);
2571             if (l + l1 < duration) {
2572                 l += l1;
2573                 start = end;
2574             } else if (l + l1 == duration) {
2575                 l += l1;
2576                 match = true;
2577             } else if (l + l1 > duration) {
2578                 end = start;
2579                 break;
2580             }
2581             //debugPlan<<"duration(s)["<<i<<"]"<<(backward?"backward":"forward:")<<" time="<<start.time().toString()<<" l="<<l.toString()<<" ("<<l.milliseconds()<<')';
2582         }
2583     }
2584     if (! match) {
2585 #ifndef PLAN_NLOGDEBUG
2586         if (sch) sch->logDebug("Seconds: duration " + logtime.toString() + " - " + end.toString() + " l " + l.toString() + " (" + (duration - l).toString() + ')');
2587 #endif
2588         for (int i=0; !match && i < 1000; ++i) {
2589             //milliseconds
2590             end.setTime(end.time().addMSecs(inc));
2591             l1 = backward ? cal->effort(end, start) : cal->effort(start, end);
2592             if (l + l1 < duration) {
2593                 l += l1;
2594                 start = end;
2595             } else if (l + l1 == duration) {
2596                 l += l1;
2597                 match = true;
2598             } else {
2599 #ifndef PLAN_NLOGDEBUG
2600                 if (sch) sch->logDebug("Got more than asked for, should not happen! Want: " + duration.toString(Duration::Format_Hour) + " got: " + l.toString(Duration::Format_Hour));
2601 #endif
2602                 break;
2603             }
2604             //debugPlan<<"duration(ms)["<<i<<"]"<<(backward?"backward":"forward:")<<" time="<<start.time().toString()<<" l="<<l.toString()<<" ("<<l.milliseconds()<<')';
2605         }
2606     }
2607     if (!match) {
2608         m_currentSchedule->logError(i18n("Could not match work duration. Want: %1 got: %2",  l.toString(Duration::Format_i18nHour), duration.toString(Duration::Format_i18nHour)));
2609     }
2610     DateTime t = end;
2611     if (l != Duration::zeroDuration) {
2612         if (backward) {
2613             if (end < projectNode()->constraintEndTime()) {
2614                 t = cal->firstAvailableAfter(end, projectNode()->constraintEndTime());
2615             }
2616         } else {
2617             if (end > projectNode()->constraintStartTime()) {
2618                 t = cal->firstAvailableBefore(end, projectNode()->constraintStartTime());
2619             }
2620         }
2621 #ifndef PLAN_NLOGDEBUG
2622         if (sch) sch->logDebug("Moved end to work: " + end.toString() + " -> " + t.toString());
2623 #endif
2624     }
2625     end = t.isValid() ? t : time;
2626     //debugPlan<<"<---"<<(backward?"(B)":"(F)")<<m_name<<":"<<end.toString()<<"-"<<time.toString()<<"="<<(end - time).toString()<<" duration:"<<duration.toString(Duration::Format_Day);
2627     l = end>time ? end-time : time-end;
2628     if (match) {
2629 #ifndef PLAN_NLOGDEBUG
2630         if (sch) sch->logDebug("Calculated length: " + time.toString() + " - " + end.toString() + " = " + l.toString());
2631 #endif
2632     }
2633     return l;
2634 }
2635 
clearProxyRelations()2636 void Task::clearProxyRelations() {
2637     m_parentProxyRelations.clear();
2638     m_childProxyRelations.clear();
2639 }
2640 
addParentProxyRelations(const QList<Relation * > & list)2641 void Task::addParentProxyRelations(const QList<Relation*> &list)
2642 {
2643     //debugPlan<<m_name;
2644     if (type() == Type_Summarytask) {
2645         // propagate to my children
2646         //debugPlan<<m_name<<" is summary task";
2647         foreach (Node *n, m_nodes) {
2648             n->addParentProxyRelations(list);
2649             n->addParentProxyRelations(dependParentNodes());
2650         }
2651     } else {
2652         // add 'this' as child relation to the relations parent
2653         //debugPlan<<m_name<<" is not summary task";
2654         foreach (Relation *r, list) {
2655             r->parent()->addChildProxyRelation(this, r);
2656             // add a parent relation to myself
2657             addParentProxyRelation(r->parent(), r);
2658         }
2659     }
2660 }
2661 
addChildProxyRelations(const QList<Relation * > & list)2662 void Task::addChildProxyRelations(const QList<Relation*> &list) {
2663     //debugPlan<<m_name;
2664     if (type() == Type_Summarytask) {
2665         // propagate to my children
2666         //debugPlan<<m_name<<" is summary task";
2667         foreach (Node *n, m_nodes) {
2668             n->addChildProxyRelations(list);
2669             n->addChildProxyRelations(dependChildNodes());
2670         }
2671     } else {
2672         // add 'this' as parent relation to the relations child
2673         //debugPlan<<m_name<<" is not summary task";
2674         foreach (Relation *r, list) {
2675             r->child()->addParentProxyRelation(this, r);
2676             // add a child relation to myself
2677             addChildProxyRelation(r->child(), r);
2678         }
2679     }
2680 }
2681 
addParentProxyRelation(Node * node,const Relation * rel)2682 void Task::addParentProxyRelation(Node *node, const Relation *rel) {
2683     if (node->type() != Type_Summarytask) {
2684         if (type() == Type_Summarytask) {
2685             //debugPlan<<"Add parent proxy from my children"<<m_name<<" to"<<node->name();
2686             foreach (Node *n, m_nodes) {
2687                 n->addParentProxyRelation(node, rel);
2688             }
2689         } else {
2690             //debugPlan<<"Add parent proxy from"<<node->name()<<" to (me)"<<m_name;
2691             m_parentProxyRelations.append(new ProxyRelation(node, this, rel->type(), rel->lag()));
2692         }
2693     }
2694 }
2695 
addChildProxyRelation(Node * node,const Relation * rel)2696 void Task::addChildProxyRelation(Node *node, const Relation *rel) {
2697     if (node->type() != Type_Summarytask) {
2698         if (type() == Type_Summarytask) {
2699             //debugPlan<<"Add child proxy from my children"<<m_name<<" to"<<node->name();
2700             foreach (Node *n, m_nodes) {
2701                 n->addChildProxyRelation(node, rel);
2702             }
2703         } else {
2704             //debugPlan<<"Add child proxy from (me)"<<m_name<<" to"<<node->name();
2705             m_childProxyRelations.append(new ProxyRelation(this, node, rel->type(), rel->lag()));
2706         }
2707     }
2708 }
2709 
isEndNode() const2710 bool Task::isEndNode() const
2711 {
2712     return m_dependChildNodes.isEmpty() && m_childProxyRelations.isEmpty();
2713 }
isStartNode() const2714 bool Task::isStartNode() const
2715 {
2716     return m_dependParentNodes.isEmpty() && m_parentProxyRelations.isEmpty();
2717 }
2718 
workTimeAfter(const DateTime & dt,Schedule * sch) const2719 DateTime Task::workTimeAfter(const DateTime &dt, Schedule *sch) const {
2720     DateTime t;
2721     if (m_estimate->type() == Estimate::Type_Duration) {
2722         if (m_estimate->calendar()) {
2723             t = m_estimate->calendar()->firstAvailableAfter(dt, projectNode()->constraintEndTime());
2724         }
2725     } else {
2726         t = m_requests.workTimeAfter(dt, sch);
2727 #ifndef PLAN_NLOGDEBUG
2728         if (sch) sch->logDebug(QStringLiteral("workTimeAfter: %1 = %2").arg(dt.toString()).arg(t.toString()));
2729 #endif
2730     }
2731     return t.isValid() ? t : dt;
2732 }
2733 
workTimeBefore(const DateTime & dt,Schedule * sch) const2734 DateTime Task::workTimeBefore(const DateTime &dt, Schedule *sch) const {
2735     DateTime t;
2736     if (m_estimate->type() == Estimate::Type_Duration) {
2737         if (m_estimate->calendar()) {
2738             t = m_estimate->calendar()->firstAvailableBefore(dt, projectNode()->constraintStartTime());
2739         }
2740     } else {
2741         t = m_requests.workTimeBefore(dt, sch);
2742     }
2743     return t.isValid() ? t : dt;
2744 }
2745 
positiveFloat(long id) const2746 Duration Task::positiveFloat(long id) const
2747 {
2748     Schedule *s = schedule(id);
2749     return s == 0 ? Duration::zeroDuration : s->positiveFloat;
2750 }
2751 
setPositiveFloat(KPlato::Duration fl,long id) const2752 void Task::setPositiveFloat(KPlato::Duration fl, long id) const
2753 {
2754     Schedule *s = schedule(id);
2755     if (s)
2756         s->positiveFloat = fl;
2757 }
2758 
negativeFloat(long id) const2759 Duration Task::negativeFloat(long id) const
2760 {
2761     Schedule *s = schedule(id);
2762     return s == 0 ? Duration::zeroDuration : s->negativeFloat;
2763 }
2764 
setNegativeFloat(KPlato::Duration fl,long id) const2765 void Task::setNegativeFloat(KPlato::Duration fl, long id) const
2766 {
2767     Schedule *s = schedule(id);
2768     if (s)
2769         s->negativeFloat = fl;
2770 }
2771 
freeFloat(long id) const2772 Duration Task::freeFloat(long id) const
2773 {
2774     Schedule *s = schedule(id);
2775     return s == 0 ? Duration::zeroDuration : s->freeFloat;
2776 }
2777 
setFreeFloat(KPlato::Duration fl,long id) const2778 void Task::setFreeFloat(KPlato::Duration fl, long id) const
2779 {
2780     Schedule *s = schedule(id);
2781     if (s)
2782         s->freeFloat = fl;
2783 }
2784 
startFloat(long id) const2785 Duration Task::startFloat(long id) const
2786 {
2787     Schedule *s = schedule(id);
2788     return s == 0 || s->earlyStart > s->lateStart ? Duration::zeroDuration : (s->earlyStart - s->lateStart);
2789 }
2790 
finishFloat(long id) const2791 Duration Task::finishFloat(long id) const
2792 {
2793     Schedule *s = schedule(id);
2794     return s == 0 || s->lateFinish < s->earlyFinish ? Duration::zeroDuration : (s->lateFinish - s->earlyFinish);
2795 }
2796 
isCritical(long id) const2797 bool Task::isCritical(long id) const
2798 {
2799     Schedule *s = schedule(id);
2800     return s == 0 ? false : s->isCritical();
2801 }
2802 
calcCriticalPath(bool fromEnd)2803 bool Task::calcCriticalPath(bool fromEnd)
2804 {
2805     if (m_currentSchedule == 0)
2806         return false;
2807     //debugPlan<<m_name<<" fromEnd="<<fromEnd<<" cp="<<m_currentSchedule->inCriticalPath;
2808     if (m_currentSchedule->inCriticalPath) {
2809         return true; // path already calculated
2810     }
2811     if (!isCritical()) {
2812         return false;
2813     }
2814     if (fromEnd) {
2815         if (isEndNode() && startFloat() == 0 && finishFloat() == 0) {
2816             m_currentSchedule->inCriticalPath = true;
2817             //debugPlan<<m_name<<" end node";
2818             return true;
2819         }
2820         foreach (Relation *r, m_childProxyRelations) {
2821             if (r->child()->calcCriticalPath(fromEnd)) {
2822                 m_currentSchedule->inCriticalPath = true;
2823             }
2824         }
2825         foreach (Relation *r, m_dependChildNodes) {
2826             if (r->child()->calcCriticalPath(fromEnd)) {
2827                 m_currentSchedule->inCriticalPath = true;
2828             }
2829         }
2830     } else {
2831         if (isStartNode() && startFloat() == 0 && finishFloat() == 0) {
2832             m_currentSchedule->inCriticalPath = true;
2833             //debugPlan<<m_name<<" start node";
2834             return true;
2835         }
2836         foreach (Relation *r, m_parentProxyRelations) {
2837             if (r->parent()->calcCriticalPath(fromEnd)) {
2838                 m_currentSchedule->inCriticalPath = true;
2839             }
2840         }
2841         foreach (Relation *r, m_dependParentNodes) {
2842             if (r->parent()->calcCriticalPath(fromEnd)) {
2843                 m_currentSchedule->inCriticalPath = true;
2844             }
2845         }
2846     }
2847     //debugPlan<<m_name<<" return cp="<<m_currentSchedule->inCriticalPath;
2848     return m_currentSchedule->inCriticalPath;
2849 }
2850 
calcFreeFloat()2851 void Task::calcFreeFloat()
2852 {
2853     //debugPlan<<m_name;
2854     if (type() == Node::Type_Summarytask) {
2855         Node::calcFreeFloat();
2856         return;
2857     }
2858     Schedule *cs = m_currentSchedule;
2859     if (cs == 0) {
2860         return;
2861     }
2862     DateTime t;
2863     foreach (Relation *r, m_dependChildNodes) {
2864         DateTime c = r->child()->startTime();
2865         if (!t.isValid() || c < t) {
2866             t = c;
2867         }
2868     }
2869     foreach (Relation *r, m_childProxyRelations) {
2870         DateTime c = r->child()->startTime();
2871         if (!t.isValid() || c < t) {
2872             t = c;
2873         }
2874     }
2875     if (t.isValid() && t > cs->endTime) {
2876         cs->freeFloat = t - cs->endTime;
2877         //debugPlan<<m_name<<": "<<cs->freeFloat.toString();
2878     }
2879 }
2880 
setCurrentSchedule(long id)2881 void Task::setCurrentSchedule(long id)
2882 {
2883     setCurrentSchedulePtr(findSchedule(id));
2884     Node::setCurrentSchedule(id);
2885 }
2886 
effortMetError(long id) const2887 bool Task::effortMetError(long id) const
2888 {
2889     Schedule *s = schedule(id);
2890     if (s == 0 || s->notScheduled || m_estimate->type() != Estimate::Type_Effort) {
2891         return false;
2892     }
2893     return s->effortNotMet;
2894 }
2895 
state(long id) const2896 uint Task::state(long id) const
2897 {
2898     int st = Node::State_None;
2899     if (! isScheduled(id)) {
2900         st |= State_NotScheduled;
2901     }
2902     if (completion().isFinished()) {
2903         st |= Node::State_Finished;
2904         if (completion().finishTime() > endTime(id)) {
2905             st |= State_FinishedLate;
2906         }
2907         if (completion().finishTime() < endTime(id)) {
2908             st |= State_FinishedEarly;
2909         }
2910     } else if (completion().isStarted()) {
2911         st |= Node::State_Started;
2912         if (completion().startTime() > startTime(id)) {
2913             st |= State_StartedLate;
2914         }
2915         if (completion().startTime() < startTime(id)) {
2916             st |= State_StartedEarly;
2917         }
2918         if (completion().percentFinished() > 0) {
2919             st |= State_Running;
2920         }
2921         if (endTime(id) < QDateTime::currentDateTime()) {
2922             st |= State_Late;
2923         }
2924     } else if (isScheduled(id)) {
2925         if (startTime(id) < QDateTime::currentDateTime()) {
2926             st |= State_Late;
2927         }
2928     }
2929     st |= State_ReadyToStart;
2930     //TODO: check proxy relations
2931     foreach (const Relation *r, m_dependParentNodes) {
2932         if (! static_cast<Task*>(r->parent())->completion().isFinished()) {
2933             st &= ~Node::State_ReadyToStart;
2934             st |= Node::State_NotReadyToStart;
2935             break;
2936         }
2937     }
2938     return st;
2939 }
2940 
addWorkPackage(WorkPackage * wp)2941 void Task::addWorkPackage(WorkPackage *wp)
2942 {
2943     emit workPackageToBeAdded(this, m_packageLog.count());
2944     m_packageLog.append(wp);
2945     emit workPackageAdded(this);
2946 }
2947 
removeWorkPackage(WorkPackage * wp)2948 void Task::removeWorkPackage(WorkPackage *wp)
2949 {
2950     int index = m_packageLog.indexOf(wp);
2951     if (index < 0) {
2952         return;
2953     }
2954     emit workPackageToBeRemoved(this, index);
2955     m_packageLog.removeAt(index);
2956     emit workPackageRemoved(this);
2957 }
2958 
workPackageAt(int index) const2959 WorkPackage *Task::workPackageAt(int index) const
2960 {
2961     Q_ASSERT (index >= 0 && index < m_packageLog.count());
2962     return m_packageLog.at(index);
2963 }
2964 
wpOwnerName() const2965 QString Task::wpOwnerName() const
2966 {
2967     if (m_packageLog.isEmpty()) {
2968         return m_workPackage.ownerName();
2969     }
2970     return m_packageLog.last()->ownerName();
2971 }
2972 
wpTransmitionStatus() const2973 WorkPackage::WPTransmitionStatus Task::wpTransmitionStatus() const
2974 {
2975     if (m_packageLog.isEmpty()) {
2976         return m_workPackage.transmitionStatus();
2977     }
2978     return m_packageLog.last()->transmitionStatus();
2979 }
2980 
wpTransmitionTime() const2981 DateTime Task::wpTransmitionTime() const
2982 {
2983     if (m_packageLog.isEmpty()) {
2984         return m_workPackage.transmitionTime();
2985     }
2986     return m_packageLog.last()->transmitionTime();
2987 }
2988 
2989 //------------------------------------------
2990 
Completion(Node * node)2991 Completion::Completion(Node *node)
2992     : m_node(node),
2993       m_started(false),
2994       m_finished(false),
2995       m_entrymode(EnterEffortPerResource)
2996 {}
2997 
Completion(const Completion & c)2998 Completion::Completion(const Completion &c)
2999 {
3000     copy(c);
3001 }
3002 
~Completion()3003 Completion::~Completion()
3004 {
3005     qDeleteAll(m_entries);
3006     qDeleteAll(m_usedEffort);
3007 }
3008 
copy(const Completion & p)3009 void Completion::copy(const Completion &p)
3010 {
3011     m_node = 0; //NOTE
3012     m_started = p.isStarted(); m_finished = p.isFinished();
3013     m_startTime = p.startTime(); m_finishTime = p.finishTime();
3014     m_entrymode = p.entrymode();
3015 
3016     qDeleteAll(m_entries);
3017     m_entries.clear();
3018     Completion::EntryList::ConstIterator entriesIt = p.entries().constBegin();
3019     const Completion::EntryList::ConstIterator entriesEnd = p.entries().constEnd();
3020     for (; entriesIt != entriesEnd; ++entriesIt) {
3021         addEntry(entriesIt.key(), new Entry(*entriesIt.value()));
3022     }
3023 
3024     qDeleteAll(m_usedEffort);
3025     m_usedEffort.clear();
3026     Completion::ResourceUsedEffortMap::ConstIterator usedEffortMapIt = p.usedEffortMap().constBegin();
3027     const Completion::ResourceUsedEffortMap::ConstIterator usedEffortMapEnd = p.usedEffortMap().constEnd();
3028     for (; usedEffortMapIt != usedEffortMapEnd; ++usedEffortMapIt) {
3029         addUsedEffort(usedEffortMapIt.key(), new UsedEffort(*usedEffortMapIt.value()));
3030     }
3031 }
3032 
operator ==(const Completion & p)3033 bool Completion::operator==(const Completion &p)
3034 {
3035     return m_started == p.isStarted() && m_finished == p.isFinished() &&
3036             m_startTime == p.startTime() && m_finishTime == p.finishTime() &&
3037             m_entries == p.entries() &&
3038             m_usedEffort == p.usedEffortMap();
3039 }
operator =(const Completion & p)3040 Completion &Completion::operator=(const Completion &p)
3041 {
3042     copy(p);
3043     return *this;
3044 }
3045 
changed(int property)3046 void Completion::changed(int property)
3047 {
3048     if (m_node) {
3049         m_node->changed(property);
3050     }
3051 }
3052 
setStarted(bool on)3053 void Completion::setStarted(bool on)
3054 {
3055      m_started = on;
3056      changed(Node::CompletionStartedProperty);
3057 }
3058 
setFinished(bool on)3059 void Completion::setFinished(bool on)
3060 {
3061      m_finished = on;
3062      changed(Node::CompletionFinishedProperty);
3063 }
3064 
setStartTime(const DateTime & dt)3065 void Completion::setStartTime(const DateTime &dt)
3066 {
3067      m_startTime = dt;
3068      changed(Node::CompletionStartTimeProperty);
3069 }
3070 
setFinishTime(const DateTime & dt)3071 void Completion::setFinishTime(const DateTime &dt)
3072 {
3073      m_finishTime = dt;
3074      changed(Node::CompletionFinishTimeProperty);
3075 }
3076 
setPercentFinished(QDate date,int value)3077 void Completion::setPercentFinished(QDate date, int value)
3078 {
3079     Entry *e = 0;
3080     if (m_entries.contains(date)) {
3081         e = m_entries[ date ];
3082     } else {
3083         e = new Entry();
3084         m_entries[ date ] = e;
3085     }
3086     e->percentFinished = value;
3087     changed(Node::CompletionPercentageProperty);
3088 }
3089 
setRemainingEffort(QDate date,KPlato::Duration value)3090 void Completion::setRemainingEffort(QDate date, KPlato::Duration value)
3091 {
3092     Entry *e = 0;
3093     if (m_entries.contains(date)) {
3094         e = m_entries[ date ];
3095     } else {
3096         e = new Entry();
3097         m_entries[ date ] = e;
3098     }
3099     e->remainingEffort = value;
3100     changed(Node::CompletionRemainingEffortProperty);
3101 }
3102 
setActualEffort(QDate date,KPlato::Duration value)3103 void Completion::setActualEffort(QDate date, KPlato::Duration value)
3104 {
3105     Entry *e = 0;
3106     if (m_entries.contains(date)) {
3107         e = m_entries[ date ];
3108     } else {
3109         e = new Entry();
3110         m_entries[ date ] = e;
3111     }
3112     e->totalPerformed = value;
3113     changed(Node::CompletionActualEffortProperty);
3114 }
3115 
addEntry(QDate date,Entry * entry)3116 void Completion::addEntry(QDate date, Entry *entry)
3117 {
3118      m_entries.insert(date, entry);
3119      //debugPlan<<m_entries.count()<<" added:"<<date;
3120      changed(Node::CompletionEntryProperty);
3121 }
3122 
entryDate() const3123 QDate Completion::entryDate() const
3124 {
3125     return m_entries.isEmpty() ? QDate() : m_entries.lastKey();
3126 }
3127 
percentFinished() const3128 int Completion::percentFinished() const
3129 {
3130     return m_entries.isEmpty() ? 0 : m_entries.last()->percentFinished;
3131 }
3132 
percentFinished(QDate date) const3133 int Completion::percentFinished(QDate date) const
3134 {
3135     int x = 0;
3136     EntryList::const_iterator it;
3137     for (it = m_entries.constBegin(); it != m_entries.constEnd() && it.key() <= date; ++it) {
3138         x = it.value()->percentFinished;
3139     }
3140     return x;
3141 }
3142 
remainingEffort() const3143 Duration Completion::remainingEffort() const
3144 {
3145     return m_entries.isEmpty() ? Duration::zeroDuration : m_entries.last()->remainingEffort;
3146 }
3147 
remainingEffort(QDate date) const3148 Duration Completion::remainingEffort(QDate date) const
3149 {
3150     Duration x;
3151     EntryList::const_iterator it;
3152     for (it = m_entries.constBegin(); it != m_entries.constEnd() && it.key() <= date; ++it) {
3153         x = it.value()->remainingEffort;
3154     }
3155     return x;
3156 }
3157 
actualEffort() const3158 Duration Completion::actualEffort() const
3159 {
3160     Duration eff;
3161     if (m_entrymode == EnterEffortPerResource) {
3162         foreach(const UsedEffort *ue, m_usedEffort) {
3163             const QMap<QDate, UsedEffort::ActualEffort> map = ue->actualEffortMap();
3164             QMap<QDate, UsedEffort::ActualEffort>::const_iterator it;
3165             for (it = map.constBegin(); it != map.constEnd(); ++it) {
3166                 eff += it.value().effort();
3167             }
3168         }
3169     } else if (! m_entries.isEmpty()) {
3170         eff = m_entries.last()->totalPerformed;
3171     }
3172     return eff;
3173 }
3174 
actualEffort(const Resource * resource,QDate date) const3175 Duration Completion::actualEffort(const Resource *resource, QDate date) const
3176 {
3177     UsedEffort *ue = usedEffort(resource);
3178     if (ue == 0) {
3179         return Duration::zeroDuration;
3180     }
3181     UsedEffort::ActualEffort ae = ue->effort(date);
3182     return ae.effort();
3183 }
3184 
actualEffort(QDate date) const3185 Duration Completion::actualEffort(QDate date) const
3186 {
3187     Duration eff;
3188     if (m_entrymode == EnterEffortPerResource) {
3189         foreach(const UsedEffort *ue, m_usedEffort) {
3190             if (ue && ue->actualEffortMap().contains(date)) {
3191                 eff += ue->actualEffortMap().value(date).effort();
3192             }
3193         }
3194     } else {
3195         // Hmmm: How to really know a specific date?
3196         if (m_entries.contains(date)) {
3197             eff = m_entries[ date ]->totalPerformed;
3198         }
3199     }
3200     return eff;
3201 }
3202 
actualEffortTo(QDate date) const3203 Duration Completion::actualEffortTo(QDate date) const
3204 {
3205     //debugPlan<<date;
3206     Duration eff;
3207     if (m_entrymode == EnterEffortPerResource) {
3208         foreach(const UsedEffort *ue, m_usedEffort) {
3209             eff += ue->effortTo(date);
3210         }
3211     } else {
3212         QListIterator<QDate> it(m_entries.uniqueKeys());
3213         it.toBack();
3214         while (it.hasPrevious()) {
3215             QDate d = it.previous();
3216             if (d <= date) {
3217                 eff = m_entries[ d ]->totalPerformed;
3218                 break;
3219             }
3220         }
3221     }
3222     return eff;
3223 }
3224 
averageCostPrHour(QDate date,long id) const3225 double Completion::averageCostPrHour(QDate date, long id) const
3226 {
3227     Schedule *s = m_node->schedule(id);
3228     if (s == 0) {
3229         return 0.0;
3230     }
3231     double cost = 0.0;
3232     double eff = 0.0;
3233     QList<double> cl;
3234     foreach (const Appointment *a, s->appointments()) {
3235         cl << a->resource()->resource()->normalRate();
3236         double e = a->plannedEffort(date).toDouble(Duration::Unit_h);
3237         if (e > 0.0) {
3238             eff += e;
3239             cost += e * cl.last();
3240         }
3241     }
3242     if (eff > 0.0) {
3243         cost /= eff;
3244     } else {
3245         foreach (double c, cl) {
3246             cost += c;
3247         }
3248         cost /= cl.count();
3249     }
3250     return cost;
3251 }
3252 
effortCostPrDay(QDate start,QDate end,long id) const3253 EffortCostMap Completion::effortCostPrDay(QDate start, QDate end, long id) const
3254 {
3255     //debugPlan<<m_node->name()<<start<<end;
3256     EffortCostMap ec;
3257     if (! isStarted()) {
3258         return ec;
3259     }
3260     switch (m_entrymode) {
3261         case FollowPlan:
3262             break;
3263         case EnterCompleted:
3264         case EnterEffortPerTask: {
3265             QDate st = start.isValid() ? start : m_startTime.date();
3266             QDate et = end.isValid() ? end : m_finishTime.date();
3267             Duration last;
3268             foreach (const QDate &d, m_entries.uniqueKeys()) {
3269                 if (d < st) {
3270                     continue;
3271                 }
3272                 if (et.isValid() && d > et) {
3273                     break;
3274                 }
3275                 Duration e = m_entries[ d ]->totalPerformed;
3276                 if (e != Duration::zeroDuration && e != last) {
3277                     Duration eff = e - last;
3278                     ec.insert(d, eff, eff.toDouble(Duration::Unit_h) * averageCostPrHour(d, id));
3279                     last = e;
3280                 }
3281             }
3282             break;
3283         }
3284         case EnterEffortPerResource: {
3285             std::pair<QDate, QDate> dates = actualStartEndDates();
3286             if (! dates.first.isValid()) {
3287                 // no data, so just break
3288                 break;
3289             }
3290             QDate st = start.isValid() ? start : dates.first;
3291             QDate et = end.isValid() ? end : dates.second;
3292             for (QDate d = st; d <= et; d = d.addDays(1)) {
3293                 ec.add(d, actualEffort(d), actualCost(d));
3294             }
3295             break;
3296         }
3297     }
3298     return ec;
3299 }
3300 
effortCostPrDay(const Resource * resource,QDate start,QDate end,long id) const3301 EffortCostMap Completion::effortCostPrDay(const Resource *resource, QDate start, QDate end, long id) const
3302 {
3303     Q_UNUSED(id);
3304     //debugPlan<<m_node->name()<<start<<end;
3305     EffortCostMap ec;
3306     if (! isStarted()) {
3307         return ec;
3308     }
3309     switch (m_entrymode) {
3310         case FollowPlan:
3311             break;
3312         case EnterCompleted:
3313         case EnterEffortPerTask: {
3314             //TODO but what todo?
3315             break;
3316         }
3317         case EnterEffortPerResource: {
3318             std::pair<QDate, QDate> dates = actualStartEndDates();
3319             if (! dates.first.isValid()) {
3320                 // no data, so just break
3321                 break;
3322             }
3323             QDate st = start.isValid() ? start : dates.first;
3324             QDate et = end.isValid() ? end : dates.second;
3325             for (QDate d = st; d <= et; d = d.addDays(1)) {
3326                 ec.add(d, actualEffort(resource, d), actualCost(resource, d));
3327             }
3328             break;
3329         }
3330     }
3331     return ec;
3332 }
3333 
addUsedEffort(const Resource * resource,Completion::UsedEffort * value)3334 void Completion::addUsedEffort(const Resource *resource, Completion::UsedEffort *value)
3335 {
3336     UsedEffort *v = value == 0 ? new UsedEffort() : value;
3337     if (m_usedEffort.contains(resource)) {
3338         m_usedEffort[ resource ]->mergeEffort(*v);
3339         delete v;
3340     } else {
3341         m_usedEffort.insert(resource, v);
3342     }
3343     changed(Node::CompletionUsedEffortProperty);
3344 }
3345 
setActualEffort(Resource * resource,const QDate & date,const Completion::UsedEffort::ActualEffort & value)3346 void Completion::setActualEffort(Resource *resource, const QDate &date, const Completion::UsedEffort::ActualEffort &value)
3347 {
3348     if (value.isNull()) {
3349         if (!m_usedEffort.contains(resource)) {
3350             return;
3351         }
3352         UsedEffort *ue = m_usedEffort.value(resource);
3353         if (!ue) {
3354             return;
3355         }
3356         ue->takeEffort(date);
3357     } else {
3358         UsedEffort *ue = m_usedEffort[resource];
3359         if (!ue) {
3360             ue = new UsedEffort();
3361             m_usedEffort.insert(resource, ue);
3362         }
3363         ue->setEffort(date, value);
3364     }
3365     changed(Node::CompletionActualEffortProperty);
3366 }
3367 
getActualEffort(Resource * resource,const QDate & date) const3368 Completion::UsedEffort::ActualEffort Completion::getActualEffort(Resource *resource, const QDate &date) const
3369 {
3370     UsedEffort::ActualEffort value;
3371     UsedEffort *ue = m_usedEffort.value(resource);
3372     if (ue) {
3373         value = ue->effort(date);
3374     }
3375     return value;
3376 }
3377 
note() const3378 QString Completion::note() const
3379 {
3380     return m_entries.isEmpty() ? QString() : m_entries.last()->note;
3381 }
3382 
setNote(const QString & str)3383 void Completion::setNote(const QString &str)
3384 {
3385     if (! m_entries.isEmpty()) {
3386         m_entries.last()->note = str;
3387         changed(Node::CompletionNoteProperty);
3388     }
3389 }
3390 
actualStartEndDates() const3391 std::pair<QDate, QDate> Completion::actualStartEndDates() const
3392 {
3393     std::pair<QDate, QDate> p;
3394     ResourceUsedEffortMap::const_iterator it;
3395     for (it = m_usedEffort.constBegin(); it != m_usedEffort.constEnd(); ++it) {
3396         if (!it.value()->actualEffortMap().isEmpty()) {
3397             QDate d = it.value()->firstDate();
3398             if (!p.first.isValid() || d < p.first) {
3399                 p.first = d;
3400             }
3401             d = it.value()->lastDate();
3402             if (!p.second.isValid() || d > p.second) {
3403                 p.second = d;
3404             }
3405         }
3406     }
3407     return p;
3408 }
3409 
actualCost(QDate date) const3410 double Completion::actualCost(QDate date) const
3411 {
3412     //debugPlan<<date;
3413     double c = 0.0;
3414     ResourceUsedEffortMap::const_iterator it;
3415     for (it = m_usedEffort.constBegin(); it != m_usedEffort.constEnd(); ++it) {
3416         double nc = it.key()->normalRate();
3417         double oc = it.key()->overtimeRate();
3418         if (it.value()->actualEffortMap().contains(date)) {
3419             UsedEffort::ActualEffort a = it.value()->effort(date);
3420             c += a.normalEffort().toDouble(Duration::Unit_h) * nc;
3421             c += a.overtimeEffort().toDouble(Duration::Unit_h) * oc;
3422         }
3423     }
3424     return c;
3425 }
3426 
actualCost(const Resource * resource) const3427 double Completion::actualCost(const Resource *resource) const
3428 {
3429     UsedEffort *ue = usedEffort(resource);
3430     if (ue == 0) {
3431         return 0.0;
3432     }
3433     double c = 0.0;
3434     double nc = resource->normalRate();
3435     double oc = resource->overtimeRate();
3436     foreach (const UsedEffort::ActualEffort &a, ue->actualEffortMap()) {
3437         c += a.normalEffort().toDouble(Duration::Unit_h) * nc;
3438         c += a.overtimeEffort().toDouble(Duration::Unit_h) * oc;
3439     }
3440     return c;
3441 }
3442 
actualCost() const3443 double Completion::actualCost() const
3444 {
3445     double c = 0.0;
3446     ResourceUsedEffortMap::const_iterator it;
3447     for (it = m_usedEffort.constBegin(); it != m_usedEffort.constEnd(); ++it) {
3448         c += actualCost(it.key());
3449     }
3450     return c;
3451 }
3452 
actualCost(const Resource * resource,QDate date) const3453 double Completion::actualCost(const Resource *resource, QDate date) const
3454 {
3455     UsedEffort *ue = usedEffort(resource);
3456     if (ue == 0) {
3457         return 0.0;
3458     }
3459     UsedEffort::ActualEffort a = ue->actualEffortMap().value(date);
3460     double c = a.normalEffort().toDouble(Duration::Unit_h) * resource->normalRate();
3461     c += a.overtimeEffort().toDouble(Duration::Unit_h) * resource->overtimeRate();
3462     return c;
3463 }
3464 
actualEffortCost(long int id,KPlato::EffortCostCalculationType type) const3465 EffortCostMap Completion::actualEffortCost(long int id, KPlato::EffortCostCalculationType type) const
3466 {
3467     //debugPlan;
3468     EffortCostMap map;
3469     if (! isStarted()) {
3470         return map;
3471     }
3472     QList< QMap<QDate, UsedEffort::ActualEffort> > lst;
3473     QList< double > rate;
3474     QDate start, end;
3475     ResourceUsedEffortMap::const_iterator it;
3476     for (it = m_usedEffort.constBegin(); it != m_usedEffort.constEnd(); ++it) {
3477         const Resource *r = it.key();
3478         //debugPlan<<m_node->name()<<r->name();
3479         lst << usedEffort(r)->actualEffortMap();
3480         if (lst.last().isEmpty()) {
3481             lst.takeLast();
3482             continue;
3483         }
3484         if (r->type() == Resource::Type_Material) {
3485             if (type == ECCT_All) {
3486                 rate.append(r->normalRate());
3487             } else if (type == ECCT_EffortWork) {
3488                 rate.append(0.0);
3489             } else {
3490                 lst.takeLast();
3491                 continue;
3492             }
3493         } else {
3494             rate.append(r->normalRate());
3495         }
3496         if (! start.isValid() || start > lst.last().firstKey()) {
3497             start = lst.last().firstKey();
3498         }
3499         if (! end.isValid() || end < lst.last().lastKey()) {
3500             end = lst.last().lastKey();
3501         }
3502     }
3503     if (! lst.isEmpty() && start.isValid() && end.isValid()) {
3504         for (QDate d = start; d <= end; d = d.addDays(1)) {
3505             EffortCost c;
3506             for (int i = 0; i < lst.count(); ++i) {
3507                 UsedEffort::ActualEffort a = lst.at(i).value(d);
3508                 double nc = rate.value(i);
3509                 Duration eff = a.normalEffort();
3510                 double cost = eff.toDouble(Duration::Unit_h) * nc;
3511                 c.add(eff, cost);
3512             }
3513             if (c.effort() != Duration::zeroDuration || c.cost() != 0.0) {
3514                 map.add(d, c);
3515             }
3516         }
3517     } else if (! m_entries.isEmpty()) {
3518         QDate st = start.isValid() ? start : m_startTime.date();
3519         QDate et = end.isValid() ? end : m_finishTime.date();
3520         Duration last;
3521         foreach (const QDate &d, m_entries.uniqueKeys()) {
3522             if (d < st) {
3523                 continue;
3524             }
3525             Duration e = m_entries[ d ]->totalPerformed;
3526             if (e != Duration::zeroDuration && e != last) {
3527                 //debugPlan<<m_node->name()<<d<<(e - last).toDouble(Duration::Unit_h);
3528                 double eff = (e - last).toDouble(Duration::Unit_h);
3529                 map.insert(d, e - last, eff * averageCostPrHour(d, id)); // try to guess cost
3530                 last = e;
3531             }
3532             if (et.isValid() && d > et) {
3533                 break;
3534             }
3535         }
3536     }
3537     return map;
3538 }
3539 
actualCostTo(long int id,QDate date) const3540 EffortCost Completion::actualCostTo(long int id, QDate date) const
3541 {
3542     //debugPlan<<date;
3543     EffortCostMap ecm = actualEffortCost(id);
3544     return EffortCost(ecm.effortTo(date), ecm.costTo(date));
3545 }
3546 
entrymodeList() const3547 QStringList Completion::entrymodeList() const
3548 {
3549     return QStringList()
3550             << QStringLiteral("FollowPlan")
3551             << QStringLiteral("EnterCompleted")
3552             << QStringLiteral("EnterEffortPerTask")
3553             << QStringLiteral("EnterEffortPerResource");
3554 
3555 }
3556 
setEntrymode(const QString & mode)3557 void Completion::setEntrymode(const QString &mode)
3558 {
3559     int m = entrymodeList().indexOf(mode);
3560     if (m == -1) {
3561         m = EnterCompleted;
3562     }
3563     m_entrymode = static_cast<Entrymode>(m);
3564 }
entryModeToString() const3565 QString Completion::entryModeToString() const
3566 {
3567     return entrymodeList().value(m_entrymode);
3568 }
3569 
loadXML(KoXmlElement & element,XMLLoaderObject & status)3570 bool Completion::loadXML(KoXmlElement &element, XMLLoaderObject &status)
3571 {
3572     //debugPlan;
3573     QString s;
3574     m_started = (bool)element.attribute(QStringLiteral("started"), QStringLiteral("0")).toInt();
3575     m_finished = (bool)element.attribute(QStringLiteral("finished"), QStringLiteral("0")).toInt();
3576     s = element.attribute(QStringLiteral("startTime"));
3577     if (!s.isEmpty()) {
3578         m_startTime = DateTime::fromString(s, status.projectTimeZone());
3579     }
3580     s = element.attribute(QStringLiteral("finishTime"));
3581     if (!s.isEmpty()) {
3582         m_finishTime = DateTime::fromString(s, status.projectTimeZone());
3583     }
3584     setEntrymode(element.attribute(QStringLiteral("entrymode")));
3585     if (status.version() < QLatin1String("0.6")) {
3586         if (m_started) {
3587             Entry *entry = new Entry(element.attribute(QStringLiteral("percent-finished"), QStringLiteral("0")).toInt(), Duration::fromString(element.attribute(QStringLiteral("remaining-effort"))),  Duration::fromString(element.attribute(QStringLiteral("performed-effort"))));
3588             entry->note = element.attribute(QStringLiteral("note"));
3589             QDate date = m_startTime.date();
3590             if (m_finished) {
3591                 date = m_finishTime.date();
3592             }
3593             // almost the best we can do ;)
3594             addEntry(date, entry);
3595         }
3596     } else {
3597         KoXmlElement e;
3598         forEachElement(e, element) {
3599                 if (e.tagName() == QLatin1String("completion-entry")) {
3600                     QDate date;
3601                     s = e.attribute(QStringLiteral("date"));
3602                     if (!s.isEmpty()) {
3603                         date = QDate::fromString(s, Qt::ISODate);
3604                     }
3605                     if (!date.isValid()) {
3606                         warnPlan<<"Invalid date: "<<date<<s;
3607                         continue;
3608                     }
3609                     Entry *entry = new Entry(e.attribute(QStringLiteral("percent-finished"), QStringLiteral("0")).toInt(), Duration::fromString(e.attribute(QStringLiteral("remaining-effort"))),  Duration::fromString(e.attribute(QStringLiteral("performed-effort"))));
3610                     addEntry(date, entry);
3611                 } else if (e.tagName() == QLatin1String("used-effort")) {
3612                     KoXmlElement el;
3613                     forEachElement(el, e) {
3614                             if (el.tagName() == QLatin1String("resource")) {
3615                                 QString id = el.attribute(QStringLiteral("id"));
3616                                 Resource *r = status.project().resource(id);
3617                                 if (r == 0) {
3618                                     warnPlan<<"Cannot find resource, id="<<id;
3619                                     continue;
3620                                 }
3621                                 UsedEffort *ue = new UsedEffort();
3622                                 addUsedEffort(r, ue);
3623                                 ue->loadXML(el, status);
3624                             }
3625                     }
3626                 }
3627         }
3628     }
3629     return true;
3630 }
3631 
saveXML(QDomElement & element) const3632 void Completion::saveXML(QDomElement &element)  const
3633 {
3634     QDomElement el = element.ownerDocument().createElement(QStringLiteral("progress"));
3635     element.appendChild(el);
3636     el.setAttribute(QStringLiteral("started"), QString::number(m_started));
3637     el.setAttribute(QStringLiteral("finished"), QString::number(m_finished));
3638     el.setAttribute(QStringLiteral("startTime"), m_startTime.toString(Qt::ISODate));
3639     el.setAttribute(QStringLiteral("finishTime"), m_finishTime.toString(Qt::ISODate));
3640     el.setAttribute(QStringLiteral("entrymode"), entryModeToString());
3641     foreach(const QDate &date, m_entries.uniqueKeys()) {
3642         QDomElement elm = el.ownerDocument().createElement(QStringLiteral("completion-entry"));
3643         el.appendChild(elm);
3644         Entry *e = m_entries[ date ];
3645         elm.setAttribute(QStringLiteral("date"), date.toString(Qt::ISODate));
3646         elm.setAttribute(QStringLiteral("percent-finished"), e->percentFinished);
3647         elm.setAttribute(QStringLiteral("remaining-effort"), e->remainingEffort.toString());
3648         elm.setAttribute(QStringLiteral("performed-effort"), e->totalPerformed.toString());
3649         elm.setAttribute(QStringLiteral("note"), e->note);
3650     }
3651     if (! m_usedEffort.isEmpty()) {
3652         QDomElement elm = el.ownerDocument().createElement(QStringLiteral("used-effort"));
3653         el.appendChild(elm);
3654         ResourceUsedEffortMap::ConstIterator i = m_usedEffort.constBegin();
3655         for (; i != m_usedEffort.constEnd(); ++i) {
3656             if (i.value() == 0) {
3657                 continue;
3658             }
3659             QDomElement e = elm.ownerDocument().createElement(QStringLiteral("resource"));
3660             elm.appendChild(e);
3661             e.setAttribute(QStringLiteral("id"), i.key()->id());
3662             i.value()->saveXML(e);
3663         }
3664     }
3665 }
3666 
3667 //--------------
UsedEffort()3668 Completion::UsedEffort::UsedEffort()
3669 {
3670 }
3671 
UsedEffort(const UsedEffort & e)3672 Completion::UsedEffort::UsedEffort(const UsedEffort &e)
3673 {
3674     mergeEffort(e);
3675 }
3676 
~UsedEffort()3677 Completion::UsedEffort::~UsedEffort()
3678 {
3679 }
3680 
mergeEffort(const Completion::UsedEffort & value)3681 void Completion::UsedEffort::mergeEffort(const Completion::UsedEffort &value)
3682 {
3683     const QMap<QDate, ActualEffort> map = value.actualEffortMap();
3684     QMap<QDate, ActualEffort>::const_iterator it;
3685     for (it = map.constBegin(); it != map.constEnd(); ++it) {
3686         setEffort(it.key(), it.value());
3687     }
3688 }
3689 
setEffort(QDate date,const ActualEffort & value)3690 void Completion::UsedEffort::setEffort(QDate date, const ActualEffort &value)
3691 {
3692     m_actual.insert(date, value);
3693 }
3694 
effortTo(QDate date) const3695 Duration Completion::UsedEffort::effortTo(QDate date) const
3696 {
3697     Duration eff;
3698     QMap<QDate, ActualEffort>::const_iterator it;
3699     for (it = m_actual.constBegin(); it != m_actual.constEnd() && it.key() <= date; ++it) {
3700         eff += it.value().effort();
3701     }
3702     return eff;
3703 }
3704 
effort() const3705 Duration Completion::UsedEffort::effort() const
3706 {
3707     Duration eff;
3708     foreach (const ActualEffort &e, m_actual) {
3709         eff += e.effort();
3710     }
3711     return eff;
3712 }
3713 
operator ==(const Completion::UsedEffort & e) const3714 bool Completion::UsedEffort::operator==(const Completion::UsedEffort &e) const
3715 {
3716     return m_actual == e.actualEffortMap();
3717 }
3718 
loadXML(KoXmlElement & element,XMLLoaderObject &)3719 bool Completion::UsedEffort::loadXML(KoXmlElement &element, XMLLoaderObject &)
3720 {
3721     //debugPlan;
3722     KoXmlElement e;
3723     forEachElement(e, element) {
3724             if (e.tagName() == QLatin1String("actual-effort")) {
3725                 QDate date = QDate::fromString(e.attribute(QStringLiteral("date")), Qt::ISODate);
3726                 if (date.isValid()) {
3727                     ActualEffort a;
3728                     a.setNormalEffort(Duration::fromString(e.attribute(QStringLiteral("normal-effort"))));
3729                     a.setOvertimeEffort(Duration::fromString(e.attribute(QStringLiteral("overtime-effort"))));
3730                     setEffort(date, a);
3731                 }
3732             }
3733     }
3734     return true;
3735 }
3736 
saveXML(QDomElement & element) const3737 void Completion::UsedEffort::saveXML(QDomElement &element) const
3738 {
3739     if (m_actual.isEmpty()) {
3740         return;
3741     }
3742     DateUsedEffortMap::ConstIterator i = m_actual.constBegin();
3743     for (; i != m_actual.constEnd(); ++i) {
3744         QDomElement el = element.ownerDocument().createElement(QStringLiteral("actual-effort"));
3745         element.appendChild(el);
3746         el.setAttribute(QStringLiteral("overtime-effort"), i.value().overtimeEffort().toString());
3747         el.setAttribute(QStringLiteral("normal-effort"), i.value().normalEffort().toString());
3748         el.setAttribute(QStringLiteral("date"), i.key().toString(Qt::ISODate));
3749     }
3750 }
3751 
3752 //----------------------------------
WorkPackage(Task * task)3753 WorkPackage::WorkPackage(Task *task)
3754     : m_task(task),
3755     m_manager(0),
3756     m_transmitionStatus(TS_None)
3757 {
3758     m_completion.setNode(task);
3759 }
3760 
WorkPackage(const WorkPackage & wp)3761 WorkPackage::WorkPackage(const WorkPackage &wp)
3762     : m_task(0),
3763     m_manager(0),
3764     m_completion(wp.m_completion),
3765     m_ownerName(wp.m_ownerName),
3766     m_ownerId(wp.m_ownerId),
3767     m_transmitionStatus(wp.m_transmitionStatus),
3768     m_transmitionTime(wp.m_transmitionTime)
3769 {
3770 }
3771 
~WorkPackage()3772 WorkPackage::~WorkPackage()
3773 {
3774 }
3775 
loadXML(KoXmlElement & element,XMLLoaderObject & status)3776 bool WorkPackage::loadXML(KoXmlElement &element, XMLLoaderObject &status)
3777 {
3778     Q_UNUSED(status);
3779     m_ownerName = element.attribute(QStringLiteral("owner"));
3780     m_ownerId = element.attribute(QStringLiteral("owner-id"));
3781     return true;
3782 }
3783 
saveXML(QDomElement & element) const3784 void WorkPackage::saveXML(QDomElement &element) const
3785 {
3786     QDomElement el = element.ownerDocument().createElement(QStringLiteral("workpackage"));
3787     element.appendChild(el);
3788     el.setAttribute(QStringLiteral("owner"), m_ownerName);
3789     el.setAttribute(QStringLiteral("owner-id"), m_ownerId);
3790 }
3791 
loadLoggedXML(KoXmlElement & element,XMLLoaderObject & status)3792 bool WorkPackage::loadLoggedXML(KoXmlElement &element, XMLLoaderObject &status)
3793 {
3794     m_ownerName = element.attribute(QStringLiteral("owner"));
3795     m_ownerId = element.attribute(QStringLiteral("owner-id"));
3796     m_transmitionStatus = transmitionStatusFromString(element.attribute(QStringLiteral("status")));
3797     m_transmitionTime = DateTime(QDateTime::fromString(element.attribute(QStringLiteral("time")), Qt::ISODate));
3798     return m_completion.loadXML(element, status);
3799 }
3800 
saveLoggedXML(QDomElement & element) const3801 void WorkPackage::saveLoggedXML(QDomElement &element) const
3802 {
3803     QDomElement el = element.ownerDocument().createElement(QStringLiteral("workpackage"));
3804     element.appendChild(el);
3805     el.setAttribute(QStringLiteral("owner"), m_ownerName);
3806     el.setAttribute(QStringLiteral("owner-id"), m_ownerId);
3807     el.setAttribute(QStringLiteral("status"), transmitionStatusToString(m_transmitionStatus));
3808     el.setAttribute(QStringLiteral("time"), m_transmitionTime.toString(Qt::ISODate));
3809     m_completion.saveXML(el);
3810 }
3811 
fetchResources()3812 QList<Resource*> WorkPackage::fetchResources()
3813 {
3814     return fetchResources(id());
3815 }
3816 
fetchResources(long id)3817 QList<Resource*> WorkPackage::fetchResources(long id)
3818 {
3819     //debugPlan<<m_task.name();
3820     QList<Resource*> lst;
3821     if (id == NOTSCHEDULED) {
3822         if (m_task) {
3823             lst << m_task->requestedResources();
3824         }
3825     } else {
3826         if (m_task) lst = m_task->assignedResources(id);
3827         foreach (const Resource *r, m_completion.resources()) {
3828             if (! lst.contains(const_cast<Resource*>(r))) {
3829                 lst << const_cast<Resource*>(r);
3830             }
3831         }
3832     }
3833     return lst;
3834 }
3835 
completion()3836 Completion &WorkPackage::completion()
3837 {
3838     return m_completion;
3839 }
3840 
completion() const3841 const Completion &WorkPackage::completion() const
3842 {
3843     return m_completion;
3844 }
3845 
3846 
setScheduleManager(ScheduleManager * sm)3847 void WorkPackage::setScheduleManager(ScheduleManager *sm)
3848 {
3849     m_manager = sm;
3850 }
3851 
3852 
transmitionStatusToString(WorkPackage::WPTransmitionStatus sts,bool trans)3853 QString WorkPackage::transmitionStatusToString(WorkPackage::WPTransmitionStatus sts, bool trans)
3854 {
3855     QString s = trans ? i18n("None") : QStringLiteral("None");
3856     switch (sts) {
3857         case TS_Send:
3858             s = trans ? i18n("Send") : QStringLiteral("Send");
3859             break;
3860         case TS_Receive:
3861             s = trans ? i18n("Receive") : QStringLiteral("Receive");
3862             break;
3863         case TS_Rejected:
3864             s = trans ? i18n("Rejected") : QStringLiteral("Rejected");
3865             break;
3866         default:
3867             break;
3868     }
3869     return s;
3870 }
3871 
transmitionStatusFromString(const QString & sts)3872 WorkPackage::WPTransmitionStatus WorkPackage::transmitionStatusFromString(const QString &sts)
3873 {
3874     QStringList lst;
3875     lst << QStringLiteral("None") << QStringLiteral("Send") << QStringLiteral("Receive");
3876     int s = lst.indexOf(sts);
3877     return s < 0 ? TS_None : static_cast<WPTransmitionStatus>(s);
3878 }
3879 
clear()3880 void WorkPackage::clear()
3881 {
3882     //m_task = 0;
3883     m_manager = 0;
3884     m_ownerName.clear();
3885     m_ownerId.clear();
3886     m_transmitionStatus = TS_None;
3887     m_transmitionTime = DateTime();
3888     m_log.clear();
3889 
3890     m_completion = Completion();
3891     m_completion.setNode(m_task);
3892 
3893 }
3894 
3895 //--------------------------------
WorkPackageSettings()3896 WorkPackageSettings::WorkPackageSettings()
3897     : usedEffort(true),
3898     progress(true),
3899     documents(true),
3900     remainingEffort(true)
3901 {
3902 }
3903 
saveXML(QDomElement & element) const3904 void WorkPackageSettings::saveXML(QDomElement &element) const
3905 {
3906     QDomElement el = element.ownerDocument().createElement(QStringLiteral("settings"));
3907     element.appendChild(el);
3908     el.setAttribute(QStringLiteral("used-effort"), QString::number(usedEffort));
3909     el.setAttribute(QStringLiteral("progress"), QString::number(progress));
3910     el.setAttribute(QStringLiteral("documents"), QString::number(documents));
3911     el.setAttribute(QStringLiteral("remaining-effort"), QString::number(remainingEffort));
3912 }
3913 
loadXML(const KoXmlElement & element)3914 bool WorkPackageSettings::loadXML(const KoXmlElement &element)
3915 {
3916     usedEffort = (bool)element.attribute(QStringLiteral("used-effort")).toInt();
3917     progress = (bool)element.attribute(QStringLiteral("progress")).toInt();
3918     documents = (bool)element.attribute(QStringLiteral("documents")).toInt();
3919     remainingEffort = (bool)element.attribute(QStringLiteral("remaining-effort")).toInt();
3920     return true;
3921 }
3922 
operator ==(KPlato::WorkPackageSettings s) const3923 bool WorkPackageSettings::operator==(KPlato::WorkPackageSettings s) const
3924 {
3925     return usedEffort == s.usedEffort &&
3926             progress == s.progress &&
3927             documents == s.documents &&
3928             remainingEffort == s.remainingEffort;
3929 }
3930 
operator !=(KPlato::WorkPackageSettings s) const3931 bool WorkPackageSettings::operator!=(KPlato::WorkPackageSettings s) const
3932 {
3933     return ! operator==(s);
3934 }
3935 
3936 
3937 }  //KPlato namespace
3938 
3939 #ifndef QT_NO_DEBUG_STREAM
operator <<(QDebug dbg,const KPlato::Completion::UsedEffort::ActualEffort & ae)3940 QDebug operator<<(QDebug dbg, const KPlato::Completion::UsedEffort::ActualEffort &ae)
3941 {
3942     dbg << QStringLiteral("%1").arg(ae.normalEffort().toDouble(KPlato::Duration::Unit_h), 1);
3943     return dbg;
3944 }
3945 #endif
3946