Skip to content

Commit

Permalink
Added convert to virtual for video hearings (#15805)
Browse files Browse the repository at this point in the history
Resolves [CASEFLOW-210](https://vajira.max.gov/browse/CASEFLOW-210)

### Description

Gives Hearing Coordinators the ability to convert the hearing request type on any appeal applying the same workflow as was used to convert Travel Board request types implemented in #15314. 

### Acceptance Criteria
- [ ] Code compiles correctly
- [ ] Users are able to successfully convert hearing request types from Central Office/Video to Virtual

### Testing Plan

1. Shadow user `BVASYELOW`: http://localhost:3000/test/users
2. Navigate to the hearing schedule: http://localhost:3000/hearings/schedule
3. Click the "Schedule Veterans" button
4. Search for "St. Petersburg, FL" in the Regional Office dropdown and navigate to the "AMA Veterans Waiting" tab
5. Select 1 of the Veterans from the list to navigate to their case details page
6. Once the case details page loads, check the actions dropdown for the Convert to Virtual Hearing action and select that
7. Review the conversion page and based on whether you selected the Central Office or another regional office, you should see a message at the top indicating in which queue this request will be
8. Click the Convert to Virtual Hearing button
9. Ensure you receive a success banner and that the action to Convert to Virtual Hearing is no longer available
10. Ensure that the Hearing Request type in the claims header now says Virtual
11. Scroll down and check that there is a ChangeHearingRequestType indicating the conversion
12. Scroll back up and click the Schedule Veteran action and ensure that the Virtual option is preselected

### Code Documentation Updates
- [x] Add or update code comments at the top of the class, module, and/or component.

### Database Changes
*Only for Schema Changes*

* [x] Column comments updated
* [x] Verify that `migrate:rollback` works as desired ([`change` supported functions](https://edgeguides.rubyonrails.org/active_record_migrations.html#using-the-change-method))
* [x] DB schema docs updated with `make docs` (after running `make migrate`)
* [x] #appeals-schema notified with summary and link to this PR
  • Loading branch information
sahalliburton authored Jan 25, 2021
1 parent a6d8a24 commit 94fbc98
Show file tree
Hide file tree
Showing 13 changed files with 431 additions and 341 deletions.
25 changes: 1 addition & 24 deletions app/models/appeal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class Appeal < DecisionReview
include PrintsTaskTree
include HasTaskHistory
include AppealAvailableHearingLocations
include HearingRequestTypeConcern

has_many :appeal_views, as: :appeal
has_many :claims_folder_searches, as: :appeal
Expand Down Expand Up @@ -544,30 +545,6 @@ def ready_for_bva_dispatch?
false
end

# Returns the hearing request type.
#
# @note See `LegacyAppeal#current_hearing_request_type` for more information.
# This method is provided for compatibility.
def current_hearing_request_type(readable: false)
return nil if closest_regional_office.nil?

current_hearing_request_type = (closest_regional_office == "C") ? :central : :video

return current_hearing_request_type if !readable

# Determine type using closest_regional_office
# "Central" if closest_regional_office office is "C", "Video" otherwise
case current_hearing_request_type
when :central
Hearing::HEARING_TYPES[:C]
else
Hearing::HEARING_TYPES[:V]
end
end

alias original_hearing_request_type current_hearing_request_type
alias previous_hearing_request_type current_hearing_request_type

private

def business_lines_needing_assignment
Expand Down
84 changes: 84 additions & 0 deletions app/models/concerns/hearing_request_type_concern.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# frozen_string_literal: true

module HearingRequestTypeConcern
extend ActiveSupport::Concern

included do
# Add Paper Trail configuration
has_paper_trail only: [:changed_request_type], on: [:update]

validates :changed_request_type,
inclusion: {
in: [
HearingDay::REQUEST_TYPES[:central],
HearingDay::REQUEST_TYPES[:video],
HearingDay::REQUEST_TYPES[:virtual]
],
message: "changed request type (%<value>s) is invalid"
},
allow_nil: true
end

class InvalidChangedRequestType < StandardError; end

# uses the paper_trail version on LegacyAppeal
def latest_appeal_event
TaskEvent.new(version: versions.last) if versions.any?
end

def original_hearing_request_type(readable: false)
# Use the VACOLS value for LegacyAppeals otherwise use the closest regional office
original_hearing_request_type = is_a?(LegacyAppeal) ? hearing_request_type : closest_regional_office

# Format the request type into a symbol
formatted_request_type = format_hearing_request_type(original_hearing_request_type)

# Return the human readable request type or the symbol of request type
readable ? LegacyAppeal::READABLE_HEARING_REQUEST_TYPES[formatted_request_type] : formatted_request_type
end

def current_hearing_request_type(readable: false)
request_type = changed_request_type.presence || original_hearing_request_type

# Format the request type into a symbol
formatted_request_type = format_hearing_request_type(request_type)

# Return the human readable request type or the symbol of request type
readable ? LegacyAppeal::READABLE_HEARING_REQUEST_TYPES[formatted_request_type] : formatted_request_type
end

# # if `change_hearing_request` is populated meaning the hearing request type was changed, then return what the
# # previous hearing request type was. Use paper trail event to derive previous type in the case the type was changed
# # multple times.
def previous_hearing_request_type(readable: false)
diff = latest_appeal_event&.diff || {} # Example of diff: {"changed_request_type"=>[nil, "R"]}
previous_hearing_request_type = diff["changed_request_type"]&.first

request_type = previous_hearing_request_type.presence || original_hearing_request_type

# Format the request type into a symbol
formatted_request_type = format_hearing_request_type(request_type)

# Return the human readable request type or the symbol of request type
readable ? LegacyAppeal::READABLE_HEARING_REQUEST_TYPES[formatted_request_type] : formatted_request_type
end

private

# rubocop:disable Metrics/CyclomaticComplexity
def format_hearing_request_type(request_type)
return nil if request_type.nil?

case request_type
when HearingDay::REQUEST_TYPES[:central], :central_office, :central
is_a?(LegacyAppeal) ? :central_office : :central
when :travel_board
video_hearing_requested ? :video : :travel_board
when HearingDay::REQUEST_TYPES[:virtual]
:virtual
else
:video
end
end
# rubocop:enable Metrics/CyclomaticComplexity
end
81 changes: 2 additions & 79 deletions app/models/legacy_appeal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class LegacyAppeal < CaseflowRecord
include PrintsTaskTree
include HasTaskHistory
include AppealAvailableHearingLocations
include HearingRequestTypeConcern

belongs_to :appeal_series
has_many :dispatch_tasks, foreign_key: :appeal_id, class_name: "Dispatch::Task"
Expand All @@ -38,20 +39,6 @@ class LegacyAppeal < CaseflowRecord
}, class_name: "Task", foreign_key: :appeal_id
accepts_nested_attributes_for :worksheet_issues, allow_destroy: true

# Add Paper Trail configuration
has_paper_trail only: [:changed_request_type], on: [:update]

validates :changed_request_type,
inclusion: {
in: [
HearingDay::REQUEST_TYPES[:video],
HearingDay::REQUEST_TYPES[:virtual]
],
message: "changed request type (%<value>s) is invalid"
},
allow_nil: true

class InvalidChangedRequestType < StandardError; end
class UnknownLocationError < StandardError; end

# When these instance variable getters are called, first check if we've
Expand Down Expand Up @@ -179,6 +166,7 @@ class UnknownLocationError < StandardError; end
READABLE_HEARING_REQUEST_TYPES = {
central_board: "Central", # Equivalent to :central_office
central_office: "Central",
central: "Central",
travel_board: "Travel",
video: "Video",
virtual: "Virtual"
Expand Down Expand Up @@ -414,66 +402,6 @@ def scheduled_hearings
hearings.select(&:scheduled_pending?)
end

# Currently changed_request_type can only be stored as 'R' or 'V' because
# you can convert a hearing request type to Video or Virtual.
# This method returns the sanitized versions of those where 'R' => :virtual and 'V' => :video
def sanitized_changed_request_type(changed_request_type)
case changed_request_type
when HearingDay::REQUEST_TYPES[:video]
:video
when HearingDay::REQUEST_TYPES[:virtual]
:virtual
else
fail InvalidChangedRequestType, "\"#{changed_request_type}\" is not a valid request type."
end
end

# `hearing_request_type` is a direct mapping from VACOLS and has some unused
# values. Also, `hearing_request_type` alone can't disambiguate a video hearing
# from a travel board hearing
# This method cleans all of these issues up to return a sanitized version of the original type requested by Appellant.
# Replicated in UpdateCachedAppealAttributesJob#original_hearing_request_type_for_vacols_case
def original_hearing_request_type(readable: false)
original_hearing_request_type = case hearing_request_type
when :central_office
:central_office
when :travel_board
video_hearing_requested ? :video : :travel_board
end
readable ? READABLE_HEARING_REQUEST_TYPES[original_hearing_request_type] : original_hearing_request_type
end

# [Sept. 2020]:
# In response to COVID, the appellant has the option of changing their hearing
# preference if they were scheduled for a travel board hearing.
# This method captures if a travel board hearing request type was overridden in Caseflow.
# In general, this method returns the current hearing request type which could be dervied
# from `change_request_type` or VACOLS `hearing_request_type`
# Replicated in UpdateCachedAppealAttributesJob#case_fields_for_vacols_ids
def current_hearing_request_type(readable: false)
current_hearing_request_type = if changed_request_type.present?
sanitized_changed_request_type(changed_request_type)
else
original_hearing_request_type
end
readable ? READABLE_HEARING_REQUEST_TYPES[current_hearing_request_type] : current_hearing_request_type
end

# if `change_hearing_request` is populated meaning the hearing request type was changed, then return what the
# previous hearing request type was. Use paper trail event to derive previous type in the case the type was changed
# multple times.
def previous_hearing_request_type(readable: false)
diff = latest_appeal_event&.diff || {} # Example of diff: {"changed_request_type"=>[nil, "R"]}
previous_unsanitized_type = diff["changed_request_type"]&.first

previous_hearing_request_type = if previous_unsanitized_type.present?
sanitized_changed_request_type(previous_unsanitized_type)
else
original_hearing_request_type
end
readable ? READABLE_HEARING_REQUEST_TYPES[previous_hearing_request_type] : previous_hearing_request_type
end

## END Hearing specific attributes and methods

def veteran_is_deceased
Expand Down Expand Up @@ -982,11 +910,6 @@ def ready_for_bva_dispatch?
false
end

# uses the paper_trail version on LegacyAppeal
def latest_appeal_event
TaskEvent.new(version: versions.last) if versions.any?
end

# Hacky logic to determine if an acting judge should see judge actions or attorney actions on a case assigned to them
# See https://github.com/department-of-veterans-affairs/caseflow/issues/14886 for details
def assigned_to_acting_judge_as_judge?(acting_judge)
Expand Down
72 changes: 54 additions & 18 deletions app/models/tasks/schedule_hearing_task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,25 +51,15 @@ def update_from_params(params, current_user)
multi_transaction do
verify_user_can_update!(current_user)

created_tasks = []

if params[:status] == Constants.TASK_STATUSES.completed
task_values = params.delete(:business_payloads)[:values]

hearing = create_hearing(task_values)

if task_values[:virtual_hearing_attributes].present?
@alerts = VirtualHearings::ConvertToVirtualHearingService
.convert_hearing_to_virtual(hearing, task_values[:virtual_hearing_attributes])
end

created_tasks << AssignHearingDispositionTask.create_assign_hearing_disposition_task!(appeal, parent, hearing)
elsif params[:status] == Constants.TASK_STATUSES.cancelled
created_tasks << withdraw_hearing(parent)
# Either change the hearing request type or schedule/cancel the hearing
if params.dig(:business_payloads, :values, :changed_request_type).present?
change_hearing_request_type(params, current_user)
else
created_tasks = create_schedule_hearing_tasks(params)

# super returns [self]
super(params, current_user) + created_tasks.compact
end

# super returns [self]
super(params, current_user) + created_tasks.compact
end
end

Expand Down Expand Up @@ -107,6 +97,10 @@ def create_change_hearing_disposition_task(instructions = nil)
def available_actions(user)
hearing_admin_actions = available_hearing_user_actions(user)

if user.can_change_hearing_request_type? && appeal.changed_request_type != HearingDay::REQUEST_TYPES[:virtual]
hearing_admin_actions.push(Constants.TASK_ACTIONS.CHANGE_HEARING_REQUEST_TYPE_TO_VIRTUAL.to_h)
end

if (assigned_to &.== user) || HearingsManagement.singleton.user_has_access?(user)
schedule_hearing_action = if FeatureToggle.enabled?(:schedule_veteran_virtual_hearing, user: user)
Constants.TASK_ACTIONS.SCHEDULE_VETERAN_V2_PAGE
Expand All @@ -126,6 +120,48 @@ def available_actions(user)

private

# Method to create the appropriate schedule hearing tasks based on the status
def create_schedule_hearing_tasks(params)
# Instantiate the tasks to create for the schedule hearing task
created_tasks = []

# Check if we are completing the schedule hearing task
if params[:status] == Constants.TASK_STATUSES.completed
# Extract the schedule hearing task values and create a hearing from them
task_values = params.delete(:business_payloads)[:values]
hearing = create_hearing(task_values)

# Create the virtual hearing if the attributes have been passed
if task_values[:virtual_hearing_attributes].present?
@alerts = VirtualHearings::ConvertToVirtualHearingService
.convert_hearing_to_virtual(hearing, task_values[:virtual_hearing_attributes])
end

# Create and assign the hearing now that it has been scheduled
created_tasks << AssignHearingDispositionTask.create_assign_hearing_disposition_task!(appeal, parent, hearing)

# The only other option is to cancel the schedule hearing task
elsif params[:status] == Constants.TASK_STATUSES.cancelled
# If we are cancelling the schedule hearing task, we need to withdraw the request
created_tasks << withdraw_hearing(parent)
end

# Return the created tasks
created_tasks
end

# Method to change the hearing request type on an appeal
def change_hearing_request_type(params, current_user)
changed_request_type = ChangeHearingRequestTypeTask.create!(
appeal: appeal,
assigned_to: current_user,
parent: self
)

# Call the child method so that we follow that workflow when changing the hearing request type
changed_request_type.update_from_params(params, current_user)
end

def cancel_parent_task(parent)
# if parent HearingTask does not have any open children tasks, cancel it
if parent.children.open.empty?
Expand Down
3 changes: 1 addition & 2 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,7 @@ def can_view_overtime_status?
end

def can_change_hearing_request_type?
(can?("Build HearSched") || can?("Edit HearSched")) &&
FeatureToggle.enabled?(:convert_travel_board_to_video_or_virtual, user: self)
can?("Build HearSched") || can?("Edit HearSched")
end

def vacols_uniq_id
Expand Down
1 change: 1 addition & 0 deletions app/queries/appeals_updated_since_query.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def call
private

SKIP_ASSOCIATIONS = %w[
versions
appeal_views
claims_folder_searches
job_notes
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class AddChangedHearingRequestTypeToAppeal < ActiveRecord::Migration[5.2]
def change
add_column :appeals,
:changed_request_type,
:string,
comment: "The new hearing type preference for an appellant that needs a hearing scheduled"
end
end
3 changes: 2 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2021_01_13_152544) do
ActiveRecord::Schema.define(version: 2021_01_15_212305) do

# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
Expand Down Expand Up @@ -94,6 +94,7 @@

create_table "appeals", comment: "Decision reviews intaken for AMA appeals to the board (also known as a notice of disagreement).", force: :cascade do |t|
t.boolean "aod_based_on_age", comment: "If true, appeal is advance-on-docket due to claimant's age."
t.string "changed_request_type", comment: "The new hearing type preference for an appellant that needs a hearing scheduled"
t.string "closest_regional_office", comment: "The code for the regional office closest to the Veteran on the appeal."
t.datetime "created_at"
t.date "docket_range_date", comment: "Date that appeal was added to hearing docket range."
Expand Down
1 change: 1 addition & 0 deletions docs/schema/caseflow.csv
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ api_views,updated_at,datetime,,,,,,
api_views,vbms_id,string,,,,,,
appeals,,,,,,,,Decision reviews intaken for AMA appeals to the board (also known as a notice of disagreement).
appeals,aod_based_on_age,boolean,,,,,x,"If true, appeal is advance-on-docket due to claimant's age."
appeals,changed_request_type,string,,,,,,The new hearing type preference for an appellant that needs a hearing scheduled
appeals,closest_regional_office,string,,,,,,The code for the regional office closest to the Veteran on the appeal.
appeals,created_at,datetime,,,,,,
appeals,docket_range_date,date,,,,,,Date that appeal was added to hearing docket range.
Expand Down
Loading

0 comments on commit 94fbc98

Please sign in to comment.