Skip to content

Commit

Permalink
End holds set with timed hold tasks (#10687)
Browse files Browse the repository at this point in the history
Connects #9207

### Description
- interface to allow users to end holds set with `TimedHoldTask`s
- consolidates task action data functions in a new `TaskActionRepository` class
- updating the parent task cancels the `TimedHoldTask`
- a `TimedHoldTask` is invalid if it has more than one `TaskTimer`
  • Loading branch information
tomas-nava authored and va-bot committed May 10, 2019
1 parent 27c223d commit dc71939
Show file tree
Hide file tree
Showing 22 changed files with 763 additions and 331 deletions.
17 changes: 17 additions & 0 deletions app/controllers/tasks/end_hold_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# frozen_string_literal: true

class Tasks::EndHoldController < TasksController
def create
task.cancel_timed_hold

render json: { tasks: json_tasks(task.appeal.tasks.includes(*task_includes)) }
rescue ActiveRecord::RecordInvalid => error
invalid_record_error(error.record)
end

private

def task
@task ||= Task.find(params[:task_id])
end
end
24 changes: 3 additions & 21 deletions app/models/legacy_tasks/legacy_task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,10 @@ def available_actions_unwrapper(user, role)
end

def build_action_hash(action, user)
{ label: action[:label], value: action[:value], data: action[:func] ? send(action[:func], user) : nil }
end

def add_admin_action_data(_user)
{
redirect_after: "/queue",
selected: nil,
options: Constants::CO_LOCATED_ADMIN_ACTIONS.map do |key, value|
{
label: value,
value: key
}
end,
type: ColocatedTask.name
}
end

def assign_to_attorney_data(_user)
{
selected: nil,
options: nil,
type: AttorneyLegacyTask.name
label: action[:label],
value: action[:value],
data: action[:func] ? TaskActionRepository.send(action[:func], self, user) : nil
}
end

Expand Down
10 changes: 8 additions & 2 deletions app/models/serializers/work_queue/task_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ class WorkQueue::TaskSerializer
attribute :started_at
attribute :created_at
attribute :closed_at
attribute :placed_on_hold_at
attribute :on_hold_duration
attribute :instructions
attribute :appeal_type
attribute :timeline_title
Expand All @@ -41,6 +39,14 @@ class WorkQueue::TaskSerializer
}
end

attribute :placed_on_hold_at do |object|
object.placed_on_hold_at || object.calculated_placed_on_hold_at
end

attribute :on_hold_duration do |object|
object.on_hold_duration || object.calculated_on_hold_duration
end

attribute :docket_name do |object|
object.appeal.try(:docket_name)
end
Expand Down
174 changes: 37 additions & 137 deletions app/models/task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class Task < ApplicationRecord
before_update :set_timestamps
after_update :update_parent_status, if: :task_just_closed_and_has_parent?
after_update :update_children_status_after_closed, if: :task_just_closed?
after_update :cancel_timed_hold, unless: :task_just_placed_on_hold?

