/** * Provides a service class for the Task domain class. * */ class TaskService { boolean transactional = false def personService def assignedGroupService def assignedPersonService /** * Determines and returns a possible parent list for a task. * @todo Create and use another method that limits the results to say the latest 20 or 100 tasks? * @param taskInstance The task to use when determining the possible parent list. * @returns A list of the possible parents. */ def possibleParentList(taskInstance) { def criteria = taskInstance.createCriteria() def possibleParentList = criteria { and { notEqual('trash', true) notEqual('id', taskInstance.id) taskInstance.subTasks.each() { notEqual('id', it.id) } } } } /** * Creates a new task with the given params. * @param params The params to use when creating the new task. * @returns A map containing result.error=true (if any error) and result.taskInstance. */ def create(params) { Task.withTransaction { status -> def result = [:] // Default status to "not started" if not supplied. params.taskStatus = params.taskStatus ?: TaskStatus.get(1) // Set budgetStatus. if(params.taskType?.id?.toLong() == 1) // Unscheduled Breakin. params.taskBudgetStatus = params.taskBudgetStatus ?: TaskBudgetStatus.get(1) // Unplanned. else params.taskBudgetStatus = params.taskBudgetStatus ?: TaskBudgetStatus.get(2) // Planned. def taskInstance = new Task(params) result.taskInstance = taskInstance if(result.taskInstance.parentTask?.trash) { status.setRollbackOnly() result.taskInstance.errors.rejectValue("parentTask", "task.operationNotPermittedOnTaskInTrash") result.error = true return result } if(taskInstance.save()) { def taskModification = new TaskModification(person: personService.currentUser, taskModificationType: TaskModificationType.get(1), task: taskInstance) if(!taskModification.save()) { status.setRollbackOnly() taskInstance.errors.rejectValue("taskModifications", "task.modifications.failedToSave") result.error = true return result } //Add the assignedGroups, provided by a new ArrayList(task.assignedGroups) if(params.assignedGroups) { def assignedGroupsResult def assignedGroupParams = [:] params.assignedGroups.each() { assignedGroupParams = [personGroup: it.personGroup, task: taskInstance, estimatedHour: it.estimatedHour, estimatedMinute: it.estimatedMinute] assignedGroupsResult = assignedGroupService.save(assignedGroupParams) if(assignedGroupsResult.error) { status.setRollbackOnly() taskInstance.errors.rejectValue("assignedGroups", "task.assignedGroups.failedToSave") result.error = true return result } } } //Add the assignedPersons, provided by a new ArrayList(task.assignedPersons) if(params.assignedPersons) { def assignedPersonsResult def assignedPersonsParams = [:] params.assignedPersons.each() { assignedPersonsParams = [person: it.person, task: taskInstance, estimatedHour: it.estimatedHour, estimatedMinute: it.estimatedMinute] assignedPersonsResult = assignedPersonService.save(assignedPersonsParams) if(assignedPersonsResult.error) { status.setRollbackOnly() taskInstance.errors.rejectValue("assignedPersons", "task.assignedPersons.failedToSave") result.error = true return result } } } // Success. return result } else { result.error = true return result } } //end withTransaction } // end create() /** * Creates a subTask copying sane attributes from the parentTask unless otherwise specified in params. * The taskProcedure is only assigned to the sub task if supplied in params. * The assignedPersons and assignedGroups are only added to the sub task if supplied in params. * Collections in params must be supplied as new ArrayList's. * This method is not intended to be a copyTask method. * There should be no reason to copy tasks, try to find a better solution. * @param parentTask The parent task to get attributes from, also set as the parent. * @param params Overrides the parent task values if specified. * @returns A map containing result.error=true (if any error) and result.taskInstance. */ def createSubTask(parentTask, params = [:]) { def result = [:] //Make our new Task a subTask and set the required properites. def p = [:] p.parentTask = parentTask p.description = params.description ?: parentTask.description p.comment = params.comment ?: parentTask.comment p.targetStartDate = params.targetStartDate ?: parentTask.targetStartDate p.targetCompletionDate = params.targetCompletionDate ?: parentTask.targetCompletionDate p.taskGroup = params.taskGroup ?: parentTask.taskGroup p.taskStatus = TaskStatus.get(1) // A new subTask must always be "Not Started". p.taskPriority = parentTask.taskPriority p.taskType = params.taskType ?: parentTask.taskType p.leadPerson = params.leadPerson ?: parentTask.leadPerson p.primaryAsset = params.primaryAsset ?: parentTask.primaryAsset p.associatedAssets = params.associatedAssets ?: new ArrayList(parentTask.associatedAssets) // Collection. // Only if supplied, otherwise this would be copying. if(params.scheduled) p.scheduled = params.scheduled if(params.approved) p.approved = params.approved // Supplied by recurring tasks. if(params.taskProcedure) p.taskProcedure = params.taskProcedure if(params.assignedGroups) p.assignedGroups = params.assignedGroups // Collection. if(params.assignedPersons) p.assignedPersons = params.assignedPersons // Collection. // trash: A new subTask must always have trash=false, which is already the domain class default. // These would be considered copying, hence not done. // taskRecurringSchedule, entries, taskModifications, subTasks, inventoryMovements. // Create the sub task and return the result. result = create(p) } // end createSubTask() /** * Creates a new task entry. * @param params The params to use when creating the new entry. * @returns A map containing result.error=true (if any error), result.entryInstance and result.taskId. */ def createEntry(params) { Task.withTransaction { status -> def result = [:] result.entryInstance = new Entry(params) result.entryInstance.enteredBy = personService.currentUser if(result.entryInstance.validate()) { result.taskId = result.entryInstance.task.id def taskInstance = Task.lock(result.taskId) if(!taskInstance) { status.setRollbackOnly() result.entryInstance.errors.rejectValue('task', "task.notFound") result.error = true return result } if(taskInstance.taskStatus.id == 3) { status.setRollbackOnly() result.entryInstance.errors.rejectValue('task', "task.operationNotPermittedOnCompleteTask") result.error = true return result } // If task status is "Not Started" and entry type is "Work Done" then we create the started modification and set the status. if(taskInstance.taskStatus.id == 1 && result.entryInstance.entryType.id == 2) { // Create the "Started" task modification, this provides the "Actual started date". def taskModification = new TaskModification(person: personService.currentUser, taskModificationType: TaskModificationType.get(2), task: taskInstance) if(!taskModification.save()) { status.setRollbackOnly() taskInstance.errors.rejectValue("task", "task.modifications.failedToSave") result.error = true return result } // Set task status to "In progress". taskInstance.taskStatus = TaskStatus.get(2) if(!taskInstance.save()) { status.setRollbackOnly() result.entryInstance.errors.rejectValue("task", "task.failedToSave") result.error = true return result } } if(!result.entryInstance.save()) { status.setRollbackOnly() result.error = true return result } // If we get here all went well. return result } else { result.error = true return result } } //end withTransaction } // end create() /** * Updates an existing task. * @param params The params to update for task with id of params.id. * @returns A map containing result.error=true (if any error) and result.taskInstance (if available). */ def update(params) { Task.withTransaction { status -> def result = [:] def fail = { Object[] args -> status.setRollbackOnly() if(args.size() == 2) result.taskInstance.errors.rejectValue(args[0], args[1]) result.error = true return result } result.taskInstance = Task.get(params.id) if(!result.taskInstance) return fail('id', "task.notFound") // Optimistic locking check. if(params.version) { def version = params.version.toLong() if(result.taskInstance.version > version) return fail("version", "default.optimistic.locking.failure") } result.taskInstance.properties = params if(result.taskInstance.hasErrors() || !result.taskInstance.save()) return fail() def taskModification = new TaskModification(person:personService.currentUser, taskModificationType: TaskModificationType.get(3), task: result.taskInstance) if(!taskModification.save()) return fail("taskModifications", "task.modifications.failedToSave") // If we get here all went well. return result } //end withTransaction } // end update() /** * Completes an existing task. * @param params The params for task with id of params.id. * @returns A map containing result.error=true (if any error) and result.taskInstance. */ def complete(params) { Task.withTransaction { status -> def result = [:] result.taskInstance = Task.get(params.id) if(result.taskInstance) { // Optimistic locking check. if(params.version) { def version = params.version.toLong() if(result.taskInstance.version > version) { status.setRollbackOnly() result.taskInstance.errors.rejectValue("version", "task.optimistic.locking.failure", "Another user has updated this Task while you were editing.") result.error = true return result } } result.taskInstance.taskStatus = TaskStatus.get(3) result.taskInstance.taskRecurringSchedule?.enabled = false if(result.taskInstance.save()) { def taskModification = new TaskModification(person:personService.currentUser, taskModificationType: TaskModificationType.get(4), task: result.taskInstance) if(taskModification.save()) { // All went well. return result } else { status.setRollbackOnly() result.taskInstance.errors.rejectValue("taskModifications", "task.modifications.failedToSave") result.error = true return result } } } // Something failed. status.setRollbackOnly() result.error = true return result } //end withTransaction } // end complete() /** * Reopens an existing task. * @param params The params for task with id of params.id. * @returns A map containing result.error=true (if any error) and result.taskInstance. */ def reopen(params) { Task.withTransaction { status -> def result = [:] result.taskInstance = Task.get(params.id) if(result.taskInstance) { // Optimistic locking check. if(params.version) { def version = params.version.toLong() if(result.taskInstance.version > version) { status.setRollbackOnly() result.taskInstance.errors.rejectValue("version", "task.optimistic.locking.failure", "Another user has updated this Task while you were editing.") result.error = true return result } } result.taskInstance.taskStatus = TaskStatus.get(2) if(result.taskInstance.save()) { def taskModification = new TaskModification(person:personService.currentUser, taskModificationType: TaskModificationType.get(5), task: result.taskInstance) if(taskModification.save()) { // All went well. return result } else { status.setRollbackOnly() result.taskInstance.errors.rejectValue("taskModifications", "task.modifications.failedToSave") result.error = true return result } } } // Something failed. status.setRollbackOnly() result.error = true return result } //end withTransaction } // end reopen() /** * Move a task to the trash. * @param params The params for task with id of params.id. * @returns A map containing result.error=true (if any error) and result.taskInstance. */ def trash(params) { Task.withTransaction { status -> def result = [:] result.taskInstance = Task.get(params.id) if(result.taskInstance) { // Optimistic locking check. if(params.version) { def version = params.version.toLong() if(result.taskInstance.version > version) { status.setRollbackOnly() result.taskInstance.errors.rejectValue("version", "task.optimistic.locking.failure", "Another user has updated this Task while you were editing.") result.error = true return result } } result.taskInstance.trash = true result.taskInstance.taskRecurringSchedule?.enabled = false if(result.taskInstance.save()) { def taskModification = new TaskModification(person:personService.currentUser, taskModificationType: TaskModificationType.get(6), task: result.taskInstance) if(taskModification.save()) { // All went well. return result } else { status.setRollbackOnly() result.taskInstance.errors.rejectValue("taskModifications", "task.modifications.failedToSave") result.error = true return result } } } // Something failed. status.setRollbackOnly() result.error = true return result } //end withTransaction } // end trash() /** * Restore a task from the trash. * @param params The params for task with id of params.id. * @returns A map containing result.error=true (if any error) and result.taskInstance. */ def restore(params) { Task.withTransaction { status -> def result = [:] result.taskInstance = Task.get(params.id) if(result.taskInstance) { // Optimistic locking check. if(params.version) { def version = params.version.toLong() if(result.taskInstance.version > version) { status.setRollbackOnly() result.taskInstance.errors.rejectValue("version", "task.optimistic.locking.failure", "Another user has updated this Task while you were editing.") result.error = true return result } } result.taskInstance.trash = false if(result.taskInstance.save()) { def taskModification = new TaskModification(person:personService.currentUser, taskModificationType: TaskModificationType.get(7), task: result.taskInstance) if(taskModification.save()) { // All went well. return result } else { status.setRollbackOnly() result.taskInstance.errors.rejectValue("taskModifications", "task.modifications.failedToSave") result.error = true return result } } } // Something failed. status.setRollbackOnly() result.error = true return result } //end withTransaction } // end restore() /** * Approve a task. * @param params The params for task with id of params.id. * @returns A map containing result.error=true (if any error) and result.taskInstance. */ def approve(params) { Task.withTransaction { status -> def result = [:] result.taskInstance = Task.get(params.id) if(result.taskInstance) { // Optimistic locking check. if(params.version) { def version = params.version.toLong() if(result.taskInstance.version > version) { status.setRollbackOnly() result.taskInstance.errors.rejectValue("version", "task.optimistic.locking.failure", "Another user has updated this Task while you were editing.") result.error = true return result } } result.taskInstance.approved = true if(result.taskInstance.save()) { def taskModification = new TaskModification(person:personService.currentUser, taskModificationType: TaskModificationType.get(8), task: result.taskInstance) if(taskModification.save()) { // All went well. return result } else { status.setRollbackOnly() result.taskInstance.errors.rejectValue("taskModifications", "task.modifications.failedToSave") result.error = true return result } } } // Something failed. status.setRollbackOnly() result.error = true return result } //end withTransaction } // end approve() /** * Remove a previously given approval from a task. * @param params The params for task with id of params.id. * @returns A map containing result.error=true (if any error) and result.taskInstance. */ def renegeApproval(params) { Task.withTransaction { status -> def result = [:] result.taskInstance = Task.get(params.id) if(result.taskInstance) { // Optimistic locking check. if(params.version) { def version = params.version.toLong() if(result.taskInstance.version > version) { status.setRollbackOnly() result.taskInstance.errors.rejectValue("version", "task.optimistic.locking.failure", "Another user has updated this Task while you were editing.") result.error = true return result } } result.taskInstance.approved = false if(result.taskInstance.save()) { def taskModification = new TaskModification(person:personService.currentUser, taskModificationType: TaskModificationType.get(9), task: result.taskInstance) if(taskModification.save()) { // All went well. return result } else { status.setRollbackOnly() result.taskInstance.errors.rejectValue("taskModifications", "task.modifications.failedToSave") result.error = true return result } } } // Something failed. status.setRollbackOnly() result.error = true return result } //end withTransaction } // end renegeApproval() } // end TaskService