import grails.util.Environment /** * Provides a service class for the Task domain class. * */ class TaskService { boolean transactional = false def authService def dateUtilService 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) } } } } /** * Determines and returns a list of possible task types for scheduled tasks. * @returns A list of the possible task types. */ def getScheduledTaskTypes() { def criteria = TaskType.createCriteria() def scheduledTaskTypes = criteria { and { eq('isActive', true) gt('id', 2L) } } } /** * Determines and returns a list of possible task priorites for Scheduled tasks. * @returns A list of the possible task priorites. */ def getScheduledTaskPriorities() { def criteria = TaskPriority.createCriteria() def scheduledTaskPriorities = [:] scheduledTaskPriorities.list = criteria { and { eq('isActive', true) gt('id', 1L) } } scheduledTaskPriorities.default = scheduledTaskPriorities.list.find { it.id == 4L } // 1-Normal. return scheduledTaskPriorities } /** * Determines and returns a list of possible task priorites for Unscheduled tasks. * @returns A map containing a list of the possible task priorites and the default priority. */ def getUnscheduledTaskPriorities() { def criteria = TaskPriority.createCriteria() def unscheduledTaskPriorities = [:] unscheduledTaskPriorities.list = criteria { and { eq('isActive', true) lt('id', 5L) ne('id', 1L) } } unscheduledTaskPriorities.default = unscheduledTaskPriorities.list.find { it.id == 3L } // 2-High. return unscheduledTaskPriorities } /** * 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 (if any error) and result.taskInstance. */ def save(params) { Task.withTransaction { status -> def result = [:] def fail = { Map m -> status.setRollbackOnly() if(result.taskInstance && m.field) result.taskInstance.errors.rejectValue(m.field, m.code) result.error = [ code: m.code, args: ["Task", params.id] ] return result } // Default status to "not started" if not supplied. params.taskStatus = params.taskStatus ?: TaskStatus.get(1) // Set budgetStatus. if(params.taskType?.id?.toLong() == 1 || params.taskType?.id?.toLong() == 2) // Immediate Callout or Unsheduled 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) return fail(field:"parentTask", code:"task.operationNotPermittedOnTaskInTrash") if(result.taskInstance.hasErrors() || !result.taskInstance.save()) return fail(code:"default.create.failure") def taskModification = new TaskModification(person: authService.currentUser, taskModificationType: TaskModificationType.get(1), task: taskInstance) if(taskModification.hasErrors() || !taskModification.save()) return fail(field:"taskModifications", code:"task.modifications.failedToSave") //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) return fail(field:"assignedGroups", code:"task.assignedGroups.failedToSave") } } //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) return fail(field:"assignedPersons", code:"task.assignedPersons.failedToSave") } } // Success. return result } //end withTransaction } // end save() /** * Creates a subTask copying sane attributes from the parentTask unless otherwise specified in params. * The targetStartDate and targetCompletionDate default to today since that is the sane thing to do. * 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, recurrence can be used to create similar tasks. * @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 ?: dateUtilService.today p.targetCompletionDate = params.targetCompletionDate ?: dateUtilService.today p.scheduled = params.scheduled ?: parentTask.scheduled 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. // 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 = save(p) // Approve. if(!result.error && parentTask.approved) { p = [:] p.id = result.taskInstance.id approve(p) } // Success. return result } // end createSubTask() /** * In production tasks are NEVER deleted, only the trash flag is set! * However during testing it may be required to delete a task and that * is why this method exists. */ def delete(params) { Task.withTransaction { status -> def result = [:] def fail = { Map m -> status.setRollbackOnly() if(result.taskInstance && m.field) result.taskInstance.errors.rejectValue(m.field, m.code) result.error = [ code: m.code, args: ["Task", params.id] ] return result } if(Environment.current == Environment.PRODUCTION) return fail(code:"task.delete.failure.production") result.taskInstance = Task.get(params.id) if(!result.taskInstance) return fail(code:"default.not.found") // Handle taskModifications. def taskModifications = TaskModification.findAllByTask(result.taskInstance) taskModifications.each() { result.taskInstance.removeFromTaskModifications(it) it.delete() } // Handle assignedPersons. def taskAssignedPersons = AssignedPerson.findAllByTask(result.taskInstance) taskAssignedPersons.each() { result.taskInstance.removeFromAssignedPersons(it) it.delete() } // Handle assignedGroups. def taskAssignedGroups = AssignedGroup.findAllByTask(result.taskInstance) taskAssignedGroups.each() { result.taskInstance.removeFromAssignedGroups(it) it.delete() } if(result.error) return result try { result.taskInstance.delete(flush:true) return result //Success. } catch(org.springframework.dao.DataIntegrityViolationException e) { return fail(code:"default.delete.failure") } } // end withTransaction } // delete() /** * 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 saveEntry(params) { Task.withTransaction { status -> def result = [:] def fail = { Map m -> status.setRollbackOnly() if(result.taskInstance && m.field) result.taskInstance.errors.rejectValue(m.field, m.code) result.error = [ code: m.code, args: ["Entry", params.id] ] return result } result.entryInstance = new Entry(params) result.entryInstance.enteredBy = authService.currentUser def taskInstance if(result.entryInstance.task?.id) { result.taskId = result.entryInstance.task.id taskInstance = Task.lock(result.entryInstance.task.id) } if(!taskInstance) return fail(field:"task", code:"task.notFound") if(result.entryInstance.hasErrors() || !result.entryInstance.save()) return fail(code:"default.create.failure") if(taskInstance.taskStatus.id == 3) return fail(field:"task", code:"task.operationNotPermittedOnCompleteTask") // If task status is "Not Started" and entry type is "Work Done" and time has been booked. // Then we create the started modification and set task status. if(taskInstance.taskStatus.id == 1 && result.entryInstance.entryType.id == 3 && (result.entryInstance.durationHour + result.entryInstance.durationMinute > 0)) { // Create the "Started" task modification, this provides the "Actual Started Date". def taskModification = new TaskModification(person: authService.currentUser, taskModificationType: TaskModificationType.read(2), task: taskInstance) if(taskModification.hasErrors() || !taskModification.save()) return fail(field:"task", code:"task.modifications.failedToSave") // Set task status to "In Progress". taskInstance.taskStatus = TaskStatus.read(2) if(taskInstance.hasErrors() || !taskInstance.save()) return fail(field:"task", code:"task.failedToSave") } // Success. return result } // end withTransaction } // end saveEntry() /** * Updates an existing task. * @param params The params to update for task with id of params.id. * @returns A map containing result.error (if any error) and result.taskInstance (if available). */ def update(params) { Task.withTransaction { status -> def result = [:] def fail = { Map m -> status.setRollbackOnly() if(result.taskInstance && m.field) result.taskInstance.errors.rejectValue(m.field, m.code) result.error = [ code: m.code, args: ["Task", params.id] ] return result } result.taskInstance = Task.get(params.id) if(!result.taskInstance) return fail('id', "task.notFound") // Optimistic locking check. if(params.version) { if(result.taskInstance.version > params.version.toLong()) return fail(field:"version", code:"default.optimistic.locking.failure") } result.taskInstance.properties = params if(result.taskInstance.hasErrors() || !result.taskInstance.save()) return fail(code:"default.update.failure") def taskModification = new TaskModification(person:authService.currentUser, taskModificationType: TaskModificationType.get(3), task: result.taskInstance) if(taskModification.hasErrors() || !taskModification.save()) return fail(code:"task.modifications.failedToSave") // Success. 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 (if any error) and result.taskInstance (if available). */ def complete(params) { Task.withTransaction { status -> def result = [:] def fail = { Map m -> status.setRollbackOnly() if(result.taskInstance && m.field) result.taskInstance.errors.rejectValue(m.field, m.code) result.error = [ code: m.code, args: ["Task", params.id] ] return result } result.taskInstance = Task.get(params.id) if(!result.taskInstance) return fail(code:"default.not.found") // Optimistic locking check. if(params.version) { if(result.taskInstance.version > params.version.toLong()) return fail(field:"version", code:"default.optimistic.locking.failure") } result.taskInstance.taskStatus = TaskStatus.get(3) result.taskInstance.attentionFlag = false result.taskInstance.taskRecurringSchedule?.enabled = false if(result.taskInstance.hasErrors() || !result.taskInstance.save()) return fail(code:"default.update.failure") def taskModification = new TaskModification(person:authService.currentUser, taskModificationType: TaskModificationType.get(4), task: result.taskInstance) if(taskModification.hasErrors() || !taskModification.save()) return fail(code:"task.modifications.failedToSave") // Success. return result } //end withTransaction } // end complete() /** * Sets the attentionFlag on an existing task. * @param params The params for task with id of params.id. * @returns A map containing result.error (if any error) and result.taskInstance (if available). */ def setAttentionFlag(params) { Task.withTransaction { status -> def result = [:] def fail = { Map m -> status.setRollbackOnly() if(result.taskInstance && m.field) result.taskInstance.errors.rejectValue(m.field, m.code) result.error = [ code: m.code, args: ["Task", params.id] ] return result } result.taskInstance = Task.get(params.id) if(!result.taskInstance) return fail(code:"default.not.found") // Optimistic locking check. if(params.version) { if(result.taskInstance.version > params.version.toLong()) return fail(field:"version", code:"default.optimistic.locking.failure") } result.taskInstance.attentionFlag = true if(result.taskInstance.hasErrors() || !result.taskInstance.save()) return fail(code:"default.update.failure") def taskModification = new TaskModification(person:authService.currentUser, taskModificationType: TaskModificationType.get(12), task: result.taskInstance) if(taskModification.hasErrors() || !taskModification.save()) return fail(code:"task.modifications.failedToSave") // Success. return result } //end withTransaction } // end flag() /** * Clears the attentionFlag on an existing task. * @param params The params for task with id of params.id. * @returns A map containing result.error (if any error) and result.taskInstance (if available). */ def clearAttentionFlag(params) { Task.withTransaction { status -> def result = [:] def fail = { Map m -> status.setRollbackOnly() if(result.taskInstance && m.field) result.taskInstance.errors.rejectValue(m.field, m.code) result.error = [ code: m.code, args: ["Task", params.id] ] return result } result.taskInstance = Task.get(params.id) if(!result.taskInstance) return fail(code:"default.not.found") // Optimistic locking check. if(params.version) { if(result.taskInstance.version > params.version.toLong()) return fail(field:"version", code:"default.optimistic.locking.failure") } result.taskInstance.attentionFlag = false if(result.taskInstance.hasErrors() || !result.taskInstance.save()) return fail(code:"default.update.failure") def taskModification = new TaskModification(person:authService.currentUser, taskModificationType: TaskModificationType.get(13), task: result.taskInstance) if(taskModification.hasErrors() || !taskModification.save()) return fail(code:"task.modifications.failedToSave") // Success. return result } //end withTransaction } // end clearFlag() /** * Reopens an existing task. * @param params The params for task with id of params.id. * @returns A map containing result.error (if any error) and result.taskInstance (if available). */ def reopen(params) { Task.withTransaction { status -> def result = [:] def fail = { Map m -> status.setRollbackOnly() if(result.taskInstance && m.field) result.taskInstance.errors.rejectValue(m.field, m.code) result.error = [ code: m.code, args: ["Task", params.id] ] return result } result.taskInstance = Task.get(params.id) if(!result.taskInstance) return fail(code:"default.not.found") // Optimistic locking check. if(params.version) { if(result.taskInstance.version > params.version.toLong()) return fail(field:"version", code:"default.optimistic.locking.failure") } def isInProgress = false result.taskInstance.entries.each() { if(it.entryType.id == 3 && (it.durationHour + it.durationMinute > 0) ) isInProgress = true } if(isInProgress) result.taskInstance.taskStatus = TaskStatus.read(2) // In Progress else result.taskInstance.taskStatus = TaskStatus.read(1) // Not Started if(result.taskInstance.hasErrors() || !result.taskInstance.save()) return fail(code:"default.update.failure") def taskModification = new TaskModification(person:authService.currentUser, taskModificationType: TaskModificationType.get(5), task: result.taskInstance) if(taskModification.hasErrors() || !taskModification.save()) return fail(code:"task.modifications.failedToSave") // Success. 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 (if any error) and result.taskInstance (if available). */ def trash(params) { Task.withTransaction { status -> def result = [:] def fail = { Map m -> status.setRollbackOnly() if(result.taskInstance && m.field) result.taskInstance.errors.rejectValue(m.field, m.code) result.error = [ code: m.code, args: ["Task", params.id] ] return result } result.taskInstance = Task.get(params.id) if(!result.taskInstance) return fail(code:"default.not.found") // Optimistic locking check. if(params.version) { if(result.taskInstance.version > params.version.toLong()) return fail(field:"version", code:"default.optimistic.locking.failure") } result.taskInstance.trash = true result.taskInstance.attentionFlag = false result.taskInstance.taskRecurringSchedule?.enabled = false if(result.taskInstance.hasErrors() || !result.taskInstance.save()) return fail(code:"default.update.failure") def taskModification = new TaskModification(person:authService.currentUser, taskModificationType: TaskModificationType.get(6), task: result.taskInstance) if(taskModification.hasErrors() || !taskModification.save()) return fail(code:"task.modifications.failedToSave") // Success. 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 (if any error) and result.taskInstance (if available). */ def restore(params) { Task.withTransaction { status -> def result = [:] def fail = { Map m -> status.setRollbackOnly() if(result.taskInstance && m.field) result.taskInstance.errors.rejectValue(m.field, m.code) result.error = [ code: m.code, args: ["Task", params.id] ] return result } result.taskInstance = Task.get(params.id) if(!result.taskInstance) return fail(code:"default.not.found") // Optimistic locking check. if(params.version) { if(result.taskInstance.version > params.version.toLong()) return fail(field:"version", code:"default.optimistic.locking.failure") } result.taskInstance.trash = false if(result.taskInstance.hasErrors() || !result.taskInstance.save()) return fail(code:"default.update.failure") def taskModification = new TaskModification(person:authService.currentUser, taskModificationType: TaskModificationType.get(7), task: result.taskInstance) if(taskModification.hasErrors() || !taskModification.save()) return fail(code:"task.modifications.failedToSave") // Success. 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 (if any error) and result.taskInstance (if available). */ def approve(params) { Task.withTransaction { status -> def result = [:] def fail = { Map m -> status.setRollbackOnly() if(result.taskInstance && m.field) result.taskInstance.errors.rejectValue(m.field, m.code) result.error = [ code: m.code, args: ["Task", params.id] ] return result } result.taskInstance = Task.get(params.id) if(!result.taskInstance) return fail(code:"default.not.found") // Optimistic locking check. if(params.version) { if(result.taskInstance.version > params.version.toLong()) return fail(field:"version", code:"default.optimistic.locking.failure") } result.taskInstance.approved = true if(result.taskInstance.hasErrors() || !result.taskInstance.save()) return fail(code:"default.update.failure") def taskModification = new TaskModification(person:authService.currentUser, taskModificationType: TaskModificationType.get(8), task: result.taskInstance) if(taskModification.hasErrors() || !taskModification.save()) return fail(code:"task.modifications.failedToSave") // Success. 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 (if any error) and result.taskInstance (if available). */ def renegeApproval(params) { Task.withTransaction { status -> def result = [:] def fail = { Map m -> status.setRollbackOnly() if(result.taskInstance && m.field) result.taskInstance.errors.rejectValue(m.field, m.code) result.error = [ code: m.code, args: ["Task", params.id] ] return result } result.taskInstance = Task.get(params.id) if(!result.taskInstance) return fail(code:"default.not.found") // Optimistic locking check. if(params.version) { if(result.taskInstance.version > params.version.toLong()) return fail(field:"version", code:"default.optimistic.locking.failure") } result.taskInstance.approved = false if(result.taskInstance.hasErrors() || !result.taskInstance.save()) return fail(code:"default.update.failure") def taskModification = new TaskModification(person:authService.currentUser, taskModificationType: TaskModificationType.get(9), task: result.taskInstance) if(taskModification.hasErrors() || !taskModification.save()) return fail(code:"task.modifications.failedToSave") // Success. return result } //end withTransaction } // end renegeApproval() /** * Creates a new unscheduled breakin task with the given params. * @param params The params to use when creating the new task. * @returns A map containing result.error (if any error) and result.taskInstance. */ def saveUnscheduled(params) { Task.withTransaction { status -> def result = [:] def fail = { Map m -> status.setRollbackOnly() if(result.taskInstance && m.field) result.taskInstance.errors.rejectValue(m.field, m.code) result.error = [ code: m.code, args: ["Task", params.id] ] return result } // If not supplied. if(!params.taskStatus) params.taskStatus = TaskStatus.get(1) // Not Started. result.taskInstance = new Task(params) // Always for an unscheduled breakin.. result.taskInstance.taskType = TaskType.get(2) // Unscheduled Breakin. result.taskInstance.taskBudgetStatus = TaskBudgetStatus.get(1) // Unplanned. if(result.taskInstance.hasErrors() || !result.taskInstance.save()) fail(code:"default.create.failure") if(!result.error) { def taskModification = new TaskModification(person: authService.currentUser, taskModificationType: TaskModificationType.get(1), // Created. task: result.taskInstance) if(taskModification.hasErrors() || !taskModification.save()) fail(field:"taskModifications", code:"task.modifications.failedToSave") } // Success. return result } //end withTransaction } // end saveUnscheduled() /** * Creates a new immediate callout task with the given params. * @param params The params to use when creating the new task. * @returns A map containing result.error (if any error) and result.taskInstance. */ def saveImmediateCallout(params) { Task.withTransaction { status -> def result = [:] def fail = { Map m -> status.setRollbackOnly() if(result.taskInstance && m.field) result.taskInstance.errors.rejectValue(m.field, m.code) result.error = [ code: m.code, args: ["Task", params.id] ] return result } // If not supplied. if(!params.taskStatus) params.taskStatus = TaskStatus.get(1) // Not Started. result.taskInstance = new Task(params) // Always for an immediate callout. result.taskInstance.taskType = TaskType.get(1) // Immediate Callout. result.taskInstance.taskBudgetStatus = TaskBudgetStatus.get(1) // Unplanned. result.taskInstance.taskPriority = TaskPriority.get(1) // Immediate. result.taskInstance.taskGroup = TaskGroup.get(1) // Engineering Activites. result.taskInstance.approved = true result.taskInstance.leadPerson = authService.currentUser result.taskInstance.targetCompletionDate = result.taskInstance.targetStartDate if(result.taskInstance.hasErrors() || !result.taskInstance.save()) fail(code:"default.create.failure") if(!result.error) { def taskModification = new TaskModification(person: authService.currentUser, taskModificationType: TaskModificationType.get(1), // Created. task: result.taskInstance) if(taskModification.hasErrors() || !taskModification.save()) fail(field:"taskModifications", code:"task.modifications.failedToSave") } def productionReference if(params.entryFault.productionReference.id.isLong()) productionReference = ProductionReference.get(params.entryFault.productionReference.id.toLong()) def faultParams = [task: result.taskInstance, entryType: EntryType.get(1), comment: params.entryFault.comment, dateDone: result.taskInstance.targetStartDate, productionReference: productionReference, durationHour: params.entryFault.durationHour, durationMinute: params.entryFault.durationMinute] def faultResult = saveEntry(faultParams) result.entryFaultInstance = faultResult.entryInstance def causeParams = [task: result.taskInstance, entryType: EntryType.get(2), dateDone: result.taskInstance.targetStartDate, comment: params.entryCause.comment] def causeResult = saveEntry(causeParams) result.entryCauseInstance = causeResult.entryInstance def workDoneParams = [task: result.taskInstance, entryType: EntryType.get(3), comment: params.entryWorkDone.comment, dateDone: result.taskInstance.targetStartDate, durationHour: params.entryWorkDone.durationHour, durationMinute: params.entryWorkDone.durationMinute] def workDoneResult = saveEntry(workDoneParams) result.entryWorkDoneInstance = workDoneResult.entryInstance if(result.error) return result if(causeResult.error) return fail(code: "default.create.failure") if(faultResult.error) return fail(code: "default.create.failure") if(workDoneResult.error) return fail(code: "default.create.failure") // Success. return result } //end withTransaction } // end saveImmediateCallout() } // end TaskService