00001 #include "task.h"
00002
00003 #include "schedule.h"
00004 #include "resource.h"
00005 #include "project.h"
00006 #include "duration.h"
00007 #include "taskstates.h"
00008 #include "user.h"
00009 #include "statechangedata.h"
00010 #include "schedule.h"
00011 #include "tasktype.h"
00012 #include "usertype.h"
00013 #include "resourcetype.h"
00014 #include "clock.h"
00015 #include "taskmanagerdata.h"
00016
00017 #include <tools/invaliddataexception.h>
00018 #include <tools/functions.h>
00019
00020 #include <QString>
00021 #include <QDateTime>
00022 #include <QList>
00023 #include <QVariant>
00024 #include <QDebug>
00025
00026 namespace domain
00027 {
00028
00029 struct TaskPrivate
00030 {
00031 TaskPrivate(Task* qq, TaskType* type, User* user, const Schedule& schedule);
00032
00033 Task* q;
00034 TaskType* type;
00035 User* user;
00036 QMap<QString, QVariant> fields;
00037 Schedule schedule;
00038 TaskState* state;
00039 QList<Reservation*> reservations;
00040 QList<Invitation*> invitations;
00041 QList<Task*> subTasks;
00042 QList<Task*> superTasks;
00043 Project* project;
00044
00045 bool violatesBusinessRule1(Task* dependencyToAdd);
00046 QList<const Task*> subTasksViolatingBusinessRule2(TaskState::SettableState newState);
00047 QList<const Task*> superTasksViolatingBusinessRule2(domain::TaskState::SettableState state);
00048 bool violatesBusinessRule3(TaskState::SettableState newState);
00049 unsigned nbReservations(ResourceType* resType);
00050 unsigned nbActiveInvitations(UserType* userType);
00051 };
00052
00053 TaskPrivate::TaskPrivate(Task* qq, TaskType* type, User* user,
00054 const Schedule& schedule) : q(qq),
00055 type(type),
00056 user(user),
00057 schedule(schedule)
00058 {
00059 if (user == 0)
00060 throw tools::InvalidDataException("No user given.");
00061
00062 if (!type->userCanCreate(user->userType()))
00063 {
00064 throw tools::InvalidDataException(QString("User of type \"%1\" is not allowed "
00065 "to create a task of type \"%2\"")
00066 .arg(user->userType()->name())
00067 .arg(type->name()));
00068 }
00069
00070 state = 0;
00071 project = 0;
00072 }
00073
00074 Task::Task(TaskType* type, domain::User* user, const domain::Schedule& schedule,
00075 TaskState::SettableState state, const QList<Task*>& subTasks,
00076 const QList<InvitationData>& invitations,
00077 const QList<ReservationData>& reservations) :
00078 d(new TaskPrivate(this, type, user, schedule))
00079 {
00080 try
00081 {
00082 Q_FOREACH (InvitationData invitation, invitations)
00083 {
00084 Invitation* inv = TaskManagerData::instance()->
00085 _createInvitation(this, invitation.user);
00086 inv->setState(invitation.state);
00087 }
00088
00089 Q_FOREACH (ReservationData reservation, reservations)
00090 {
00091 TaskManagerData::instance()->_createReservation(this,
00092 reservation.resource,
00093 reservation.startDate,
00094 reservation.duration);
00095 }
00096
00097 Q_FOREACH (Task* subTask, subTasks)
00098 _addSubTask(subTask);
00099
00100 user->addTask(this);
00101
00102 setState(state);
00103 }
00104 catch (tools::StringException& e)
00105 {
00106 Q_FOREACH (Invitation* invitation, d->invitations)
00107 TaskManagerData::instance()->removeData(invitation);
00108
00109 Q_FOREACH (Reservation* reservation, d->reservations)
00110 TaskManagerData::instance()->removeData(reservation);
00111
00112 Q_FOREACH (Task* subTask, subTasks)
00113 removeSubTask(subTask);
00114
00115 user->removeTask(this);
00116
00117 throw e;
00118 }
00119
00120 Clock::newEvent(schedule.dueDate(), this);
00121 Clock::newEvent(schedule.startDate(), this);
00122 connect(type, SIGNAL(dataChanged()), this, SLOT(notify()));
00123 }
00124
00125 Task::~Task()
00126 {
00127 d->state = TaskDeleting::instance();
00128 deleteDependentData();
00129 setProject(0);
00130 d->user->removeTask(this);
00131 Clock::deleteEvents(this);
00132
00133 Q_FOREACH (Task* task, d->superTasks)
00134 task->removeSubTask(this);
00135
00136 Q_FOREACH (Task* task, d->subTasks)
00137 removeSubTask(task);
00138
00139 delete d;
00140 }
00141
00142 void Task::setField(const QString& id, const QVariant& value)
00143 {
00144 d->state->setField(this, id, value);
00145 }
00146
00147 void Task::_setField(const QString& id, const QVariant& value)
00148 {
00149 Field field = d->type->fieldById(id);
00150
00151 if (field.isNull())
00152 throw tools::InvalidDataException(QString("No field with id %1").arg(id));
00153
00154 if (!field.isValid(value))
00155 {
00156 throw tools::InvalidDataException(QString("Invalid value for field"
00157 " with name \"%1\"").arg(field.name));
00158 }
00159
00160 d->fields[id] = value;
00161 }
00162
00163 QVariant Task::field(const QString& id) const
00164 {
00165 if (d->fields.contains(id))
00166 return d->fields[id];
00167
00168 return QVariant();
00169 }
00170
00171 QMap<QString, QVariant> Task::fields() const
00172 {
00173 return d->fields;
00174 }
00175
00176 void Task::addReservation(Reservation* reservation)
00177 {
00178 if (d->state == 0)
00179 _addReservation(reservation);
00180 else
00181 d->state->addReservation(this, reservation);
00182 }
00183
00184 void Task::_addReservation(Reservation* reservation)
00185 {
00186 ResourceType* resType = reservation->resource()->resourceType();
00187 Requirement* req = d->type->requirementByType(resType);
00188
00189 if (req == 0)
00190 {
00191 throw tools::InvalidDataException(QString("No requirement for resource "
00192 "of type %1").arg(resType->name()));
00193 }
00194 if (d->nbReservations(resType) >= req->max)
00195 {
00196 throw tools::InvalidDataException(QString("Maximum number of reservations "
00197 "of type %1 already obtained").arg(resType->name()));
00198 }
00199
00200 if (reservation->duration() < d->schedule.duration())
00201 {
00202 QString msg("The reservation for resource \"%1\" is shorter than the"
00203 " duration of the task");
00204 throw tools::InvalidDataException(msg.arg(reservation->resource()
00205 ->description()));
00206 }
00207
00208 Q_FOREACH (Reservation* res, d->reservations)
00209 {
00210 if (!res->overlapsWith(reservation, d->schedule.duration()))
00211 {
00212 QString newResStr = reservation->resource()->description();
00213 QString problemResStr = res->resource()->description();
00214 QString msg("The reservation for resource \"%1\" does not overlap with "
00215 "the reservation of resource \"%2\"");
00216
00217 throw tools::InvalidDataException(msg.arg(newResStr).arg(problemResStr));
00218 }
00219 }
00220
00221 d->reservations << reservation;
00222 Clock::newEvent(reservation->time(), this);
00223 Clock::newEvent(reservation->endTime(), this);
00224 }
00225
00226 void Task::removeReservation(Reservation* reservation)
00227 {
00228 d->reservations.removeAll(reservation);
00229 }
00230
00231 QList<const Reservation*> Task::reservations() const
00232 {
00233 return tools::makeConstList(d->reservations);
00234 }
00235
00236 unsigned Task::nbActiveReservations(ResourceType* type) const
00237 {
00238 unsigned count = 0;
00239
00240 Q_FOREACH (Reservation* reservation, d->reservations)
00241 {
00242 if (reservation->resource()->resourceType() == type && reservation->isActive())
00243 count++;
00244 }
00245
00246 return count;
00247 }
00248
00249 QList<const Invitation*> Task::invitations() const
00250 {
00251 return tools::makeConstList(d->invitations);
00252 }
00253
00254 unsigned Task::nbAcceptedInvitations(UserType* type) const
00255 {
00256 unsigned count = 0;
00257
00258 Q_FOREACH (Invitation* invitation, d->invitations)
00259 {
00260 if (invitation->user()->userType() == type &&
00261 invitation->state() == Invitation::Accepted)
00262 {
00263 count++;
00264 }
00265 }
00266
00267 return count;
00268 }
00269
00270 const Schedule& Task::schedule() const
00271 {
00272 return d->schedule;
00273 }
00274
00275 QString Task::stateString() const
00276 {
00277 return d->state->name();
00278 }
00279
00280 TaskState::SettableState Task::state() const
00281 {
00282 return d->state->settableState();
00283 }
00284
00285 void Task::setState(TaskState::SettableState newState)
00286 {
00287 StateChangeData changeData = canHaveAsState(newState);
00288
00289 if (!changeData.isChangePossible())
00290 throw tools::InvalidDataException(changeData.message());
00291
00292 Q_FOREACH (const Task* constTask, changeData.violatingTasks())
00293 {
00294 Task* task = const_cast<Task*>(constTask);
00295 TaskState::stateForEnum(changeData.suggestedState(task))->updateTask(task);
00296 }
00297
00298 TaskState::stateForEnum(newState)->updateTask(this);
00299 }
00300
00301 void Task::setState(TaskState* state)
00302 {
00303 d->state = state;
00304 emit dataChanged();
00305 }
00306
00307 StateChangeData Task::canHaveAsState(TaskState::SettableState state) const
00308 {
00309 QList<const Task*> violatingTasks;
00310
00311
00312 violatingTasks = d->subTasksViolatingBusinessRule2(state);
00313
00314 if (!violatingTasks.isEmpty())
00315 {
00316 StateChangeData ret(false);
00317 ret.addViolatingTask(violatingTasks.first());
00318 ret.setMessage("The selected task cannot be updated to given state:\n\n"
00319 "If a foregoing task is failed, the selected task has to"
00320 " be failed.\nIf a foregoing task is unfinished, the"
00321 " selected task cannot be successful");
00322 return ret;
00323 }
00324
00325
00326 if (d->violatesBusinessRule3(state))
00327 {
00328 StateChangeData ret(false);
00329 ret.setMessage("The selected task cannot be updated to given state:\n"
00330 "If the selected task is not started yet, it cannot be"
00331 " successful.\nIf the selected task is past its"
00332 " deadline, it cannot be unfinished.");
00333 return ret;
00334 }
00335
00336
00337
00338 if (state == TaskState::Successful)
00339 {
00340 if (d->state == 0)
00341 {
00342 if (!canBeSuccessful())
00343 {
00344 StateChangeData ret(false);
00345 ret.setMessage("Cannot create a successful task "
00346 "without all requirements being fulfilled");
00347 return ret;
00348 }
00349 }
00350 else if (!isAvailable() && d->state != TaskSuccessful::instance())
00351 {
00352 StateChangeData ret(false);
00353 ret.setMessage("Cannot set the state of an unavailable task to Successful");
00354 return ret;
00355 }
00356 }
00357
00358
00359
00360 StateChangeData ret(true);
00361
00362 violatingTasks = d->superTasksViolatingBusinessRule2(state);
00363
00364 if (!violatingTasks.isEmpty())
00365 {
00366 TaskState::SettableState newState = (state == TaskState::Unfinished) ?
00367 TaskState::Unfinished :
00368 TaskState::Failed;
00369
00370 TaskState* oldState = d->state;
00371 d->state = TaskState::stateForEnum(newState);
00372
00373 Q_FOREACH (const Task* task, violatingTasks)
00374 {
00375 if (!task->canHaveAsState(newState))
00376 {
00377 StateChangeData ret(false);
00378 ret.setMessage(QString("The state of task %1 needs to be changed"
00379 " to %2 but this is impossible.")
00380 .arg(task->id())
00381 .arg(TaskState::nameForEnum(newState)));
00382 d->state = oldState;
00383 return ret;
00384 }
00385
00386 ret.addViolatingTask(task, newState);
00387 }
00388
00389 d->state = oldState;
00390
00391 ret.setMessage(QString("The new state for the selected task is"
00392 " conflicting with the state of depending"
00393 " tasks.\nThe suggested new state is %1.")
00394 .arg(TaskState::nameForEnum(newState)));
00395 }
00396
00397 return ret;
00398 }
00399
00400 void Task::addSubTask(Task* dependency)
00401 {
00402 d->state->addSubTask(this, dependency);
00403 }
00404
00405 void Task::_addSubTask(Task* dependency)
00406 {
00407 if (!d->subTasks.contains(dependency))
00408 {
00409 if (!dependency->dependsOn(this))
00410 {
00411 if (!d->violatesBusinessRule1(dependency))
00412 {
00413 d->subTasks << dependency;
00414 dependency->d->superTasks << this;
00415 notify();
00416 }
00417 else
00418 throw tools::InvalidDataException("Business rule 1 is violated.");
00419 }
00420 else
00421 throw tools::InvalidDataException("Dependency loop detected.");
00422 }
00423 }
00424
00425 void Task::addInvitation(Invitation* invitation)
00426 {
00427 if (invitation->user() == d->user)
00428 {
00429 throw tools::InvalidDataException("It is impossible to invite yourself "
00430 "for a task");
00431 }
00432
00433 Q_FOREACH (Invitation* inv, d->invitations)
00434 {
00435 if (inv->user() == invitation->user())
00436 {
00437 throw tools::InvalidDataException(QString("You have already invited "
00438 "user %1")
00439 .arg(inv->user()->name()));
00440 }
00441 }
00442
00443 UserType* userType = invitation->user()->userType();
00444 Requirement* req = d->type->requirementByType(userType);
00445
00446 if (req == 0)
00447 {
00448 throw tools::InvalidDataException(QString("No requirement for a user of "
00449 "type %1").arg(userType->name()));
00450 }
00451 if (d->nbActiveInvitations(userType) >= req->max)
00452 {
00453 throw tools::InvalidDataException(QString("Maximum number of invitations"
00454 " for users of type %1 reached")
00455 .arg(userType->name()));
00456 }
00457
00458 d->invitations << invitation;
00459 notify();
00460 emit dataChanged();
00461 }
00462
00463 void Task::removeInvitation(Invitation* invitation)
00464 {
00465 d->invitations.removeAll(invitation);
00466 notify();
00467 emit dataChanged();
00468 }
00469
00470 void Task::removeSubTask(Task* dependency)
00471 {
00472 if (d->subTasks.contains(dependency))
00473 {
00474 d->subTasks.removeAll(dependency);
00475 dependency->d->superTasks.removeAll(this);
00476
00477 emit dependency->dataChanged();
00478 emit dataChanged();
00479 }
00480 }
00481
00482 bool Task::dependsOn(const Task* other) const
00483 {
00484 if (other == this)
00485 return true;
00486
00487 Q_FOREACH (Task* dependency, d->subTasks)
00488 {
00489 if (dependency == other || dependency->dependsOn(other))
00490 return true;
00491 }
00492
00493 return false;
00494 }
00495
00496 bool Task::isAvailable() const
00497 {
00498 return dynamic_cast<TaskAvailable*>(d->state) != 0;
00499 }
00500
00501 bool Task::requirementsAreFulfilled() const
00502 {
00503 return d->type->requirementsAreFulfilled(this);
00504 }
00505
00506 bool Task::canBeSuccessful() const
00507 {
00508 return d->type->canBeSuccessful(this);
00509 }
00510
00511 TaskType* Task::taskType() const
00512 {
00513 return d->type;
00514 }
00515
00516 const Project* Task::project() const
00517 {
00518 return d->project;
00519 }
00520
00521 const User* Task::user() const
00522 {
00523 return d->user;
00524 }
00525
00526 void Task::setProject(Project* project)
00527 {
00528 d->state->setProject(this, project);
00529 }
00530
00531 void Task::_setProject(Project* project)
00532 {
00533 if (d->project != project)
00534 {
00535 if (d->project != 0)
00536 d->project->removeTask(this);
00537
00538 d->project = project;
00539
00540 if (d->project != 0)
00541 d->project->addTask(this);
00542
00543 emit dataChanged();
00544 }
00545 }
00546
00547 QList<const Task*> Task::subTasks() const
00548 {
00549 return tools::makeConstList(d->subTasks);
00550 }
00551
00552 QDateTime Task::earliestEndTime() const
00553 {
00554 QDateTime earliestStartTime = schedule().startDate();
00555 QDateTime tempStartTime;
00556
00557 Q_FOREACH(const Task* subtask, subTasks())
00558 {
00559 tempStartTime = subtask->earliestEndTime();
00560 if (tempStartTime > earliestStartTime)
00561 earliestStartTime = tempStartTime;
00562 }
00563
00564 return earliestStartTime.addSecs(schedule().duration().totalSeconds());
00565 }
00566
00567 QList<Task*> Task::superTasks()
00568 {
00569 return d->superTasks;
00570 }
00571
00572 QList<const Task*> Task::superTasks() const
00573 {
00574 return tools::makeConstList(d->superTasks);
00575 }
00576
00577 QList<const Task*> Task::allSuperTasks() const
00578 {
00579 QSet<const Task*> allSuperTasks = QSet<const Task*>::fromList(superTasks());
00580 Q_FOREACH (const Task* task, allSuperTasks)
00581 {
00582 allSuperTasks += QSet<const Task*>::fromList(task->allSuperTasks());
00583 }
00584 return allSuperTasks.toList();
00585 }
00586
00587 void Task::notify()
00588 {
00589 if (d->state != 0)
00590 {
00591 d->state->updateTask(this);
00592 }
00593 }
00594
00595 QList<StorableData*> Task::dependentData() const
00596 {
00597 QList<StorableData*> ret;
00598
00599 Q_FOREACH (Reservation* reservation, d->reservations)
00600 ret << reservation;
00601 Q_FOREACH (Invitation* invitation, d->invitations)
00602 ret << invitation;
00603
00604 return ret;
00605 }
00606
00607 bool TaskPrivate::violatesBusinessRule1(domain::Task* dependencyToAdd)
00608 {
00609 QDateTime depEndTime = dependencyToAdd->earliestEndTime();
00610
00611 if (q->schedule().dueDate() >=
00612 depEndTime.addSecs(q->schedule().duration().totalSeconds()))
00613 {
00614 return false;
00615 }
00616
00617 return true;
00618 }
00619
00620 QList<const domain::Task*>
00621 TaskPrivate::subTasksViolatingBusinessRule2(TaskState::SettableState state)
00622 {
00623 QList<const Task*> ret;
00624
00625 Q_FOREACH(const Task* subTask, q->subTasks())
00626 {
00627 if (subTask->state() == TaskState::Failed)
00628 {
00629 if (state != TaskState::Failed)
00630 ret << subTask;
00631 }
00632 else if (subTask->state() == TaskState::Unfinished)
00633 {
00634 if (state == TaskState::Successful)
00635 ret << subTask;
00636 }
00637 }
00638
00639 return ret;
00640 }
00641
00642 QList<const domain::Task*>
00643 TaskPrivate::superTasksViolatingBusinessRule2(TaskState::SettableState state)
00644 {
00645 QList<const Task*> ret;
00646
00647 if (state != TaskState::Successful)
00648 {
00649 Q_FOREACH(const Task* superTask, q->allSuperTasks())
00650 {
00651 if (state == TaskState::Unfinished)
00652 {
00653 if (superTask->state() == TaskState::Successful)
00654 ret << superTask;
00655 }
00656 else if (state == TaskState::Failed)
00657 {
00658 if (superTask->state() != TaskState::Failed )
00659 ret << superTask;
00660 }
00661 }
00662 }
00663
00664 return ret;
00665 }
00666
00667 bool TaskPrivate::violatesBusinessRule3(TaskState::SettableState newState)
00668 {
00669
00670 QDateTime currentTime = Clock::currentTime();
00671
00672 if (currentTime < schedule.startDate())
00673 {
00674 if (newState == TaskState::Successful)
00675 return true;
00676 }
00677 if (currentTime > schedule.dueDate())
00678 {
00679 if (newState == TaskState::Unfinished)
00680 return true;
00681 }
00682
00683 return false;
00684 }
00685
00686 unsigned TaskPrivate::nbReservations(ResourceType* resType)
00687 {
00688 unsigned ret = 0;
00689
00690 Q_FOREACH (Reservation* res, reservations)
00691 {
00692 if (res->resource()->resourceType() == resType)
00693 ret++;
00694 }
00695
00696 return ret;
00697 }
00698
00699 unsigned TaskPrivate::nbActiveInvitations(UserType* userType)
00700 {
00701 unsigned ret = 0;
00702
00703 Q_FOREACH (Invitation* invitation, invitations)
00704 {
00705 if (invitation->user()->userType() == userType &&
00706 invitation->state() != Invitation::Declined)
00707 {
00708 ret++;
00709 }
00710 }
00711
00712 return ret;
00713 }
00714
00715 }