enum status: {
Constants.TASK_STATUSES.assigned.to_sym => Constants.TASK_STATUSES.assigned,
Expand Down Expand Up @@ -65,12 +66,17 @@ def active_with_no_children?
active? && children.empty?
end

# available_actions() returns an array of options from selected by the subclass
# from TASK_ACTIONS that looks something like:
# available_actions() returns an array of options selected by
# the subclass from TASK_ACTIONS that looks something like:
# [ { "label": "Assign to person", "value": "modal/assign_to_person", "func": "assignable_users" }, ... ]
def available_actions_unwrapper(user)
actions = actions_available?(user) ? available_actions(user).map { |action| build_action_hash(action, user) } : []

# Add the cancel timed hold option to the set of actions here.
if actions.any? && on_timed_hold?
actions.push(build_action_hash(Constants.TASK_ACTIONS.END_TIMED_HOLD.to_h, user))
end

# Make sure each task action has a unique URL so we can determine which action we are selecting on the frontend.
if actions.length > actions.pluck(:value).uniq.length
fail Caseflow::Error::DuplicateTaskActionPaths, task_id: id, user_id: user.id, labels: actions.pluck(:label)
Expand All @@ -80,12 +86,16 @@ def available_actions_unwrapper(user)
end

def build_action_hash(action, user)
{ label: action[:label], value: action[:value], data: action[:func] ? send(action[:func], user) : nil }
{
label: action[:label],
value: action[:value],
data: action[:func] ? TaskActionRepository.send(action[:func], self, user) : nil
}
end

# A wrapper around actions_allowable that also disallows doing actions to on_hold tasks.
def actions_available?(user)
return false if status == Constants.TASK_STATUSES.on_hold
return false if status == Constants.TASK_STATUSES.on_hold && !on_timed_hold?

actions_allowable?(user)
end
Expand All @@ -111,6 +121,27 @@ def children_attorney_tasks
children.where(type: AttorneyTask.name)
end

def on_timed_hold?
!active_child_timed_hold_task.nil?
end

def active_child_timed_hold_task
children.active.find_by(type: TimedHoldTask.name)
end

def cancel_timed_hold
active_child_timed_hold_task&.update!(status: Constants.TASK_STATUSES.cancelled)
end

def calculated_placed_on_hold_at
active_child_timed_hold_task&.timer_start_time
end

def calculated_on_hold_duration
timed_hold_task = active_child_timed_hold_task
(timed_hold_task&.timer_end_time&.to_date &.- timed_hold_task&.timer_start_time&.to_date)&.to_i
end

def self.recently_closed
inactive.where(closed_at: (Time.zone.now - 2.weeks)..Time.zone.now)
end
Expand Down Expand Up @@ -295,136 +326,10 @@ def cancel_task_and_child_subtasks
)
end

def assign_to_organization_data(_user = nil)
organizations = Organization.assignable(self).map do |organization|
{
label: organization.name,
value: organization.id
}
end

{
selected: nil,
options: organizations,
type: GenericTask.name
}
end

def mail_assign_to_organization_data(_user = nil)
{ options: MailTask.subclass_routing_options }
end

def cancel_task_data(_user = nil)
{
modal_title: COPY::CANCEL_TASK_MODAL_TITLE,
modal_body: COPY::CANCEL_TASK_MODAL_DETAIL,
message_title: format(COPY::CANCEL_TASK_CONFIRMATION, appeal.veteran_full_name),
message_detail: format(COPY::MARK_TASK_COMPLETE_CONFIRMATION_DETAIL, assigned_by&.full_name || "the assigner")
}
end

def assign_to_user_data(user = nil)
users = if assigned_to.is_a?(Organization)
assigned_to.users
elsif parent&.assigned_to.is_a?(Organization)
parent.assigned_to.users.reject { |u| u == assigned_to }
else
[]
end

{
selected: user,
options: users_to_options(users),
type: type
}
end

def assign_to_judge_data(_user = nil)
{
selected: root_task.children.find { |task| task.is_a?(JudgeTask) }&.assigned_to,
options: users_to_options(Judge.list_all),
type: JudgeQualityReviewTask.name
}
end

def assign_to_attorney_data(_user = nil)
{
selected: nil,
options: nil,
type: AttorneyTask.name
}
end

def assign_to_privacy_team_data(_user = nil)
org = PrivacyTeam.singleton

{
selected: org,
options: [{ label: org.name, value: org.id }],
type: PrivacyActTask.name
}
end

def assign_to_translation_team_data(_user = nil)
org = Translation.singleton

{
selected: org,
options: [{ label: org.name, value: org.id }],
type: TranslationTask.name
}
end

def add_admin_action_data(_user = nil)
{
redirect_after: "/queue",
selected: nil,
options: Constants::CO_LOCATED_ADMIN_ACTIONS.map do |key, value|
{
label: value,
value: key
}
end,
type: ColocatedTask.name
}
end

def complete_data(_user = nil)
{
modal_body: COPY::MARK_TASK_COMPLETE_COPY
}
end

def schedule_veteran_data(_user = nil)
{
selected: nil,
options: nil,
type: ScheduleHearingTask.name
}
end

