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