Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Teaches BVA Dispatching Tasks to actually block #15041

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions app/models/task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,13 @@ class Task < CaseflowRecord

before_create :set_assigned_at
before_create :verify_org_task_unique
before_create :conditionally_set_dispatch_as_parent, if: :blocking_dispatch?

after_create :create_and_auto_assign_child_task, if: :automatically_assign_org_task?
after_create :tell_parent_task_child_task_created

before_save :set_timestamp
after_update :dispatch_if_ready, if: :task_just_closed_and_blocking_dispatch?
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_task_timers, if: :task_just_closed?
Expand Down Expand Up @@ -609,6 +611,10 @@ def blocking_dispatch?
self.class.blocking_dispatch?
end

def task_just_closed_and_blocking_dispatch?
task_just_closed? && blocking_dispatch?
end

# currently only defined by ScheduleHearingTask and AssignHearingDispositionTask for virtual hearing related updates
def alerts
@alerts ||= []
Expand Down Expand Up @@ -684,6 +690,30 @@ def set_assigned_at
self.assigned_at = created_at unless assigned_at
end

lomky marked this conversation as resolved.
Show resolved Hide resolved
# If there is a BVA Dispatch task open, we want to make sure we block it
# Prefer the provided parent if it already blocks BVA Dispatch, then the User-assigned
# BVA Dispatch task, and fallback to the Organization assigned one.
# If there is no BVA Dispatch task open, set the parent to the root_task unless
# the parent already blocks BVA Dispatch to avoid splicing connected tasks
lomky marked this conversation as resolved.
Show resolved Hide resolved
def conditionally_set_dispatch_as_parent
dispatch_task = BvaDispatchTask.open.find_by(appeal: appeal, assigned_to_type: "User") ||
BvaDispatchTask.open.find_by(appeal: appeal, assigned_to_type: "Organization")
lomky marked this conversation as resolved.
Show resolved Hide resolved

if ! (dispatch_task || self.parent&.blocking_dispatch?)
self.parent = appeal.root_task
end

if dispatch_task&.descendants&.exclude?(parent)
self.parent = dispatch_task
end
lomky marked this conversation as resolved.
Show resolved Hide resolved
end

def dispatch_if_ready
if appeal.ready_for_bva_dispatch?
BvaDispatchTask.create_from_root_task(appeal.root_task)
end
end