def return_to_attorney_data(_user = nil)
assignee = children.select { |t| t.is_a?(AttorneyTask) }.max_by(&:created_at)&.assigned_to
attorneys = JudgeTeam.for_judge(assigned_to)&.attorneys || []
attorneys |= [assignee] if assignee.present?
{
selected: assignee,
options: users_to_options(attorneys),
type: AttorneyRewriteTask.name
}
end

def timeline_title
"#{type} completed"
end

def timeline_details
{
title: timeline_title,
date: closed_at
}
end

def update_if_hold_expired!
update!(status: Constants.TASK_STATUSES.in_progress) if on_hold_expired?
end
Expand Down Expand Up @@ -472,13 +377,8 @@ def task_just_closed_and_has_parent?
task_just_closed? && parent
end

def users_to_options(users)
users.map do |user|
{
label: user.full_name,
value: user.id
}
end
def task_just_placed_on_hold?
saved_change_to_attribute?(:placed_on_hold_at)
end

def update_status_if_children_tasks_are_complete
Expand Down
11 changes: 0 additions & 11 deletions app/models/tasks/disposition_task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,6 @@ def available_actions(user)
end
end

def add_schedule_hearing_task_admin_actions_data(_user)
{
redirect_after: "/queue/appeals/#{appeal.external_id}",
message_detail: COPY::ADD_HEARING_ADMIN_TASK_CONFIRMATION_DETAIL,
selected: nil,
options: HearingAdminActionTask.subclasses.sort_by(&:label).map do |subclass|
{ value: subclass.name, label: subclass.label }
end
}
end

def update_from_params(params, user)
payload_values = params.delete(:business_payloads)&.dig(:values)

Expand Down
13 changes: 0 additions & 13 deletions app/models/tasks/hearing_admin_action_task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,6 @@ def actions_allowable?(user)
(HearingsManagement.singleton.user_has_access?(user) || HearingAdmin.singleton.user_has_access?(user)) && super
end

def assign_to_user_data(user = nil)
super(user).merge(
redirect_after: "/organizations/#{HearingAdmin.singleton.url}",
message_detail: COPY::HEARING_ASSIGN_TASK_SUCCESS_MESSAGE_DETAIL
)
end

def complete_data(_user = nil)
{
modal_body: COPY::HEARING_SCHEDULE_COMPLETE_ADMIN_MODAL
}
end

private

def on_hold_duration_is_set
Expand Down
6 changes: 0 additions & 6 deletions app/models/tasks/no_show_hearing_task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,6 @@ def reschedule_hearing
end
end

def complete_data(_user = nil)
{
modal_body: COPY::NO_SHOW_HEARING_TASK_COMPLETE_MODAL_BODY
}
end

private

def set_assignee
Expand Down
22 changes: 0 additions & 22 deletions app/models/tasks/schedule_hearing_task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -106,28 +106,6 @@ def available_actions(user)
hearing_admin_actions
end

def add_admin_action_data(_user)
{
redirect_after: "/queue/appeals/#{appeal.external_id}",
message_detail: COPY::ADD_HEARING_ADMIN_TASK_CONFIRMATION_DETAIL,
selected: nil,
options: HearingAdminActionTask.subclasses.sort_by(&:label).map do |subclass|
{ value: subclass.name, label: subclass.label }
end
}
end

def withdraw_hearing_data(_user)
{
redirect_after: "/queue/appeals/#{appeal.external_id}",
modal_title: COPY::WITHDRAW_HEARING_MODAL_TITLE,
modal_body: COPY::WITHDRAW_HEARING_MODAL_BODY,
message_title: format(COPY::WITHDRAW_HEARING_SUCCESS_MESSAGE_TITLE, appeal.veteran_full_name),
message_detail: format(COPY::WITHDRAW_HEARING_SUCCESS_MESSAGE_BODY, appeal.veteran_full_name),
back_to_hearing_schedule: true
}
end

private

def set_assignee
Expand Down
Loading

0 comments on commit dc71939

Please sign in to comment.