STATUS_TIMESTAMPS = {
assigned: :assigned_at,
in_progress: :started_at,
Expand Down
2 changes: 2 additions & 0 deletions app/models/tasks/bva_dispatch_task.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ def task_is_assigned_to_organization_user_administers?(user)

class << self
def create_from_root_task(root_task)
return if root_task.appeal.tasks.open.map(&:blocking_dispatch?).any?

create!(assigned_to: BvaDispatch.singleton, parent_id: root_task.id, appeal: root_task.appeal)
end

Expand Down
1 change: 1 addition & 0 deletions app/repositories/task_action_repository.rb
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ def assign_to_privacy_team_data(_task, _user = nil)
{
selected: org,
options: [{ label: org.name, value: org.id }],
message_detail: COPY::PRIVACY_ASSIGN_TASK_SUCCESS_MESSAGE_DETAIL,
type: PrivacyActTask.name
}
end
Expand Down
2 changes: 2 additions & 0 deletions client/COPY.json
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,7 @@
"ADD_COLOCATED_TASK_CONFIRMATION_DETAIL": "If you need to make any changes, please email your administration action team.",
"ADD_COLOCATED_TASK_ACTION_DUPLICATE_ERROR": "There is already an open %s action on this case with the instructions \"%s\"",
"ADD_HEARING_ADMIN_TASK_CONFIRMATION_DETAIL": "The task has been placed in your teams queue.",
"ADD_FOIA_TASK_CONFIRMATION_DETAIL": "You can continue your work on this case while the FOIA task is worked, case remains in your Assigned queue.",

"CHANGE_TASK_TYPE_SUBHEAD": "Change task type",
"CHANGE_TASK_TYPE_ACTION_LABEL": "Select another task type from the list of available options:",
Expand Down Expand Up @@ -420,6 +421,7 @@
"ASSIGN_TASK_SUCCESS_MESSAGE": "Task assigned to %s",
"REASSIGN_TASK_SUCCESS_MESSAGE": "Task reassigned to %s",
"HEARING_ASSIGN_TASK_SUCCESS_MESSAGE_DETAIL": "You can continue to assign tasks to yourself and others using this queue.",
"PRIVACY_ASSIGN_TASK_SUCCESS_MESSAGE_DETAIL": "You can return this case to the Judge/Attorney or continue to assign tasks to yourself and others using this queue.",

"CREATE_MAIL_TASK_TITLE": "Create new mail task",
"MAIL_TASK_DROPDOWN_TYPE_SELECTOR_LABEL": "Select correspondence type",
Expand Down
5 changes: 4 additions & 1 deletion client/app/queue/colocatedTasks/AddColocatedTaskView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,12 @@ class AddColocatedTaskView extends React.PureComponent {
map((action) => taskActionData(this.props).options.find((option) => option.value === action.type).label).
join(', ');
const msgDisplayCount = this.state.adminActions.length === 1 ? 'an' : this.state.adminActions.length;
const msgDetails = (msgActions === 'FOIA' && !task.isLegacy) ?
COPY.ADD_FOIA_TASK_CONFIRMATION_DETAIL :
taskActionData(this.props).message_detail || COPY.ADD_HEARING_ADMIN_TASK_CONFIRMATION_DETAIL;
const successMsg = {
title: sprintf(msgTitle, msgDisplayCount, msgSubject, msgActions),
detail: taskActionData(this.props).message_detail || COPY.ADD_HEARING_ADMIN_TASK_CONFIRMATION_DETAIL
detail: msgDetails
};

this.props.
Expand Down
197 changes: 197 additions & 0 deletions spec/models/task_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1748,4 +1748,201 @@ def next_assignee(_options = {})
end
end
end

describe ".conditionally_set_dispatch_as_parent" do
let(:user) { create(:user) }
let(:appeal) { create(:appeal) }
let(:root_task) { RootTask.find_or_create_by!(appeal: appeal, assigned_to: Bva.singleton) }
let(:parent_task) { create(:task, parent: root_task) }
before { FeatureToggle.enable!(:block_at_dispatch) }
after { FeatureToggle.disable!(:block_at_dispatch) }
subject { FoiaTask.create!(appeal: appeal, parent: parent_task, assigned_to: create(:user)) }

shared_examples "parent task is descendant of BvaDispatchTask" do
let(:parent_task) { ColocatedTask.find_by(appeal: appeal) }

before do
ColocatedTask.create!(assigned_by: create(:user),
assigned_to: create(:organization),
parent: dispatch_task,
appeal: dispatch_task.appeal)
end
it "uses the descendant task as the parent" do
expect(subject.parent).to eq(parent_task)
end
end

shared_examples "organization-assignee BvaDispatchTask as parent" do
context "that is active" do
before do
BvaDispatchTask.find_by(appeal: appeal,
assigned_to_type: "Organization").update!(status: Constants.TASK_STATUSES.assigned)
end

it "uses that as the parent" do
expect(subject.parent).to eq(dispatch_org_task)
end
end

context "that is on_hold" do
before do
BvaDispatchTask.find_by(appeal: appeal,
assigned_to_type: "Organization").update!(status: Constants.TASK_STATUSES.on_hold)
end

it "uses that as the parent" do
expect(subject.parent).to eq(dispatch_org_task)
end

context "but the parent task is a descendant of BvaDispatchTask" do
let(:dispatch_task) { dispatch_org_task }

include_examples "parent task is descendant of BvaDispatchTask"
end
end

context "that is completed" do
before do
BvaDispatchTask.find_by(appeal: appeal,
assigned_to_type: "Organization").update!(status: Constants.TASK_STATUSES.completed)
end

include_examples "depends on whether parent blocking_dispatch?"
end
end

shared_examples "depends on whether parent blocking_dispatch?" do
context "parent task already blocks dispatch" do
let(:parent_task) { create(:congressional_interest_mail_task, parent: root_task) }
it "doesn't change the parent task" do
expect(subject.parent).to eq(parent_task)
end
end

context "parent task does not block dispatch" do
it "sets the root task as parent" do
expect(subject.parent).to eq(root_task)
end
end
end

context "appeal has user-assignee BvaDispatchTask" do
let(:dispatch_user_task) { BvaDispatchTask.find_by(appeal: appeal, assigned_to_type: "User") }
before do
BvaDispatch.singleton.add_user(user)
BvaDispatchTask.create_from_root_task(root_task)
end

context "that is open" do
context "that is active" do
it "uses that as the parent" do
expect(subject.parent).to eq(dispatch_user_task)
end
end

context "that is on_hold" do
before do
dispatch_user_task.update!(status: Constants.TASK_STATUSES.on_hold)
end

it "uses that as the parent" do
expect(subject.parent).to eq(dispatch_user_task)
end

context "but the parent task is a descendant of BvaDispatchTask" do
let(:dispatch_task) { dispatch_user_task }

include_examples "parent task is descendant of BvaDispatchTask"
end
end
end

context "that is completed" do
before do
BvaDispatchTask.find_by(appeal: appeal,
assigned_to_type: "User").update_column(:status, Constants.TASK_STATUSES.completed)
end

context "but has an organization-assignee BvaDispatchTask" do
let(:dispatch_org_task) { BvaDispatchTask.find_by(appeal: appeal, assigned_to_type: "Organization") }

include_examples "organization-assignee BvaDispatchTask as parent"
end
end
end

context "appeal has no user-assignee BvaDispatchTask" do
before do
BvaDispatch.singleton.add_user(user)
end

context "but has an organization-assignee BvaDispatchTask" do
let(:dispatch_org_task) { BvaDispatchTask.find_by(appeal: appeal, assigned_to_type: "Organization") }

before do
BvaDispatch.singleton.add_user(user)
BvaDispatchTask.create_from_root_task(root_task)
BvaDispatchTask.find_by(appeal: appeal, assigned_to_type: "User").delete
end

include_examples "organization-assignee BvaDispatchTask as parent"
end

context "and no organization-assignee BvaDispatchTask" do
include_examples "depends on whether parent blocking_dispatch?"
end
end
end

describe ".dispatch_if_ready" do
let(:appeal) { create(:appeal) }
let(:user) { create(:user) }
let!(:root_task) { RootTask.find_or_create_by!(appeal: appeal, assigned_to: Bva.singleton) }
let!(:foia_task) { FoiaTask.create!(appeal: appeal, parent: appeal.root_task, assigned_to: create(:user)) }

before { FeatureToggle.enable!(:block_at_dispatch) }
after { FeatureToggle.disable!(:block_at_dispatch) }

subject { foia_task.completed! }

before do
allow(appeal).to receive(:ready_for_bva_dispatch?).and_return(ready)
BvaDispatch.singleton.add_user(user)
end

context "appeal is not ready for BVA Dispatch" do
let(:ready) { false }

it "does not create BVA Dispatch tasks" do
subject

expect(BvaDispatchTask.find_by(appeal: appeal)).to be(nil)
end
end

context "appeal is ready for BVA Dispatch" do
let(:ready) { true }

context "and no other tasks block BVA Dispatch" do
it "creates BVA Dispatch tasks" do
subject

expect(BvaDispatchTask.find_by(appeal: appeal)).to be_a(BvaDispatchTask)
expect(RootTask.find_by(appeal: appeal).open?).to be(true)
end
end

context "but other tasks block BVA Dispatch" do
let!(:privacy_act_task) do
PrivacyActTask.create!(appeal: appeal, parent: appeal.root_task, assigned_to: create(:user))
end

it "does not create BVA Dispatch tasks" do
subject

expect(BvaDispatchTask.find_by(appeal: appeal)).to be(nil)
end
end
end
end
end