From 50bf288421d46c5fffe7330e89fc61e1f220294e Mon Sep 17 00:00:00 2001 From: Craig Reese Date: Mon, 26 Aug 2024 08:14:54 -0500 Subject: [PATCH 01/17] add metabase to m1 docker compose --- docker-compose-m1.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docker-compose-m1.yml b/docker-compose-m1.yml index 6d0f25a0f2b..367bf92b2d6 100644 --- a/docker-compose-m1.yml +++ b/docker-compose-m1.yml @@ -28,5 +28,12 @@ services: volumes: - ./local/sqs/conf:/conf + appeals-metabase: + platform: linux/amd64 + container_name: metabase3 # change this to metabase after testing is done + image: metabase/metabase:latest + ports: + - "3002:3000" + volumes: postgresdata: From eb9c35386d073518804232cf85a300ef427a1002 Mon Sep 17 00:00:00 2001 From: Craig Reese Date: Mon, 26 Aug 2024 10:46:13 -0500 Subject: [PATCH 02/17] rename metabase container and specify a version --- docker-compose-m1.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose-m1.yml b/docker-compose-m1.yml index 367bf92b2d6..c8a6644ba7d 100644 --- a/docker-compose-m1.yml +++ b/docker-compose-m1.yml @@ -30,8 +30,8 @@ services: appeals-metabase: platform: linux/amd64 - container_name: metabase3 # change this to metabase after testing is done - image: metabase/metabase:latest + container_name: metabase + image: metabase/metabase:v0.50.21 ports: - "3002:3000" From 06cc15a4a7f550a9ee4dba345ecd32e607d9a325 Mon Sep 17 00:00:00 2001 From: Craig Reese Date: Wed, 28 Aug 2024 11:51:02 -0500 Subject: [PATCH 03/17] add script for initial metabase setup --- scripts/metabase_api_script.sh | 73 ++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100755 scripts/metabase_api_script.sh diff --git a/scripts/metabase_api_script.sh b/scripts/metabase_api_script.sh new file mode 100755 index 00000000000..35aed2c9809 --- /dev/null +++ b/scripts/metabase_api_script.sh @@ -0,0 +1,73 @@ +#!/bin/sh + +ADMIN_EMAIL=admin@caseflow.demo +ADMIN_PASSWORD=caseflow1 +USER_EMAIL=caseflow@caseflow.demo +USER_PASSWORD=caseflow1 + +CASEFLOW_HOST=appeals-db +VACOLS_HOST=host.docker.internal + +echo "āŒšļøŽ Waiting for Metabase to start" +while (! curl -s -m 5 http://localhost:3002/api/session/properties -o /dev/null); do sleep 5; done + +echo "šŸ˜Ž Creating admin user" + +SETUP_TOKEN=$(curl -s -m 5 -X GET \ + -H "Content-Type: application/json" \ + http://localhost:3002/api/session/properties \ + | jq -r '.["setup-token"]' +) + +MB_TOKEN=$(curl -s -X POST \ + -H "Content-type: application/json" \ + http://localhost:3002/api/setup \ + -d '{ + "token": "'${SETUP_TOKEN}'", + "user": { + "email": "'${ADMIN_EMAIL}'", + "first_name": "Caseflow", + "last_name": "Admin", + "password": "'${ADMIN_PASSWORD}'" + }, + "prefs": { + "allow_tracking": false, + "site_name": "Metawhat" + } +}' | jq -r '.id') + +echo "Logging in as admin" +ADMIN_SESSION_ID=$(curl -s -X POST -H \ + "Content-type: application/json" http://localhost:3002/api/session \ + -d '{"username": "'${ADMIN_EMAIL}'", "password": "'${ADMIN_PASSWORD}'"}' \ + | jq -r '.id') + +echo "Getting Sample Database ID" +SAMPLE_DB_ID=$(curl -X GET http://localhost:3002/api/database -H "X-Metabase-Session: ${ADMIN_SESSION_ID}" \ + | jq '.data[0].id') + +echo "Deleting Sample Database" +curl -X DELETE http://localhost:3002/api/database/${SAMPLE_DB_ID} -H "X-Metabase-Session: ${ADMIN_SESSION_ID}" + +echo "Creating Caseflow Database connection" +curl -X POST http://localhost:3002/api/database \ + -H "Content-type: application/json" \ + -H "X-Metabase-Session: ${ADMIN_SESSION_ID}" \ + -d '{ + "engine": "postgres", + "name":"Caseflow DB", + "details": { + "host": "'${CASEFLOW_HOST}'", "port":"5432", "db": "caseflow_certification_development", "user": "postgres", "password": "postgres" + } + }' + +echo "Creating VACOLS Database connection" +echo "Not yet implemented" + +echo -e "\nšŸ‘„ Creating a basic user: " +curl -s "http://localhost:3002/api/user" \ + -H 'Content-Type: application/json' \ + -H "X-Metabase-Session: ${ADMIN_SESSION_ID}" \ + -d '{"first_name":"Caseflow","last_name":"User","email":"'${USER_EMAIL}'","login_attributes":{"region_filter":"WA"},"password":"'${USER_PASSWORD}'"}' + +echo -e "\nšŸ‘„ Basic user created!" From 765c61b8f777256fd31ef319a50f95b4b44789a9 Mon Sep 17 00:00:00 2001 From: Craig Reese Date: Wed, 28 Aug 2024 13:45:03 -0500 Subject: [PATCH 04/17] script output formatting --- scripts/metabase_api_script.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/metabase_api_script.sh b/scripts/metabase_api_script.sh index 35aed2c9809..8007d13ff48 100755 --- a/scripts/metabase_api_script.sh +++ b/scripts/metabase_api_script.sh @@ -61,7 +61,7 @@ curl -X POST http://localhost:3002/api/database \ } }' -echo "Creating VACOLS Database connection" +echo -e "\nCreating VACOLS Database connection" echo "Not yet implemented" echo -e "\nšŸ‘„ Creating a basic user: " From f7cdc93a210efc2e0a88576f1777f26485dc7958 Mon Sep 17 00:00:00 2001 From: Craig Reese Date: Wed, 28 Aug 2024 13:47:34 -0500 Subject: [PATCH 05/17] add metabase directory --- {scripts => metabase}/metabase_api_script.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {scripts => metabase}/metabase_api_script.sh (100%) diff --git a/scripts/metabase_api_script.sh b/metabase/metabase_api_script.sh similarity index 100% rename from scripts/metabase_api_script.sh rename to metabase/metabase_api_script.sh From 0be4bc6d4fe663247274eaccdded53cd8327a3a8 Mon Sep 17 00:00:00 2001 From: Craig Reese Date: Wed, 28 Aug 2024 17:55:11 -0500 Subject: [PATCH 06/17] move to a dockerfile, implement VACOLS to setup script --- docker-compose-m1.yml | 3 ++- metabase/Dockerfile | 14 ++++++++++++++ metabase/metabase_api_script.sh | 19 ++++++++++++++----- 3 files changed, 30 insertions(+), 6 deletions(-) create mode 100644 metabase/Dockerfile diff --git a/docker-compose-m1.yml b/docker-compose-m1.yml index c8a6644ba7d..c3164bd83a6 100644 --- a/docker-compose-m1.yml +++ b/docker-compose-m1.yml @@ -31,7 +31,8 @@ services: appeals-metabase: platform: linux/amd64 container_name: metabase - image: metabase/metabase:v0.50.21 + build: + context: metabase ports: - "3002:3000" diff --git a/metabase/Dockerfile b/metabase/Dockerfile new file mode 100644 index 00000000000..a9f2bc61bb2 --- /dev/null +++ b/metabase/Dockerfile @@ -0,0 +1,14 @@ +FROM metabase/metabase:v0.50.21 +MAINTAINER BID Appeals + +ENV MB_PLUGINS_DIR /app/plugins + +WORKDIR /app +RUN addgroup --gid 2000 --system metabase +RUN adduser --disabled-password -u 2000 --ingroup metabase metabase +RUN mkdir plugins && chmod g-w plugins +RUN cd plugins && curl -sLO https://download.oracle.com/otn-pub/otn_software/jdbc/235/ojdbc8.jar + +RUN chown -R metabase:metabase /app/plugins + +ENTRYPOINT ["/app/run_metabase.sh"] diff --git a/metabase/metabase_api_script.sh b/metabase/metabase_api_script.sh index 8007d13ff48..c13175973b6 100755 --- a/metabase/metabase_api_script.sh +++ b/metabase/metabase_api_script.sh @@ -31,8 +31,8 @@ MB_TOKEN=$(curl -s -X POST \ "password": "'${ADMIN_PASSWORD}'" }, "prefs": { - "allow_tracking": false, - "site_name": "Metawhat" + "allow_tracking": false, + "site_name": "Caseflow" } }' | jq -r '.id') @@ -55,19 +55,28 @@ curl -X POST http://localhost:3002/api/database \ -H "X-Metabase-Session: ${ADMIN_SESSION_ID}" \ -d '{ "engine": "postgres", - "name":"Caseflow DB", + "name": "Caseflow DB", "details": { "host": "'${CASEFLOW_HOST}'", "port":"5432", "db": "caseflow_certification_development", "user": "postgres", "password": "postgres" } }' echo -e "\nCreating VACOLS Database connection" -echo "Not yet implemented" +curl -X POST http://localhost:3002/api/database \ + -H "Content-type: application/json" \ + -H "X-Metabase-Session: ${ADMIN_SESSION_ID}" \ + -d '{ + "engine": "oracle", + "name": "VACOLS", + "details": { + "host": "'${VACOLS_HOST}'", "port": "1521", "sid": "BVAP", "name": "VACOLS_DEV", "user": "VACOLS_DEV", "password": "VACOLS_DEV" + } + }' echo -e "\nšŸ‘„ Creating a basic user: " curl -s "http://localhost:3002/api/user" \ -H 'Content-Type: application/json' \ -H "X-Metabase-Session: ${ADMIN_SESSION_ID}" \ - -d '{"first_name":"Caseflow","last_name":"User","email":"'${USER_EMAIL}'","login_attributes":{"region_filter":"WA"},"password":"'${USER_PASSWORD}'"}' + -d '{"first_name": "Caseflow", "last_name": "User", "email": "'${USER_EMAIL}'", "login_attributes": {"region_filter": "WA"}, "password":"'${USER_PASSWORD}'"}' echo -e "\nšŸ‘„ Basic user created!" From d874156a863e4fa2ab3980261e171784f8e46766 Mon Sep 17 00:00:00 2001 From: Craig Reese Date: Wed, 28 Aug 2024 18:01:53 -0500 Subject: [PATCH 07/17] add metabase to original docker-compose --- docker-compose.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 02ec69bc364..ad8e7178948 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -33,5 +33,12 @@ services: volumes: - ./local/sqs/conf:/conf + appeals-metabase: + container_name: metabase + build: + context: metabase + ports: + - "3002:3000" + volumes: postgresdata: From 052a707b281f0cadd5137de41d6609c71847ee28 Mon Sep 17 00:00:00 2001 From: Craig Reese Date: Mon, 9 Sep 2024 09:21:39 -0500 Subject: [PATCH 08/17] add step to initialize metabase in demo startup --- docker-bin/startup.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker-bin/startup.sh b/docker-bin/startup.sh index 97a73eeae4e..05d9c7033be 100644 --- a/docker-bin/startup.sh +++ b/docker-bin/startup.sh @@ -39,5 +39,8 @@ bundle exec rails runner scripts/enable_features_dev.rb echo "Enabling caching" touch tmp/caching-dev.txt +echo "Initializing metabase" +../metabase/metabase_api_script.sh + echo "Starting Caseflow App RoR" rails server --binding 0.0.0.0 -p 3000 From 010beca0083be42f08350686e88a12eb8af1b0de Mon Sep 17 00:00:00 2001 From: Craig Reese Date: Mon, 9 Sep 2024 09:38:31 -0500 Subject: [PATCH 09/17] update demo startup.sh to initialize metabase with absolute path --- docker-bin/startup.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-bin/startup.sh b/docker-bin/startup.sh index 05d9c7033be..df49be9470b 100644 --- a/docker-bin/startup.sh +++ b/docker-bin/startup.sh @@ -40,7 +40,7 @@ echo "Enabling caching" touch tmp/caching-dev.txt echo "Initializing metabase" -../metabase/metabase_api_script.sh +/caseflow/metabase/metabase_api_script.sh echo "Starting Caseflow App RoR" rails server --binding 0.0.0.0 -p 3000 From 3456dd294a2ac4277a12794f334d74c8168b721b Mon Sep 17 00:00:00 2001 From: Craig Reese Date: Mon, 9 Sep 2024 09:48:15 -0500 Subject: [PATCH 10/17] add make command for local metabase setup --- Makefile.example | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Makefile.example b/Makefile.example index e27b6efcb99..b275b850988 100644 --- a/Makefile.example +++ b/Makefile.example @@ -262,6 +262,9 @@ seed-dbs: ## Seed all databases bundle exec rake db:seed DB=etl bundle exec rake db:seed +setup-metabase: + metabase/metabase_api_script.sh + enable-feature-flags: ## enable all feature flags bundle exec rails runner scripts/enable_features_dev.rb From 692be83971cae3562681befcf45a7e65b8f4a73a Mon Sep 17 00:00:00 2001 From: Craig Reese Date: Mon, 9 Sep 2024 09:53:07 -0500 Subject: [PATCH 11/17] fix makefile syntax error --- Makefile.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.example b/Makefile.example index b275b850988..5334b44c47f 100644 --- a/Makefile.example +++ b/Makefile.example @@ -263,7 +263,7 @@ seed-dbs: ## Seed all databases DB=etl bundle exec rake db:seed setup-metabase: - metabase/metabase_api_script.sh + ./metabase/metabase_api_script.sh enable-feature-flags: ## enable all feature flags bundle exec rails runner scripts/enable_features_dev.rb From 2160f1095c8483a87b8b5766217da1634bbd12da Mon Sep 17 00:00:00 2001 From: Craig Reese Date: Fri, 20 Sep 2024 13:57:06 -0500 Subject: [PATCH 12/17] update scripts to work in AWS --- docker-bin/startup.sh | 2 +- metabase/metabase_api_script.sh | 2 +- metabase/metabase_api_script_demo.sh | 89 ++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 2 deletions(-) create mode 100755 metabase/metabase_api_script_demo.sh diff --git a/docker-bin/startup.sh b/docker-bin/startup.sh index ddd1663d301..f95463ebc30 100644 --- a/docker-bin/startup.sh +++ b/docker-bin/startup.sh @@ -35,7 +35,7 @@ echo "Enabling caching" touch tmp/caching-dev.txt echo "Initializing metabase" -/caseflow/metabase/metabase_api_script.sh +/caseflow/metabase/metabase_api_script_demo.sh echo "Starting Caseflow App RoR" rails server --binding 0.0.0.0 -p 3000 diff --git a/metabase/metabase_api_script.sh b/metabase/metabase_api_script.sh index c13175973b6..165e03bd234 100755 --- a/metabase/metabase_api_script.sh +++ b/metabase/metabase_api_script.sh @@ -79,4 +79,4 @@ curl -s "http://localhost:3002/api/user" \ -H "X-Metabase-Session: ${ADMIN_SESSION_ID}" \ -d '{"first_name": "Caseflow", "last_name": "User", "email": "'${USER_EMAIL}'", "login_attributes": {"region_filter": "WA"}, "password":"'${USER_PASSWORD}'"}' -echo -e "\nšŸ‘„ Basic user created!" +echo -e "\nMetabase setup complete!" diff --git a/metabase/metabase_api_script_demo.sh b/metabase/metabase_api_script_demo.sh new file mode 100755 index 00000000000..1e7d8936f84 --- /dev/null +++ b/metabase/metabase_api_script_demo.sh @@ -0,0 +1,89 @@ +#!/bin/sh + +ADMIN_EMAIL=admin@caseflow.demo +ADMIN_PASSWORD=caseflow1 +USER_EMAIL=caseflow@caseflow.demo +USER_PASSWORD=caseflow1 + +CASEFLOW_HOST=appeals-db +# The Metabase port needs to be 3000 because this will run in the appeals-app container in AWS and the port mapping +# in docker-compose is for the host only +METABASE_HOST=appeals-metabase +METABASE_PORT=3000 +VACOLS_HOST=host.docker.internal + +echo "Installing JQ" +yum install -y jq + +echo "Waiting for Metabase to start" +while (! curl -s -m 5 http://${METABASE_HOST}:${METABASE_PORT}/api/session/properties -o /dev/null); do sleep 5; done + +echo "Creating admin user" + +SETUP_TOKEN=$(curl -s -m 5 -X GET \ + -H "Content-Type: application/json" \ + http://${METABASE_HOST}:${METABASE_PORT}/api/session/properties \ + | jq -r '.["setup-token"]' +) + +MB_TOKEN=$(curl -s -X POST \ + -H "Content-type: application/json" \ + http://${METABASE_HOST}:${METABASE_PORT}/api/setup \ + -d '{ + "token": "'${SETUP_TOKEN}'", + "user": { + "email": "'${ADMIN_EMAIL}'", + "first_name": "Caseflow", + "last_name": "Admin", + "password": "'${ADMIN_PASSWORD}'" + }, + "prefs": { + "allow_tracking": false, + "site_name": "Caseflow" + } +}' | jq -r '.id') + +echo "Logging in as admin" +ADMIN_SESSION_ID=$(curl -s -X POST -H \ + "Content-type: application/json" http://${METABASE_HOST}:${METABASE_PORT}/api/session \ + -d '{"username": "'${ADMIN_EMAIL}'", "password": "'${ADMIN_PASSWORD}'"}' \ + | jq -r '.id') + +echo "Getting Sample Database ID" +SAMPLE_DB_ID=$(curl -X GET http://${METABASE_HOST}:${METABASE_PORT}/api/database -H "X-Metabase-Session: ${ADMIN_SESSION_ID}" \ + | jq '.data[0].id') + +echo "Deleting Sample Database" +curl -X DELETE http://${METABASE_HOST}:${METABASE_PORT}/api/database/${SAMPLE_DB_ID} -H "X-Metabase-Session: ${ADMIN_SESSION_ID}" + +echo "Creating Caseflow Database connection" +curl -X POST http://${METABASE_HOST}:${METABASE_PORT}/api/database \ + -H "Content-type: application/json" \ + -H "X-Metabase-Session: ${ADMIN_SESSION_ID}" \ + -d '{ + "engine": "postgres", + "name": "Caseflow DB", + "details": { + "host": "'${CASEFLOW_HOST}'", "port":"5432", "db": "caseflow_certification_development", "user": "postgres", "password": "postgres" + } + }' + +echo -e "\nCreating VACOLS Database connection" +curl -X POST http://${METABASE_HOST}:${METABASE_PORT}/api/database \ + -H "Content-type: application/json" \ + -H "X-Metabase-Session: ${ADMIN_SESSION_ID}" \ + -d '{ + "engine": "oracle", + "name": "VACOLS", + "details": { + "host": "'${VACOLS_HOST}'", "port": "1521", "sid": "BVAP", "name": "VACOLS_DEV", "user": "VACOLS_DEV", "password": "VACOLS_DEV" + } + }' + +echo -e "\nCreating a basic user: " +curl -s "http://${METABASE_HOST}:${METABASE_PORT}/api/user" \ + -H 'Content-Type: application/json' \ + -H "X-Metabase-Session: ${ADMIN_SESSION_ID}" \ + -d '{"first_name": "Caseflow", "last_name": "User", "email": "'${USER_EMAIL}'", "login_attributes": {"region_filter": "WA"}, "password":"'${USER_PASSWORD}'"}' + +echo -e "\nMetabase setup complete!" From 1683aad8a01e3b5efac0cab4aef1530f6f9c2377 Mon Sep 17 00:00:00 2001 From: Craig Reese Date: Fri, 20 Sep 2024 14:10:55 -0500 Subject: [PATCH 13/17] update demo script to use apt-get instead of yum --- metabase/metabase_api_script_demo.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/metabase/metabase_api_script_demo.sh b/metabase/metabase_api_script_demo.sh index 1e7d8936f84..8d2e6d479b3 100755 --- a/metabase/metabase_api_script_demo.sh +++ b/metabase/metabase_api_script_demo.sh @@ -13,7 +13,8 @@ METABASE_PORT=3000 VACOLS_HOST=host.docker.internal echo "Installing JQ" -yum install -y jq +apt-get update +apt-get install -y jq echo "Waiting for Metabase to start" while (! curl -s -m 5 http://${METABASE_HOST}:${METABASE_PORT}/api/session/properties -o /dev/null); do sleep 5; done From 666fcab2d31d30cee29e55a750de47dc3bb1b134 Mon Sep 17 00:00:00 2001 From: Craig Reese Date: Mon, 23 Sep 2024 06:54:56 -0500 Subject: [PATCH 14/17] update demo script to use dns for vacols-db --- metabase/metabase_api_script_demo.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metabase/metabase_api_script_demo.sh b/metabase/metabase_api_script_demo.sh index 8d2e6d479b3..8f251869a9d 100755 --- a/metabase/metabase_api_script_demo.sh +++ b/metabase/metabase_api_script_demo.sh @@ -10,7 +10,7 @@ CASEFLOW_HOST=appeals-db # in docker-compose is for the host only METABASE_HOST=appeals-metabase METABASE_PORT=3000 -VACOLS_HOST=host.docker.internal +VACOLS_HOST=vacols-db echo "Installing JQ" apt-get update From 8b85ec09b3be120f793c7dd0a828f807e5cb143e Mon Sep 17 00:00:00 2001 From: Craig Reese Date: Mon, 23 Sep 2024 08:49:08 -0500 Subject: [PATCH 15/17] fix vacols DB docker DNS name --- metabase/metabase_api_script_demo.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metabase/metabase_api_script_demo.sh b/metabase/metabase_api_script_demo.sh index 8f251869a9d..c41565cc28a 100755 --- a/metabase/metabase_api_script_demo.sh +++ b/metabase/metabase_api_script_demo.sh @@ -10,7 +10,7 @@ CASEFLOW_HOST=appeals-db # in docker-compose is for the host only METABASE_HOST=appeals-metabase METABASE_PORT=3000 -VACOLS_HOST=vacols-db +VACOLS_HOST=VACOLS_DB-development echo "Installing JQ" apt-get update From 25ed7f1037172a2e7ac4385e32372e8d25251297 Mon Sep 17 00:00:00 2001 From: ryanpmessner <163380175+ryanpmessner@users.noreply.github.com> Date: Fri, 27 Sep 2024 15:33:01 -0400 Subject: [PATCH 16/17] hotfix/APPEALS-23420: search defect Add search query service for the api response for the `/search` page Add issues to legacy appeals Add assigned_to_judge Use correct id for list of request issues export proper attributes for hearing --- .../idt/api/v2/appeals_controller.rb | 4 +- app/decorators/appeal_status_api_decorator.rb | 12 +- app/models/appeal.rb | 16 +- app/models/hearing.rb | 8 +- app/models/task.rb | 6 +- .../tasks/evidence_submission_window_task.rb | 2 +- app/services/bva_appeal_status.rb | 75 ++-- app/services/search_query_service.rb | 113 +++++ .../search_query_service/api_response.rb | 3 + .../search_query_service/appeal_row.rb | 177 ++++++++ .../search_query_service/attributes.rb | 30 ++ .../search_query_service/legacy_appeal_row.rb | 164 +++++++ .../search_query_service/legacy_attributes.rb | 27 ++ .../search_query_service/queried_appeal.rb | 133 ++++++ .../search_query_service/queried_hearing.rb | 90 ++++ .../queried_legacy_appeal.rb | 41 ++ app/services/search_query_service/query.rb | 416 ++++++++++++++++++ .../search_query_service/search_response.rb | 9 + .../search_query_service/vacols_query.rb | 112 +++++ .../vso_user_search_results.rb | 68 +++ app/workflows/case_search_results_base.rb | 118 +++-- ..._search_results_for_caseflow_veteran_id.rb | 22 + .../case_search_results_for_docket_number.rb | 6 +- ..._search_results_for_veteran_file_number.rb | 20 + spec/feature/queue/search_spec.rb | 6 +- spec/fixes/assigned_to_search_results_spec.rb | 6 +- spec/fixes/backfill_early_ama_appeal_spec.rb | 4 +- spec/services/bva_appeal_status_spec.rb | 2 +- spec/services/search_query_service_spec.rb | 318 +++++++++++++ 29 files changed, 1905 insertions(+), 103 deletions(-) create mode 100644 app/services/search_query_service.rb create mode 100644 app/services/search_query_service/api_response.rb create mode 100644 app/services/search_query_service/appeal_row.rb create mode 100644 app/services/search_query_service/attributes.rb create mode 100644 app/services/search_query_service/legacy_appeal_row.rb create mode 100644 app/services/search_query_service/legacy_attributes.rb create mode 100644 app/services/search_query_service/queried_appeal.rb create mode 100644 app/services/search_query_service/queried_hearing.rb create mode 100644 app/services/search_query_service/queried_legacy_appeal.rb create mode 100644 app/services/search_query_service/query.rb create mode 100644 app/services/search_query_service/search_response.rb create mode 100644 app/services/search_query_service/vacols_query.rb create mode 100644 app/services/search_query_service/vso_user_search_results.rb create mode 100644 spec/services/search_query_service_spec.rb diff --git a/app/controllers/idt/api/v2/appeals_controller.rb b/app/controllers/idt/api/v2/appeals_controller.rb index 74884231964..b93cf8179dc 100644 --- a/app/controllers/idt/api/v2/appeals_controller.rb +++ b/app/controllers/idt/api/v2/appeals_controller.rb @@ -14,11 +14,11 @@ def details result = if docket_number?(case_search) CaseSearchResultsForDocketNumber.new( docket_number: case_search, user: current_user - ).call + ).api_call else CaseSearchResultsForVeteranFileNumber.new( file_number_or_ssn: case_search, user: current_user - ).call + ).api_call end render_search_results_as_json(result) diff --git a/app/decorators/appeal_status_api_decorator.rb b/app/decorators/appeal_status_api_decorator.rb index a93ed1b9bbf..7be8e8e75aa 100644 --- a/app/decorators/appeal_status_api_decorator.rb +++ b/app/decorators/appeal_status_api_decorator.rb @@ -3,6 +3,12 @@ # Extends the Appeal model with methods for the Appeals Status API class AppealStatusApiDecorator < ApplicationDecorator + def initialize(appeal, scheduled_hearing = nil) + super(appeal) + + @scheduled_hearing = scheduled_hearing + end + def appeal_status_id "A#{id}" end @@ -162,11 +168,11 @@ def remanded_sc_decision_issues end def open_pre_docket_task? - tasks.open.any? { |task| task.is_a?(PreDocketTask) } + open_tasks.any? { |task| task.is_a?(PreDocketTask) } end def pending_schedule_hearing_task? - tasks.open.where(type: ScheduleHearingTask.name).any? + pending_schedule_hearing_tasks.any? end def hearing_pending? @@ -174,7 +180,7 @@ def hearing_pending? end def evidence_submission_hold_pending? - tasks.open.where(type: EvidenceSubmissionWindowTask.name).any? + evidence_submission_hold_pending_tasks.any? end def at_vso? diff --git a/app/models/appeal.rb b/app/models/appeal.rb index 4691f672563..59367fa3a36 100644 --- a/app/models/appeal.rb +++ b/app/models/appeal.rb @@ -308,6 +308,18 @@ def decorated_with_status AppealStatusApiDecorator.new(self) end + def open_tasks + tasks.open + end + + def pending_schedule_hearing_tasks + tasks.open.where(type: ScheduleHearingTask.name) + end + + def evidence_submission_hold_pending_tasks + tasks.open.where(type: EvidenceSubmissionWindowTask.name) + end + # :reek:RepeatedConditionals def active_request_issues_or_decision_issues decision_issues.empty? ? active_request_issues : fetch_all_decision_issues @@ -633,7 +645,7 @@ def direct_review_docket? end def active? - tasks.open.of_type(:RootTask).any? + open_tasks.of_type(:RootTask).any? end def ready_for_distribution? @@ -748,7 +760,7 @@ def substitutions end def status - @status ||= BVAAppealStatus.new(appeal: self) + @status ||= BVAAppealStatus.new(tasks: tasks) end def previously_selected_for_quality_review diff --git a/app/models/hearing.rb b/app/models/hearing.rb index 6008a49d157..7ad17f1b3e1 100644 --- a/app/models/hearing.rb +++ b/app/models/hearing.rb @@ -187,12 +187,15 @@ def advance_on_docket_motion .first end + def scheduled_for + scheduled_for_hearing_day(hearing_day, updated_by, regional_office_timezone) + end + # returns scheduled datetime object considering the timezones # @return [nil] if hearing_day is nil # @return [Time] in scheduled_in_timezone timezone - if scheduled_datetime and scheduled_in_timezone are present # @return [Time] else datetime in regional office timezone - # rubocop:disable Metrics/AbcSize - def scheduled_for + def scheduled_for_hearing_day(hearing_day, updated_by, regional_office_timezone) return nil unless hearing_day # returns datetime in scheduled_in_timezone timezone @@ -234,7 +237,6 @@ def scheduled_for ) end end - # rubocop:enable Metrics/AbcSize def scheduled_for_past? scheduled_for < DateTime.yesterday.in_time_zone(regional_office_timezone) diff --git a/app/models/task.rb b/app/models/task.rb index b2244e0f1b0..4c0b7066290 100644 --- a/app/models/task.rb +++ b/app/models/task.rb @@ -101,7 +101,7 @@ class << self; undef_method :open; end # Equivalent to .reject(&:hide_from_queue_table_view) but offloads that to the database. scope :visible_in_queue_table_view, lambda { where.not( - type: Task.descendants.select(&:hide_from_queue_table_view).map(&:name) + type: hidden_task_classes ) } @@ -138,6 +138,10 @@ class << self # With taks that are likely to need Reader to complete READER_PRIORITY_TASK_TYPES = [JudgeAssignTask.name, JudgeDecisionReviewTask.name].freeze + def hidden_task_classes + Task.descendants.select(&:hide_from_queue_table_view).map(&:name) + end + def reader_priority_task_types READER_PRIORITY_TASK_TYPES end diff --git a/app/models/tasks/evidence_submission_window_task.rb b/app/models/tasks/evidence_submission_window_task.rb index 7c9cdb5f8fa..0028fc54366 100644 --- a/app/models/tasks/evidence_submission_window_task.rb +++ b/app/models/tasks/evidence_submission_window_task.rb @@ -11,7 +11,7 @@ class EvidenceSubmissionWindowTask < Task before_validation :set_assignee - def initialize(args) + def initialize(args = {}) @end_date = args&.fetch(:end_date, nil) super(args&.except(:end_date)) end diff --git a/app/services/bva_appeal_status.rb b/app/services/bva_appeal_status.rb index 1577a7ea7c6..c25886c15a3 100644 --- a/app/services/bva_appeal_status.rb +++ b/app/services/bva_appeal_status.rb @@ -3,7 +3,7 @@ # Determine the BVA workflow status of an Appeal (symbol and string) based on its Tasks. class BVAAppealStatus - attr_reader :status + attr_reader :status, :tasks SORT_KEYS = { not_distributed: 1, @@ -69,8 +69,18 @@ def attorney_task_names end end - def initialize(appeal:) - @appeal = appeal + Tasks = Struct.new( + :open, + :active, + :in_progress, + :cancelled, + :completed, + :assigned, + keyword_init: true + ) + + def initialize(tasks:) + @tasks = tasks @status = compute end @@ -86,15 +96,12 @@ def to_i SORT_KEYS[status] end - def as_json(_args) + def as_json(_args = nil) to_sym end private - attr_reader :appeal - - delegate :tasks, to: :appeal # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength def compute if open_pre_docket_task? @@ -113,7 +120,7 @@ def compute :ready_for_signature elsif active_sign_task? :signed - elsif completed_dispatch_task? && open_tasks.empty? + elsif completed_dispatch_task? && tasks.open.empty? :dispatched elsif completed_dispatch_task? :post_dispatch @@ -133,84 +140,60 @@ def compute end # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength - def open_tasks - @open_tasks ||= tasks.open - end - - def active_tasks - @active_tasks ||= tasks.active - end - - def assigned_tasks - @assigned_tasks ||= tasks.assigned - end - - def in_progress_tasks - @in_progress_tasks ||= tasks.in_progress - end - - def cancelled_tasks - @cancelled_tasks ||= tasks.cancelled - end - - def completed_tasks - @completed_tasks ||= tasks.completed - end - def open_pre_docket_task? - open_tasks.any? { |task| task.is_a?(PreDocketTask) } + tasks.open.any? { |task| task.type == "PreDocketTask" } end def open_distribution_task? - open_tasks.any? { |task| task.is_a?(DistributionTask) } + tasks.open.any? { |task| task.type == "DistributionTask" } end def open_timed_hold_task? - open_tasks.any? { |task| task.is_a?(TimedHoldTask) } + tasks.open.any? { |task| task.type == "TimedHoldTask" } end def active_judge_assign_task? - active_tasks.any? { |task| task.is_a?(JudgeAssignTask) } + tasks.active.any? { |task| task.type == "JudgeAssignTask" } end def assigned_attorney_task? - assigned_tasks.any? { |task| self.class.attorney_task_names.include?(task.type) } + tasks.assigned.any? { |task| self.class.attorney_task_names.include?(task.type) } end def active_colocated_task? - active_tasks.any? { |task| self.class.colocated_task_names.include?(task.type) } + tasks.active.any? { |task| self.class.colocated_task_names.include?(task.type) } end def attorney_task_in_progress? - in_progress_tasks.any? { |task| self.class.attorney_task_names.include?(task.type) } + tasks.in_progress.any? { |task| self.class.attorney_task_names.include?(task.type) } end def active_judge_decision_review_task? - active_tasks.any? { |task| task.is_a?(JudgeDecisionReviewTask) } + tasks.active.any? { |task| task.type == "JudgeDecisionReviewTask" } end def active_sign_task? - active_tasks.any? { |task| %w[BvaDispatchTask QualityReviewTask].include?(task.type) } + tasks.active.any? { |task| %w[BvaDispatchTask QualityReviewTask].include?(task.type) } end def completed_dispatch_task? - completed_tasks.any? { |task| task.is_a?(BvaDispatchTask) } + tasks.completed.any? { |task| task.type == "BvaDispatchTask" } end def docket_switched? # TODO: this should be updated to check that there are no active tasks once the task handling is implemented - completed_tasks.any? { |task| task.is_a?(DocketSwitchGrantedTask) } + tasks.completed.any? { |task| task.type == "DocketSwitchGrantedTask" } end def cancelled_root_task? - cancelled_tasks.any? { |task| task.is_a?(RootTask) } + tasks.cancelled.any? { |task| task.type == "RootTask" } end def misc_task? - active_tasks.any? { |task| self.class.misc_task_names.include?(task.type) } + tasks.active.any? { |task| self.class.misc_task_names.include?(task.type) } end def active_specialty_case_team_assign_task? - active_tasks.any? { |task| task.is_a?(SpecialtyCaseTeamAssignTask) } + tasks.active.any? { |task| task.type == "SpecialtyCaseTeamAssignTask" } end end diff --git a/app/services/search_query_service.rb b/app/services/search_query_service.rb new file mode 100644 index 00000000000..f3434d4c9a6 --- /dev/null +++ b/app/services/search_query_service.rb @@ -0,0 +1,113 @@ +# frozen_string_literal: true + +class SearchQueryService + def initialize(file_number: nil, docket_number: nil, veteran_ids: nil) + @docket_number = docket_number + @file_number = file_number + @veteran_ids = veteran_ids + @queries = SearchQueryService::Query.new + end + + def search_by_veteran_file_number + combined_results + end + + def search_by_docket_number + results = ActiveRecord::Base.connection.exec_query( + sanitize( + queries.docket_number_query(docket_number) + ) + ) + + results.map do |row| + AppealRow.new(row).search_response + end + end + + def search_by_veteran_ids + combined_results + end + + private + + attr_reader :docket_number, :file_number, :queries, :veteran_ids + + def combined_results + search_results.map do |row| + if row["type"] != "legacy_appeal" + AppealRow.new(row).search_response + else + vacols_row = vacols_results.find { |result| result["vacols_id"] == row["external_id"] } + + Rails.logger.warn(no_vacols_record_warning(result)) if vacols_row.blank? + LegacyAppealRow.new(row, vacols_row || null_vacols_row).search_response + end + end + end + + def null_vacols_row + {} + end + + def no_vacols_record_warning(result) + <<-WARN + No corresponding VACOLS record found for appeal with: + id: #{result['id']} + vacols_id: #{result['vacols_id']} + searching with: + #{file_number.present? "file_number #{file_number}"} \ + #{veteran_ids.present? "veteran_ids #{veteran_ids.join(',')}"} \ + #{file_number.present? "docket_number #{docket_number}"} + WARN + end + + def vacols_ids + legacy_results.map { |result| result["external_id"] } + end + + def legacy_results + search_results.select { |result| result["type"] == "legacy_appeal" } + end + + def search_results + @search_results ||= + if file_number.present? + file_number_search_results + else + veteran_ids_search_results + end + end + + def veteran_ids_search_results + ActiveRecord::Base + .connection + .exec_query( + sanitize(queries.veteran_ids_query(veteran_ids)) + ) + .uniq { |result| result["external_id"] } + end + + def file_number_search_results + ActiveRecord::Base + .connection + .exec_query(file_number_or_ssn_query) + .uniq { |result| result["external_id"] } + end + + def file_number_or_ssn_query + sanitize( + queries.veteran_file_number_query(file_number) + ) + end + + def vacols_results + @vacols_results ||= begin + vacols_query = VACOLS::Record.sanitize_sql_array(queries.vacols_query(vacols_ids)) + VACOLS::Record.connection.exec_query(vacols_query) + end + end + + def sanitize(values) + ActiveRecord::Base.sanitize_sql_array(values) + end +end diff --git a/app/services/search_query_service/api_response.rb b/app/services/search_query_service/api_response.rb new file mode 100644 index 00000000000..76a216b678e --- /dev/null +++ b/app/services/search_query_service/api_response.rb @@ -0,0 +1,3 @@ +# frozen_string_literal: true + +SearchQueryService::ApiResponse = Struct.new(:id, :type, :attributes, keyword_init: true) diff --git a/app/services/search_query_service/appeal_row.rb b/app/services/search_query_service/appeal_row.rb new file mode 100644 index 00000000000..fd6046b1216 --- /dev/null +++ b/app/services/search_query_service/appeal_row.rb @@ -0,0 +1,177 @@ +# frozen_string_literal: true + +class SearchQueryService::AppealRow + def initialize(query_row) + @query_row = query_row + end + + def search_response + SearchQueryService::SearchResponse.new( + queried_appeal, + :appeal, + SearchQueryService::ApiResponse.new( + id: query_row["id"], + type: "appeal", + attributes: attributes + ) + ) + end + + private + + attr_reader :query_row + + # rubocop:disable Metrics/MethodLength + def attributes + SearchQueryService::Attributes.new( + aod: aod, + appellant_full_name: appellant_full_name, + assigned_to_location: queried_appeal.assigned_to_location, + assigned_attorney: assigned_attorney, + assigned_judge: assigned_judge, + caseflow_veteran_id: query_row["veteran_id"], + contested_claim: contested_claim, + decision_date: decision_date, + decision_issues: decision_issues, + docket_name: query_row["docket_type"], + docket_number: docket_number, + external_id: query_row["external_id"], + hearings: hearings, + issues: issues, + mst: mst_status, + overtime: query_row["overtime"], + pact: pact_status, + paper_case: false, + status: queried_appeal.status, + type: stream_type, + veteran_appellant_deceased: veteran_appellant_deceased, + veteran_file_number: veteran_file_number, + veteran_full_name: veteran_full_name, + withdrawn: withdrawn + ) + end + # rubocop:enable Metrics/MethodLength + + def aod + query_row["aod_granted_for_person"].present? + end + + def decision_issues + json_array("decision_issues") + end + + def docket_number + attrs, = JSON.parse query_row["appeal"] + attrs["stream_docket_number"] + end + + def decision_date + Date.parse(query_row["decision_date"]) + rescue TypeError, Date::Error + nil + end + + def appellant_full_name + FullName.new(query_row["person_first_name"], "", query_row["person_last_name"]).to_s + end + + def veteran_full_name + FullName.new(query_row["veteran_first_name"], "", query_row["veteran_last_name"]).to_s + end + + def veteran_file_number + attrs, = JSON.parse(query_row["appeal"]) + attrs["veteran_file_number"] + end + + def issue(attributes) + unless FeatureToggle.enabled?(:pact_identification) + attributes.delete("pact_status") + end + unless FeatureToggle.enabled?(:mst_identification) + attributes.delete("mst_status") + end + end + + def issues + json_array("request_issues").map do |attributes| + attributes.tap do |attrs| + issue(attrs) + end + end + end + + def hearings + json_array("hearings").map do |attrs| + AppealHearingSerializer.new( + SearchQueryService::QueriedHearing.new(attrs), + { user: RequestStore[:current_user] } + ).serializable_hash[:data][:attributes] + end + end + + def withdrawn + WithdrawnDecisionReviewPolicy.new( + Struct.new( + :active_request_issues, + :withdrawn_request_issues + ).new( + json_array("active_request_issues"), + json_array("active_request_issues") + ) + ).satisfied? + end + + def stream_type + (query_row["stream_type"] || "Original").titleize + end + + def contested_claim + json_array("active_request_issues").any? do |issue| + %w(Contested Apportionment).any? do |code| + category = issue["nonrating_issue_category"] || "" + category.include?(code) + end + end + end + + def veteran_appellant_deceased + !!query_row["date_of_death"] && !json_array("appeal").first["veteran_is_not_claimant"] + end + + def pact_status + json_array("decision_issues").any? do |issue| + issue["pact_status"] + end + end + + def mst_status + json_array("decision_issues").any? do |issue| + issue["mst_status"] + end + end + + def queried_appeal + @queried_appeal ||= begin + appeal_attrs, = JSON.parse query_row["appeal"] + + SearchQueryService::QueriedAppeal.new( + attributes: appeal_attrs, + tasks_attributes: json_array("tasks"), + hearings_attributes: json_array("hearings") + ) + end + end + + def assigned_attorney + json_array("assigned_attorney").first + end + + def assigned_judge + json_array("assigned_judge").first + end + + def json_array(key) + JSON.parse(query_row[key] || "[]") + end +end diff --git a/app/services/search_query_service/attributes.rb b/app/services/search_query_service/attributes.rb new file mode 100644 index 00000000000..6c4487a9b1e --- /dev/null +++ b/app/services/search_query_service/attributes.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +SearchQueryService::Attributes = Struct.new( + :aod, + :power_of_attorney, + :appellant_full_name, + :assigned_attorney, + :assigned_judge, + :assigned_to_location, + :caseflow_veteran_id, + :decision_date, + :decision_issues, + :docket_name, + :docket_number, + :external_id, + :hearings, + :issues, + :mst, + :overtime, + :pact, + :paper_case, + :status, + :type, + :veteran_appellant_deceased, + :veteran_file_number, + :veteran_full_name, + :contested_claim, + :withdrawn, + keyword_init: true +) diff --git a/app/services/search_query_service/legacy_appeal_row.rb b/app/services/search_query_service/legacy_appeal_row.rb new file mode 100644 index 00000000000..b9dfe0af476 --- /dev/null +++ b/app/services/search_query_service/legacy_appeal_row.rb @@ -0,0 +1,164 @@ +# frozen_string_literal: true + +class SearchQueryService::LegacyAppealRow + def initialize(search_row, vacols_row) + @search_row = search_row + @vacols_row = vacols_row + end + + def search_response + SearchQueryService::SearchResponse.new( + legacy_appeal, + :legacy_appeal, + SearchQueryService::ApiResponse.new( + id: search_row["id"], + type: "legacy_appeal", + attributes: attributes + ) + ) + end + + private + + attr_reader :search_row, :vacols_row + + # rubocop:disable Metrics/MethodLength + def attributes + SearchQueryService::LegacyAttributes.new( + aod: aod, + appellant_full_name: appellant_full_name, + assigned_attorney: assigned_attorney, + assigned_judge: assigned_judge, + assigned_to_location: vacols_row["bfcurloc"], + caseflow_veteran_id: search_row["veteran_id"], + decision_date: decision_date, + docket_name: "legacy", + docket_number: vacols_row["tinum"], + external_id: vacols_row["vacols_id"], + hearings: hearings, + issues: issues, + mst: mst, + overtime: search_row["overtime"], + pact: pact, + paper_case: paper_case, + status: status, + type: stream_type, + veteran_appellant_deceased: veteran_appellant_deceased, + veteran_file_number: search_row["veteran_file_number"], + veteran_full_name: veteran_full_name + ) + end + # rubocop:enable Metrics/MethodLength + + def hearings + vacols_json_array("hearings").map do |attrs| + HearingAttributes.new(attrs).call + end + end + + class HearingAttributes + def initialize(attributes) + @attributes = attributes + end + + def call + { + disposition: VACOLS::CaseHearing::HEARING_DISPOSITIONS[attributes["disposition"].try(:to_sym)], + request_type: attributes["type"], + appeal_type: VACOLS::Case::TYPES[attributes["bfac"]], + external_id: attributes["external_id"], + date: HearingMapper.datetime_based_on_type( + datetime: attributes["date"], + regional_office: regional_office(attributes["venue"]), + type: attributes["type"] + ) + } + end + + private + + attr_reader :attributes + + def regional_office(ro_key) + RegionalOffice.find!(ro_key) + rescue NotFoundError + nil + end + end + + def issues + vacols_json_array("issues").map do |attrs| + WorkQueue::LegacyIssueSerializer.new( + Issue.load_from_vacols(attrs) + ).serializable_hash[:data][:attributes] + end + end + + def assigned_attorney + json_array("assigned_attorney").first + end + + def assigned_judge + json_array("assigned_judge").first + end + + def json_array(key) + JSON.parse(search_row[key] || "[]") + end + + def vacols_json_array(key) + JSON.parse(vacols_row[key] || "[]") + end + + def veteran_appellant_deceased + search_row["date_of_death"].present? && + search_row["person_first_name"].present? + end + + def stream_type + VACOLS::Case::TYPES[vacols_row["bfac"]] + end + + def status + VACOLS::Case::STATUS[vacols_row["bfmpro"]] + end + + def paper_case + folder = Struct.new(:tivbms, :tisubj2).new( + vacols_row["tivbms"], + vacols_row["tisubj2"] + ) + AppealRepository.folder_type_from(folder) + end + + def mst + vacols_row["issues_mst_count"].to_i > 0 + end + + def pact + vacols_row["issues_pact_count"].to_i > 0 + end + + def appellant_full_name + FullName.new(vacols_row["sspare2"], "", vacols_row["sspare1"]).to_s + end + + def veteran_full_name + FullName.new(vacols_row["snamef"], "", vacols_row["snamel"]).to_s + end + + def aod + vacols_row["aod"].to_i == 1 + end + + def decision_date + AppealRepository.normalize_vacols_date(vacols_row["bfddec"]) + end + + def legacy_appeal + @legacy_appeal ||= begin + appeal_attrs, = JSON.parse search_row["appeal"] + SearchQueryService::QueriedLegacyAppeal.new(attributes: appeal_attrs) + end + end +end diff --git a/app/services/search_query_service/legacy_attributes.rb b/app/services/search_query_service/legacy_attributes.rb new file mode 100644 index 00000000000..5ac4e1439bd --- /dev/null +++ b/app/services/search_query_service/legacy_attributes.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +SearchQueryService::LegacyAttributes = Struct.new( + :aod, + :appellant_full_name, + :assigned_attorney, + :assigned_judge, + :assigned_to_location, + :caseflow_veteran_id, + :decision_date, + :docket_name, + :docket_number, + :external_id, + :hearings, + :issues, + :mst, + :overtime, + :pact, + :paper_case, + :status, + :type, + :veteran_appellant_deceased, + :veteran_file_number, + :veteran_full_name, + :withdrawn, + keyword_init: true +) diff --git a/app/services/search_query_service/queried_appeal.rb b/app/services/search_query_service/queried_appeal.rb new file mode 100644 index 00000000000..297ad46cdcf --- /dev/null +++ b/app/services/search_query_service/queried_appeal.rb @@ -0,0 +1,133 @@ +# frozen_string_literal: true + +class SearchQueryService::QueriedAppeal < SimpleDelegator + def initialize(attributes:, tasks_attributes:, hearings_attributes:) + @attributes = OpenStruct.new( + appeal: attributes, + tasks: tasks_attributes, + hearings: hearings_attributes, + root_task: attributes.delete("root_task") || {}, + claimants: attributes.delete("claimants") || [] + ) + + super(appeal) + end + + def assigned_to_location + return COPY::CASE_LIST_TABLE_POST_DECISION_LABEL if root_task&.status == Constants.TASK_STATUSES.completed + + return most_recently_updated_visible_task.assigned_to_label if most_recently_updated_visible_task + + # this condition is no longer needed since we only want active or on hold tasks + return most_recently_updated_task&.assigned_to_label if most_recently_updated_task.present? + + fetch_api_status + end + + def claimant_participant_ids + claimants.map(&:participant_id) + end + + def claimants + @claimants ||= begin + attributes.claimants.map do |attrs| + Struct.new(:participant_id).new(attrs["participant_id"]) + end + end + end + + def root_task + @root_task ||= begin + if attributes.root_task.present? + Task.new.tap do |task| + task.assign_attributes attributes.root_task + end + end + end + end + + def open_tasks + @open_tasks ||= tasks.select do |task| + Task.open_statuses.include?(task.status) + end + end + + def active? + Task.active_statuses.include?(attributes.root_task["status"]) + end + + def pending_schedule_hearing_tasks + open_tasks.select { |task| task.type == "ScheduleHearingTask" } + end + + def evidence_submission_hold_pending_tasks + open_tasks.select { |task| task.type == "EvidenceSubmissionWindowTask" } + end + + def status + BVAAppealStatus.new( + tasks: BVAAppealStatus::Tasks.new( + open: tasks.select(&:open?), + active: tasks.select(&:active?), + in_progress: tasks.select(&:in_progress?), + cancelled: tasks.select(&:cancelled?), + completed: tasks.select(&:completed?), + assigned: tasks.select(&:assigned?) + ) + ).status + end + + private + + attr_reader :attributes + + def appeal + @appeal ||= Appeal.new.tap do |appeal| + appeal.assign_attributes(attributes.appeal) + end + end + + def most_recently_updated_visible_task + visible_tasks.select { |task| Task.active_statuses.include?(task.status) }.max_by(&:updated_at) || + visible_tasks.select { |task| task.status == "on_hold" }.max_by(&:updated_at) + end + + def visible_tasks + @visible_tasks ||= tasks.reject do |task| + Task.hidden_task_classes.include?(task.type) + end + end + + def most_recently_updated_task + tasks.max_by(&:updated_at) + end + + def tasks + @tasks ||= begin + attributes.tasks.map do |attrs| + attrs["type"].constantize.new.tap do |task| + task.assign_attributes attrs + end + end + end + end + + def fetch_api_status + AppealStatusApiDecorator.new( + self, + scheduled_hearing + ).fetch_status.to_s.titleize.to_sym + end + + def scheduled_hearing + @scheduled_hearing ||= begin + hearings = attributes.hearings.map do |attrs| + SearchQueryService::QueriedHearing.new(attrs) + end + + hearings.reject(&:disposition).find do |hearing| + hearing.scheduled_for >= Time.zone.today + end + end + end +end diff --git a/app/services/search_query_service/queried_hearing.rb b/app/services/search_query_service/queried_hearing.rb new file mode 100644 index 00000000000..e179866f95b --- /dev/null +++ b/app/services/search_query_service/queried_hearing.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +class SearchQueryService::QueriedHearing < SimpleDelegator + def initialize(attributes) + @attributes = attributes + + manage_attributes! + + super(hearing) + end + + def judge + @judge ||= + if judge_attributes.present? + User.new.tap do |j| + j.assign_attributes judge_attributes + end + end + end + + def hearing_views + @hearing_views ||= + views_attributes.map do |view_attrs| + HearingView.new.tap do |v| + v.assign_attributes view_attrs + end + end + end + + def readable_request_type + Hearing::HEARING_TYPES[hearing_day&.request_type&.to_sym] + end + + def hearing_day + @hearing_day ||= HearingDay.new.tap do |hd| + hd.assign_attributes hearing_day_attributes + end + end + + def updated_by + @updated_by ||= User.new.tap do |u| + u.assign_attributes updated_by_attributes + end + end + + def virtual? + %w(pending active closed).include?( + virtual_hearing.status + ) + end + + def scheduled_for + scheduled_for_hearing_day(hearing_day, updated_by, regional_office_timezone) + end + + private + + attr_reader( + :attributes, + :hearing_day_attributes, + :updated_by_attributes, + :views_attributes, + :virtual_hearing_attributes, + :judge_attributes + ) + + def regional_office_timezone + RegionalOffice.find!(hearing_day.regional_office || "C").timezone + end + + def virtual_hearing + @virtual_hearing ||= VirtualHearing.new.tap do |vh| + vh.assign_attributes virtual_hearing_attributes + end + end + + def hearing + Hearing.new.tap do |hearing| + hearing.assign_attributes attributes + end + end + + def manage_attributes! + @hearing_day_attributes = attributes.delete("hearing_day") + @updated_by_attributes = attributes.delete("updated_by") + @views_attributes = attributes.delete("views") || [] + @virtual_hearing_attributes = attributes.delete("virtual_hearing") + @judge_attributes = attributes.delete("judge") + end +end diff --git a/app/services/search_query_service/queried_legacy_appeal.rb b/app/services/search_query_service/queried_legacy_appeal.rb new file mode 100644 index 00000000000..50aaab31a7f --- /dev/null +++ b/app/services/search_query_service/queried_legacy_appeal.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +class SearchQueryService::QueriedLegacyAppeal < SimpleDelegator + def initialize(attributes:) + @attributes = attributes + @root_task_attributes = attributes.delete("root_task") + @veteran_attributes = attributes.delete("veteran") + + super(legacy_appeal) + end + + def veteran + @veteran ||= Veteran.new.tap do |veteran| + veteran.assign_attributes veteran_attributes + end + end + + def root_task + @root_task ||= begin + if root_task_attributes + RootTask.new.tap do |root_task| + root_task.assign_attributes root_task_attributes + end + end + end + end + + def claimant_participant_ids + veteran.participant_id + end + + private + + attr_reader :attributes, :root_task_attributes, :veteran_attributes + + def legacy_appeal + @legacy_appeal ||= LegacyAppeal.new.tap do |appeal| + appeal.assign_attributes(attributes) + end + end +end diff --git a/app/services/search_query_service/query.rb b/app/services/search_query_service/query.rb new file mode 100644 index 00000000000..b63abc2085b --- /dev/null +++ b/app/services/search_query_service/query.rb @@ -0,0 +1,416 @@ +# frozen_string_literal: true + +class SearchQueryService::Query + def initialize + @vacols = SearchQueryService::VacolsQuery.new + end + + def docket_number_query(docket_number) + query = <<-SQL + #{appeals_internal_query} + where a.stream_docket_number=?; + SQL + [query, docket_number] + end + + def veteran_file_number_query(file_number) + query = <<-SQL + ( + #{appeals_internal_query} + where v.ssn=? or v.file_number=? + ) + UNION + ( + #{legacy_appeals_internal_query} + where v.ssn=? or v.file_number=? + ) + SQL + num_params = 4 + [query, *[file_number].cycle(num_params).to_a] + end + + def veteran_ids_query(veteran_id) + query = <<-SQL + ( + #{appeals_internal_query} + where v.id in (?) + ) + UNION + ( + #{legacy_appeals_internal_query} + where v.id in (?) + ) + SQL + [query, veteran_id, veteran_id] + end + + def vacols_query(vacols_ids) + [vacols.query, vacols_ids] + end + + private + + attr_reader :vacols + + def legacy_appeals_internal_query + <<-SQL + select + a.id, + a.vacols_id external_id, + 'legacy_appeal' type, + null aod_granted_for_person, + 'legacy' docket_type, + ( + select + jsonb_agg(la2) + from + ( + select + la.*, + ( + select + row_to_json(t.*) + from tasks t + where + t.appeal_id=a.id and + t.type='RootTask' and + t.appeal_type='Appeal' + order by t.updated_at desc + limit 1 + ) root_task, + (select row_to_json(v.*)) veteran + from legacy_appeals la + where la.id=a.id + ) la2 + ) appeal, + dd.decision_date, + wm.overtime, + pp.first_name person_first_name, + pp.last_name person_last_name, + v.id veteran_id, + v.first_name veteran_first_name, + v.last_name veteran_last_name, + v.file_number as veteran_file_number, + v.date_of_death, + ( + select jsonb_agg(u2) from + ( + select + u.* + from tasks t + left join users u on + t.assigned_to_id=u.id and + t.assigned_to_type='User' + where + t.appeal_type = 'LegacyAppeal' and + t.appeal_id=a.id and + t.type in ('#{attorney_task_classes.join("','")}') and + t.status != '#{Constants.TASK_STATUSES.cancelled}' + order by t.created_at desc + limit 1 + ) u2 + ) assigned_attorney, + ( + select jsonb_agg(u2) from + ( + select + u.* + from tasks t + left join users u on + t.assigned_to_id=u.id and + t.assigned_to_type='User' + where + t.appeal_type = 'LegacyAppeal' and + t.appeal_id=a.id and + t.type in ('#{judge_task_classes.join("','")}') and + t.status != '#{Constants.TASK_STATUSES.cancelled}' + order by t.created_at desc + limit 1 + ) u2 + ) assigned_judge, + null request_issues, + null active_request_issues, + null withdrawn_request_issues, + null decision_issues, + null hearings_count, + '[]' hearings, + ( + select jsonb_agg(t2) from + ( + select + t.* + from tasks t + left join organizations o on o.id=t.assigned_to_id + left join users u on u.id=t.assigned_to_id + where + t.appeal_id=a.id and + t.appeal_type='LegacyAppeal' + order by updated_at desc + ) t2 + ) tasks + from legacy_appeals a + left join claimants cl on cl.decision_review_id=a.id and cl.decision_review_type='LegacyAppeal' + left join people pp on cl.participant_id=pp.participant_id + left join work_modes wm on wm.appeal_id=a.id and wm.appeal_type='LegacyAppeal' + left join decision_documents dd on dd.appeal_id=a.id and dd.appeal_type='LegacyAppeal' + left join veterans v on v.file_number=( + select + case + when right(a.vbms_id, 1) = 'C' then lpad(regexp_replace(a.vbms_id, '[^0-9]+', '', 'g'), 8, '0') + else regexp_replace(a.vbms_id, '[^0-9]+', '', 'g') + end + ) or v.ssn=( + select + case + when right(a.vbms_id, 1) = 'C' then lpad(regexp_replace(a.vbms_id, '[^0-9]+', '', 'g'), 8, '0') + else regexp_replace(a.vbms_id, '[^0-9]+', '', 'g') + end + ) + SQL + end + + def appeals_internal_query + <<-SQL + select + a.id, + a.uuid::varchar external_id, + a.stream_type type, + aod.id aod_granted_for_person, + a.docket_type, + ( + select jsonb_agg(a2) + from + ( + select + appeals.*, + ( + select + row_to_json(t.*) + from tasks t + where + t.appeal_id=a.id and + t.type='RootTask' and + t.appeal_type='Appeal' + order by updated_at desc + limit 1 + ) root_task, + ( + select jsonb_agg(c2) from + ( + select + c.id, + c.participant_id + from claimants c + where + c.decision_review_type = 'Appeal' and + c.decision_review_id=a.id + ) c2 + ) claimants + from appeals + where id=a.id + ) a2 + ) appeal, + dd.decision_date, + wm.overtime, + pp.first_name person_first_name, + pp.last_name person_last_name, + v.id veteran_id, + v.first_name veteran_first_name, + v.last_name veteran_last_name, + v.file_number as veteran_file_number, + v.date_of_death, + ( + select jsonb_agg(u2) from + ( + select + u.* + from tasks t + left join users u on + t.assigned_to_id=u.id and + t.assigned_to_type='User' + where + t.appeal_type = 'Appeal' and + t.appeal_id=a.id and + t.type in ('#{attorney_task_classes.join("','")}') and + t.status != '#{Constants.TASK_STATUSES.cancelled}' + order by t.created_at desc + limit 1 + ) u2 + ) assigned_attorney, + ( + select jsonb_agg(u2) from + ( + select + u.* + from tasks t + left join users u on + t.assigned_to_id=u.id and + t.assigned_to_type='User' + where + t.appeal_type = 'Appeal' and + t.appeal_id=a.id and + t.type in ('#{judge_task_classes.join("','")}') and + t.status != '#{Constants.TASK_STATUSES.cancelled}' + order by t.created_at desc + limit 1 + ) u2 + ) assigned_judge, + ( + select jsonb_agg(ri2) from + ( + select + ri.id, + ri.benefit_type program, + ri.notes, + ri.decision_date, + ri.nonrating_issue_category, + ri.mst_status, + ri.pact_status, + ri.mst_status_update_reason_notes mst_justification, + ri.pact_status_update_reason_notes pact_justification + from request_issues ri + where + ri.decision_review_type='Appeal' and + ri.decision_review_id=a.id + ) ri2 + ) request_issues, + ( + select jsonb_agg(ri2) from + ( + select + nonrating_issue_category + from request_issues ri + where + ri.ineligible_reason is null and + ri.closed_at is null and + (ri.split_issue_status is null or ri.split_issue_status = 'in_progress') and + ri.decision_review_type='Appeal' and + ri.decision_review_id=a.id + ) ri2 + ) active_request_issues, + ( + select jsonb_agg(ri2) from + ( + select + nonrating_issue_category + from request_issues ri + where + ri.ineligible_reason is null and + ri.closed_status = 'widthrawn' and + ri.decision_review_type='Appeal' and + ri.decision_review_id=a.id + ) ri2 + ) withdrawn_request_issues, + ( + select jsonb_agg(di2) from + ( + select + di.id, + di.disposition, + di.description, + di.benefit_type, + di.diagnostic_code, + di.mst_status, + di.pact_status, + array( + select rdi.request_issue_id + from request_decision_issues rdi + where rdi.decision_issue_id=di.id + ) request_issue_ids, + array( + select rr2 from + ( + select + rr.id, + rr.code, + rr.post_aoj + from remand_reasons rr + where rr.decision_issue_id=di.id + ) rr2 + ) remand_reasons + from decision_issues di + where + di.decision_review_type='Appeal' and + di.decision_review_id = a.id + ) di2 + ) decision_issues, + (select count(id) from hearings h where h.appeal_id=a.id) hearings_count, + ( + select jsonb_agg(h2) from + ( + select + h.*, + ( + select row_to_json(ub) + from (select * from users u where u.id=h.updated_by_id limit 1) ub + ) updated_by, + ( + select row_to_json(hd2) + from (select * from hearing_days hd where hd.id=h.hearing_day_id limit 1) hd2 + ) hearing_day, + ( + select row_to_json(vh2) + from (select * from virtual_hearings vh where vh.hearing_id=h.id limit 1) vh2 + ) virtual_hearing, + ( + select jsonb_agg(hv2) + from (select * from hearing_views hv where hv.hearing_id=h.id) hv2 + ) views, + ( + select row_to_json(j2) + from (select u.full_name from users u where u.id=h.judge_id limit 1) j2 + ) judge + from + hearings h + where + h.appeal_id=a.id + ) h2 + ) hearings, + ( + select jsonb_agg(t2) from + ( + select + t.* + from tasks t + left join organizations o on o.id=t.assigned_to_id + left join users u on u.id=t.assigned_to_id + where + t.appeal_id=a.id and + t.appeal_type='Appeal' + order by updated_at desc + ) t2 + ) tasks + from appeals a + left join claimants cl on cl.decision_review_id=a.id and cl.decision_review_type='Appeal' + left join people pp on cl.participant_id=pp.participant_id + left join work_modes wm on wm.appeal_id=a.id and wm.appeal_type='Appeal' + left join decision_documents dd on dd.appeal_id=a.id and dd.appeal_type='Appeal' + left join advance_on_docket_motions aod on aod.appeal_id=a.id and aod.person_id=pp.id and aod.appeal_type='Appeal' + left join veterans v on a.veteran_file_number=v.file_number or a.veteran_file_number=v.ssn + SQL + end + + def attorney_task_classes + [ + "AttorneyTask", + *AttorneyTask.descendants.map(&:name) + # 'AttorneyRewriteTask', + # 'AttorneyDispatchReturnTask', + # 'DocketSwitchGrantedTask', + # 'DocketSwitchDeniedTask', + ] + end + + def judge_task_classes + [ + "JudgeTask", + *JudgeTask.descendants.map(&:name) + # 'JudgeAddressMotionToVacateTask', + # 'JudgeQualityReviewTask', + # 'JudgeDispatchReturnTask', + # 'JudgeAssignTask', + # 'DocketSwitchRulingTask', + # 'JudgeDecisionReviewTask' + ] + end +end diff --git a/app/services/search_query_service/search_response.rb b/app/services/search_query_service/search_response.rb new file mode 100644 index 00000000000..09ca32c022e --- /dev/null +++ b/app/services/search_query_service/search_response.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +SearchQueryService::SearchResponse = Struct.new(:appeal, :type, :api_response) do + def filter_restricted_info!(statuses) + if statuses.include?(api_response.attributes.status) + api_response.attributes.assigned_to_location = nil + end + end +end diff --git a/app/services/search_query_service/vacols_query.rb b/app/services/search_query_service/vacols_query.rb new file mode 100644 index 00000000000..68a9d287451 --- /dev/null +++ b/app/services/search_query_service/vacols_query.rb @@ -0,0 +1,112 @@ +# frozen_string_literal: true + +class SearchQueryService::VacolsQuery + def query + <<-SQL + select + aod, + "cases".bfkey vacols_id, + "cases".bfcurloc, + "cases".bfddec, + "cases".bfmpro, + "cases".bfac, + "cases".bfcorlid, + "correspondents".snamef, + "correspondents".snamel, + "correspondents".sspare1, + "correspondents".sspare2, + "correspondents".slogid, + "folders".tinum, + "folders".tivbms, + "folders".tisubj2, + (select + JSON_ARRAYAGG(JSON_OBJECT( + 'venue' value #{case_hearing_venue_select}, + 'external_id' value "h".hearing_pkseq, + 'type' value "h".hearing_type, + 'disposition' value "h".hearing_disp, + 'date' value "h".hearing_date + )) + from hearsched "h" + where "h".folder_nr="cases".bfkey + ) hearings, + (select + JSON_ARRAYAGG(JSON_OBJECT( + 'id' value "i".isskey, + 'vacols_sequence_id' value "i".issseq, + 'issprog' value "i".issprog, + 'isscode' value "i".isscode, + 'isslev1' value "i".isslev1, + 'isslev2' value "i".isslev2, + 'isslev3' value "i".isslev3, + 'issdc' value "i".issdc, + 'issdesc' value "i".issdesc, + 'issdcls' value "i".issdcls, + 'issmst' value "i".issmst, + 'isspact' value "i".isspact, + 'issprog_label' value "iss".prog_desc, + 'isscode_label' value "iss".iss_desc, + 'isslev1_label' value case when "i".isslev1 is not null then + case when "iss".lev1_code = '##' then + "vft".ftdesc else "iss".lev1_desc + end + end , + 'isslev2_label' value case when "i".isslev2 is not null then + case when "iss".lev2_code = '##' then + "vft".ftdesc else "iss".lev2_desc + end + end, + 'isslev3_label' value case when "i".isslev3 is not null then + case when "iss".lev3_code = '##' then + "vft".ftdesc else "iss".lev3_desc + end + end + )) + from issues "i" + inner join issref "iss" + on "i".issprog = "iss".prog_code + and "i".isscode = "iss".iss_code + and ("i".isslev1 is null + or "iss".lev1_code = '##' + or "i".isslev1 = "iss".lev1_code) + and ("i".isslev2 is null + or "iss".lev2_code = '##' + or "i".isslev2 = "iss".lev2_code) + and ("i".isslev3 is null + or "iss".lev3_code = '##' + or "i".isslev3 = "iss".lev3_code) + left join vftypes "vft" + on "vft".fttype = 'dg' + and (("iss".lev1_code = '##' and 'dg' || "i".isslev1 = "vft".ftkey) + or ("iss".lev2_code = '##' and 'dg' || "i".isslev2 = "vft".ftkey) + or ("iss".lev3_code = '##' and 'dg' || "i".isslev3 = "vft".ftkey)) + where "i".isskey="cases".bfkey + ) issues, + (select count("hearings".hearing_pkseq) from hearsched "hearings" where "hearings".folder_nr="cases".bfkey) hearing_count, + (select count("issues".isskey) from issues "issues" where "issues".isskey="cases".bfkey and "issues".issmst='Y') issues_mst_count, + (select count("issues".isskey) from issues "issues" where "issues".isskey="cases".bfkey and "issues".isspact='Y') issues_pact_count + from + brieff "cases" + left join folder "folders" + on "cases".bfkey="folders".ticknum + left join corres "correspondents" + on "cases".bfcorkey="correspondents".stafkey + #{VACOLS::Case::JOIN_AOD} + where + "cases".bfkey in (?) + SQL + end + + private + + def case_hearing_venue_select + <<-SQL + case + when "h".hearing_type='#{VACOLS::CaseHearing::HEARING_TYPE_LOOKUP[:video]}' AND + "h".hearing_date < '#{VACOLS::CaseHearing::VACOLS_VIDEO_HEARINGS_END_DATE}' + then #{Rails.application.config.vacols_db_name}.HEARING_VENUE("h".vdkey) + else "cases".bfregoff + end + SQL + end +end diff --git a/app/services/search_query_service/vso_user_search_results.rb b/app/services/search_query_service/vso_user_search_results.rb new file mode 100644 index 00000000000..7b7e313b29e --- /dev/null +++ b/app/services/search_query_service/vso_user_search_results.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +class SearchQueryService::VsoUserSearchResults + def initialize(search_results:, user:) + @user = user + @search_results = search_results + + filter_restricted_results! + end + + def call + established_results.select do |result| + if result.type == :appeal + result.appeal.claimants.any? do |claimant| + vso_participant_ids.include?(poas.dig(claimant.participant_id, :participant_id)) + end + else + vso_participant_ids.include?(poas.dig(result.appeal.veteran.participant_id, :participant_id)) + end + end + end + + private + + attr_reader :search_results, :user + + RESTRICTED_STATUSES = + [ + :distributed_to_judge, + :ready_for_signature, + :on_hold, + :misc, + :unknown, + :assigned_to_attorney + ].freeze + + def filter_restricted_results! + search_results.map do |result| + result.filter_restricted_info!(RESTRICTED_STATUSES) + end + end + + def vso_participant_ids + @vso_participant_ids ||= user.vsos_user_represents.map { |poa| poa[:participant_id] } + end + + def established_results + @established_results ||= search_results.select do |result| + result.type == :legacy_appeal || result.appeal.established_at.present? + end + end + + def claimant_participant_ids + @claimant_participant_ids ||= established_results.flat_map do |result| + result.appeal.claimant_participant_ids + end.uniq + end + + def poas + Rails.logger.info "BGS Called `fetch_poas_by_participant_ids` with \"#{claimant_participant_ids.join('"')}\"" + + @poas ||= bgs.fetch_poas_by_participant_ids(claimant_participant_ids) + end + + def bgs + @bgs ||= BGSService.new + end +end diff --git a/app/workflows/case_search_results_base.rb b/app/workflows/case_search_results_base.rb index f8e4e1a0528..56c7d870c1e 100644 --- a/app/workflows/case_search_results_base.rb +++ b/app/workflows/case_search_results_base.rb @@ -56,16 +56,52 @@ def search_call ) end + def api_call + @success = valid? + + api_search_results if success + + FormResponse.new( + success: success, + errors: errors.messages[:workflow], + extra: error_status_or_api_search_results + ) + end + protected attr_reader :status, :user - def current_user_is_vso_employee? - user.vso_employee? + def search_page_json_appeals(appeals) + ama_appeals, legacy_appeals = appeals.partition { |appeal| appeal.is_a?(Appeal) } + + ama_hash = WorkQueue::AppealSearchSerializer.new( + ama_appeals, is_collection: true, params: { user: user } + ).serializable_hash + + legacy_hash = WorkQueue::LegacyAppealSearchSerializer.new( + legacy_appeals, is_collection: true, params: { user: user } + ).serializable_hash + + ama_hash[:data].concat(legacy_hash[:data]) end - def appeals - AppealFinder.new(user: user).find_appeals_for_veterans(veterans_user_can_access) + def json_appeals(appeals) + ama_appeals, legacy_appeals = appeals.partition { |appeal| appeal.is_a?(Appeal) } + + ama_hash = WorkQueue::AppealSerializer.new( + ama_appeals, is_collection: true, params: { user: user } + ).serializable_hash + + legacy_hash = WorkQueue::LegacyAppealSerializer.new( + legacy_appeals, is_collection: true, params: { user: user } + ).serializable_hash + + ama_hash[:data].concat(legacy_hash[:data]) + end + + def current_user_is_vso_employee? + user.vso_employee? end def claim_reviews @@ -77,10 +113,14 @@ def veterans [] end + def appeals + [] + end + # Users may also view appeals with appellants whom they represent. # We use this to add these appeals back into results when the user is not on the veteran's poa. def additional_appeals_user_can_access - appeals.filter do |appeal| + appeals.map(&:appeal).filter do |appeal| appeal.veteran_is_not_claimant && user.organizations.any? do |uo| appeal.representatives.include?(uo) @@ -92,34 +132,6 @@ def veterans_user_can_access @veterans_user_can_access ||= veterans.select { |veteran| access?(veteran.file_number) } end - def json_appeals(appeals) - ama_appeals, legacy_appeals = appeals.partition { |appeal| appeal.is_a?(Appeal) } - - ama_hash = WorkQueue::AppealSerializer.new( - ama_appeals, is_collection: true, params: { user: user } - ).serializable_hash - - legacy_hash = WorkQueue::LegacyAppealSerializer.new( - legacy_appeals, is_collection: true, params: { user: user } - ).serializable_hash - - ama_hash[:data].concat(legacy_hash[:data]) - end - - def search_page_json_appeals(appeals) - ama_appeals, legacy_appeals = appeals.partition { |appeal| appeal.is_a?(Appeal) } - - ama_hash = WorkQueue::AppealSearchSerializer.new( - ama_appeals, is_collection: true, params: { user: user } - ).serializable_hash - - legacy_hash = WorkQueue::LegacyAppealSearchSerializer.new( - legacy_appeals, is_collection: true, params: { user: user } - ).serializable_hash - - ama_hash[:data].concat(legacy_hash[:data]) - end - private attr_accessor :errors @@ -142,7 +154,11 @@ def valid? def validation_hook; end def access?(file_number) - !current_user_is_vso_employee? || bgs.can_access?(file_number) + return true if !current_user_is_vso_employee? + + Rails.logger.info "BGS Called `can_access?` with \"#{file_number}\"" + + bgs.can_access?(file_number) end def bgs @@ -168,10 +184,40 @@ def error_status_or_case_search_results case_search_results end + def error_status_or_api_search_results + return { status: status } unless success + + api_search_results + end + + def error_status_or_api_case_search_results + return { status: status } unless success + + api_case_search_results + end + + def api_search_results + @api_search_results ||= { + search_results: { + appeals: json_appeals(appeal_finder_appeals), + claim_reviews: claim_reviews.map(&:search_table_ui_hash) + } + } + end + + def api_case_search_results + @api_case_search_results ||= { + case_search_results: { + appeals: search_page_json_appeals(appeal_finder_appeals), + claim_reviews: claim_reviews.map(&:search_table_ui_hash) + } + } + end + def search_results @search_results ||= { search_results: { - appeals: json_appeals(appeals), + appeals: appeals.map(&:api_response), claim_reviews: claim_reviews.map(&:search_table_ui_hash) } } @@ -180,7 +226,7 @@ def search_results def case_search_results @case_search_results ||= { case_search_results: { - appeals: search_page_json_appeals(appeals), + appeals: appeals.map(&:api_response), claim_reviews: claim_reviews.map(&:search_table_ui_hash) } } diff --git a/app/workflows/case_search_results_for_caseflow_veteran_id.rb b/app/workflows/case_search_results_for_caseflow_veteran_id.rb index 3fe2b5604df..b75e13dc0e3 100644 --- a/app/workflows/case_search_results_for_caseflow_veteran_id.rb +++ b/app/workflows/case_search_results_for_caseflow_veteran_id.rb @@ -20,6 +20,28 @@ def validation_hook validate_veterans_exist end + def appeal_finder_appeals + AppealFinder.new(user: user).find_appeals_for_veterans(veterans_user_can_access) + end + + def search_results + @search_results ||= SearchQueryService.new( + veteran_ids: veterans_user_can_access.map(&:id) + ).search_by_veteran_ids + end + + def vso_user_search_results + SearchQueryService::VsoUserSearchResults.new(user: user, search_results: search_results).call + end + + def appeals + if user.vso_employee? + vso_user_search_results + else + search_results + end + end + def not_found_error { "title": "Veteran not found", diff --git a/app/workflows/case_search_results_for_docket_number.rb b/app/workflows/case_search_results_for_docket_number.rb index 6c16a50a46d..78b9dc49656 100644 --- a/app/workflows/case_search_results_for_docket_number.rb +++ b/app/workflows/case_search_results_for_docket_number.rb @@ -13,6 +13,10 @@ def claim_reviews end def appeals + SearchQueryService.new(docket_number: docket_number).search_by_docket_number + end + + def appeal_finder_appeals AppealFinder.find_appeals_by_docket_number(docket_number) end @@ -33,7 +37,7 @@ def not_found_error def veterans # Determine vet that corresponds to docket number so we can validate user can access - @file_numbers_for_appeals ||= appeals.map(&:veteran_file_number) + @file_numbers_for_appeals ||= appeals.map(&:api_response).map(&:attributes).map(&:veteran_file_number) @veterans ||= VeteranFinder.find_or_create_all(@file_numbers_for_appeals) end end diff --git a/app/workflows/case_search_results_for_veteran_file_number.rb b/app/workflows/case_search_results_for_veteran_file_number.rb index 3f0f97fe466..1cd1bf6edeb 100644 --- a/app/workflows/case_search_results_for_veteran_file_number.rb +++ b/app/workflows/case_search_results_for_veteran_file_number.rb @@ -23,6 +23,26 @@ def validate_file_number_or_ssn_presence @status = :bad_request end + def appeals + if user.vso_employee? + vso_user_search_results + else + search_results + end + end + + def vso_user_search_results + SearchQueryService::VsoUserSearchResults.new(user: user, search_results: search_results).call + end + + def search_results + @search_results ||= SearchQueryService.new(file_number: file_number_or_ssn).search_by_veteran_file_number + end + + def appeal_finder_appeals + AppealFinder.new(user: user).find_appeals_for_veterans(veterans_user_can_access) + end + def missing_veteran_file_number_or_ssn_error { "title": "Veteran file number missing", diff --git a/spec/feature/queue/search_spec.rb b/spec/feature/queue/search_spec.rb index 7be69b901dd..02e393a7350 100644 --- a/spec/feature/queue/search_spec.rb +++ b/spec/feature/queue/search_spec.rb @@ -612,7 +612,9 @@ def perform_search(docket_number = appeal.docket_number) context "when backend returns non-serialized error" do it "displays generic server error message" do - allow(LegacyAppeal).to receive(:fetch_appeals_by_file_number).and_raise(StandardError) + allow_any_instance_of(SearchQueryService).to( + receive(:search_by_veteran_file_number).and_raise(StandardError) + ) visit "/search" fill_in "searchBarEmptyList", with: appeal.sanitized_vbms_id click_on "Search" @@ -655,7 +657,7 @@ def perform_search it "shows 'Withdrawn' text on search results page" do policy = instance_double(WithdrawnDecisionReviewPolicy) - allow(WithdrawnDecisionReviewPolicy).to receive(:new).with(caseflow_appeal).and_return policy + allow(WithdrawnDecisionReviewPolicy).to receive(:new).and_return policy allow(policy).to receive(:satisfied?).and_return true perform_search diff --git a/spec/fixes/assigned_to_search_results_spec.rb b/spec/fixes/assigned_to_search_results_spec.rb index 75765fdddd3..1ef26f380a1 100644 --- a/spec/fixes/assigned_to_search_results_spec.rb +++ b/spec/fixes/assigned_to_search_results_spec.rb @@ -29,7 +29,7 @@ end it "creates tasks and other records associated with a dispatched appeal" do - expect(BVAAppealStatus.new(appeal: appeal).status).to eq :unknown # We will fix this + expect(BVAAppealStatus.new(tasks: appeal.tasks).status).to eq :unknown # We will fix this expect(appeal.root_task.status).to eq "on_hold" visit "/search?veteran_ids=#{appeal.veteran.id}" @@ -57,7 +57,7 @@ } visit "/search?veteran_ids=#{appeal.veteran.id}" expect(page).to have_content("Signed") # in the "Appellant Name" column - expect(BVAAppealStatus.new(appeal: appeal).status).to eq :signed + expect(BVAAppealStatus.new(tasks: appeal.tasks).status).to eq :signed bva_dispatcher = org_dispatch_task.children.first.assigned_to expect(page).to have_content(bva_dispatcher.css_id) # in the "Assigned To" column expect(appeal.assigned_to_location).to eq bva_dispatcher.css_id @@ -65,7 +65,7 @@ BvaDispatchTask.outcode(appeal, params, bva_dispatcher) visit "/search?veteran_ids=#{appeal.veteran.id}" expect(page).to have_content("Dispatched") # in the "Appellant Name" column - expect(BVAAppealStatus.new(appeal: appeal).status).to eq :dispatched + expect(BVAAppealStatus.new(tasks: appeal.tasks).status).to eq :dispatched expect(page).to have_content("Post-decision") # in the "Assigned To" column expect(appeal.assigned_to_location).to eq "Post-decision" expect(appeal.root_task.status).to eq "completed" diff --git a/spec/fixes/backfill_early_ama_appeal_spec.rb b/spec/fixes/backfill_early_ama_appeal_spec.rb index e1ed3774706..e26da3bfe4d 100644 --- a/spec/fixes/backfill_early_ama_appeal_spec.rb +++ b/spec/fixes/backfill_early_ama_appeal_spec.rb @@ -28,7 +28,7 @@ let(:atty_draft_date) { dispatch_date - 2.hours } it "creates tasks and other records associated with a dispatched appeal" do - expect(BVAAppealStatus.new(appeal: appeal).status).to eq :unknown # We will fix this + expect(BVAAppealStatus.new(tasks: appeal.tasks).status).to eq :unknown # We will fix this expect(appeal.root_task.status).to eq "completed" # 1. Create tasks to associate appeal with a judge and attorney @@ -134,7 +134,7 @@ decision_doc = DecisionDocument.create!(params) expect(appeal.decision_document).to eq decision_doc - expect(BVAAppealStatus.new(appeal: appeal).status).to eq :dispatched + expect(BVAAppealStatus.new(tasks: appeal.tasks).status).to eq :dispatched end end end diff --git a/spec/services/bva_appeal_status_spec.rb b/spec/services/bva_appeal_status_spec.rb index 5bdb70c2929..8effec2eb4a 100644 --- a/spec/services/bva_appeal_status_spec.rb +++ b/spec/services/bva_appeal_status_spec.rb @@ -18,7 +18,7 @@ status = pair.first sort_key = pair.last appeal = Appeal.find(appeal_id) - appeal_status = described_class.new(appeal: appeal) + appeal_status = described_class.new(tasks: appeal.tasks) expect(appeal_status.to_s).to eq(status) expect(appeal_status.to_i).to eq(sort_key.to_i + 1) # our sort keys are 1-based diff --git a/spec/services/search_query_service_spec.rb b/spec/services/search_query_service_spec.rb new file mode 100644 index 00000000000..b9d87a7a29c --- /dev/null +++ b/spec/services/search_query_service_spec.rb @@ -0,0 +1,318 @@ +# frozen_string_literal: true + +describe "SearchQueryService" do + let(:ssn) { "146600001" } + let(:dob) { Faker::Date.in_date_period(year: 1960) } + + let(:uuid) { SecureRandom.uuid } + let(:veteran_first_name) { Faker::Name.first_name } + let(:veteran_last_name) { Faker::Name.last_name } + let(:claimant_first_name) { Faker::Name.first_name } + let(:claimant_last_name) { Faker::Name.last_name } + let(:veteran_full_name) { FullName.new(veteran_first_name, "", veteran_last_name).to_s } + let(:claimant_full_name) { FullName.new(claimant_first_name, "", claimant_last_name).to_s } + let(:docket_type) { "hearing" } + let(:docket_number) { "240111-1111" } + + let(:descision_document_attrs) do + { + decision_date: Faker::Date.between(from: 2.years.ago, to: 1.year.ago) + } + end + + context "all data in caseflow" do + context "veteran is claimant" do + let(:veteran_attrs) do + { + ssn: ssn, + file_number: ssn, + date_of_birth: dob, + date_of_death: nil, + first_name: veteran_first_name, + middle_name: nil, + last_name: veteran_last_name + } + end + + let(:veteran) { FactoryBot.create(:veteran, veteran_attrs) } + + let(:appeal_attributes) do + { + aod_based_on_age: false, + stream_docket_number: docket_number, + veteran_file_number: ssn, + veteran: veteran, + stream_type: Constants.AMA_STREAM_TYPES.original, + uuid: uuid + } + end + + let(:judge) { create(:user, :judge) } + + let!(:appeal) do + FactoryBot.create( + :appeal, + # has hearing(s) + :hearing_docket, + :held_hearing, + :tied_to_judge, + # has decision document + :dispatched, + # has issue(s) + :with_request_issues, + :with_decision_issue, + { + associated_judge: judge, + tied_judge: judge + }.merge(appeal_attributes) + ).tap do |appeal| + appeal.decision_issues.first.update( + mst_status: true, + pact_status: true + ) + create( + :virtual_hearing, + hearing: appeal.hearings.first + ) + appeal.hearings.first.update(updated_by: judge) + appeal.hearings.first.hearing_day.update(regional_office: "RO19") + appeal.hearings.first.hearing_views.create(user_id: judge.id) + # create work mode + appeal.overtime = true + AdvanceOnDocketMotion.create( + person: appeal.claimants.first.person, + appeal: appeal + ) + end.reload + end + + context "finds by docket number" do + subject { SearchQueryService.new(docket_number: appeal.stream_docket_number) } + + it "finds by docket number" do + expect(appeal).to be_persisted + + search_results = subject.search_by_docket_number + + expect(search_results.length).to eq(1) + + result = search_results.first.api_response + + expect(result.id).to be + expect(result.type).to eq "appeal" + + attributes = result.attributes + + expect(attributes.aod).to be_truthy + expect(attributes.appellant_full_name).to eq veteran_full_name + expect(attributes.assigned_to_location).to eq appeal.assigned_to_location + expect(attributes.caseflow_veteran_id).to eq veteran.id + expect(attributes.decision_date).to eq appeal.decision_document.decision_date + expect(attributes.docket_name).to eq appeal.docket_type + expect(attributes.docket_number).to eq appeal.stream_docket_number + expect(attributes.external_id).to eq appeal.uuid + expect(attributes.hearings.length).to eq appeal.hearings.length + expect(attributes.hearings.first[:held_by]).to eq judge.full_name + expect(attributes.issues.length).to eq(appeal.request_issues.length) + expect(attributes.mst).to eq appeal.decision_issues.any?(&:mst_status) + expect(attributes.pact).to eq appeal.decision_issues.any?(&:pact_status) + expect(attributes.paper_case).to be_falsy + expect(attributes.status).to eq Appeal.find(appeal.id).status.status + expect(attributes.veteran_appellant_deceased).to be_falsy + expect(attributes.veteran_file_number).to eq ssn + expect(attributes.veteran_full_name).to eq veteran_full_name + expect(attributes.contested_claim).to be_falsy + expect(attributes.withdrawn).to eq(false) + end + end + + context "finds by file number" do + subject { SearchQueryService.new(file_number: ssn) } + + it "finds by veteran file number" do + expect(appeal).to be_persisted + + search_results = subject.search_by_veteran_file_number + + expect(search_results.length).to eq(1) + + result = search_results.first.api_response + + expect(result.id).to be + expect(result.type).to eq "appeal" + end + end + + context "finds by veteran ids" do + subject { SearchQueryService.new(veteran_ids: [veteran.id]) } + + it "finds by veteran ids" do + expect(appeal).to be_persisted + + search_results = subject.search_by_veteran_ids + + expect(search_results.length).to eq(1) + + result = search_results.first.api_response + + expect(result.id).to be + expect(result.type).to eq "appeal" + end + end + end + end + + let(:veteran_address) do + { + addrs_one_txt: nil, + addrs_two_txt: nil, + addrs_three_txt: nil, + city_nm: nil, + cntry_nm: nil, + postal_cd: nil, + zip_prefix_nbr: nil, + ptcpnt_addrs_type_nm: nil + } + end + + let(:legacy_appeal) do + create( + :legacy_appeal, + vbms_id: ssn, + vacols_case: vacols_case, + veteran_address: veteran_address + ) + end + + # must be created first for legacy_appeal factory to find it + let!(:veteran) do + create( + :veteran, + file_number: ssn, + first_name: veteran_first_name, + last_name: veteran_last_name + ) + end + + let(:vacols_decision_date) { 2.weeks.ago } + let(:vacols_case_attrs) do + { + bfkey: ssn, + bfcorkey: ssn, + bfac: "1", + bfcorlid: "100000099", + bfcurloc: "CASEFLOW", + bfddec: vacols_decision_date, + bfmpro: "ACT" + + # bfregoff: "RO18", + # bfdloout: "2024-03-26T11:13:32.000Z", + # bfcallup: "", + # bfhr: "2", + # bfdocind: "T", + } + end + + let(:issues_count) { 5 } + let(:vacols_case_issues) do + create_list( + :case_issue, + issues_count, + isspact: "Y", + issmst: "Y" + ) + end + + let(:hearings_count) { 5 } + let(:vacols_case_hearings) do + create_list( + :case_hearing, + hearings_count + ) + end + + let(:vacols_correspondent) do + create(:correspondent, vacols_correspondent_attrs) + end + + let(:vacols_folder) do + build(:folder) + end + + let(:vacols_case) do + create( + :case, + { + correspondent: vacols_correspondent, + case_issues: vacols_case_issues, + case_hearings: vacols_case_hearings, + folder: vacols_folder + }.merge(vacols_case_attrs) + ) + end + + context "when appeal is a legacy appeal with data in vacols and caseflow" do + context "when veteran is claimant" do + let(:vacols_correspondent_attrs) do + { + sspare2: veteran_first_name, + sspare1: veteran_last_name, + snamel: veteran_last_name, + snamef: veteran_first_name, + stafkey: ssn + } + end + + let!(:claimant) do + create( + :claimant, + type: "VeteranClaimant", + decision_review: legacy_appeal + ) + end + + subject { SearchQueryService.new(file_number: ssn) } + + it "finds by file number" do + search_results = subject.search_by_veteran_file_number + result = search_results.first.api_response + + expect(result.id).to be + expect(result.type).to eq "legacy_appeal" + + attributes = result.attributes + expect(attributes.docket_name).to eq "legacy" + expect(attributes.aod).to be_falsy + expect(attributes.appellant_full_name).to eq veteran_full_name + expect(attributes.assigned_to_location).to eq legacy_appeal.assigned_to_location + expect(attributes.caseflow_veteran_id).to eq veteran.id + expect(attributes.decision_date).to eq AppealRepository.normalize_vacols_date(vacols_decision_date) + expect(attributes.docket_name).to eq "legacy" + expect(attributes.docket_number).to eq vacols_folder.tinum + expect(attributes.external_id).to eq vacols_case.id + expect(attributes.hearings.length).to eq hearings_count + expect(attributes.issues.length).to eq issues_count + expect(attributes.mst).to be_truthy + expect(attributes.pact).to be_truthy + expect(attributes.paper_case).to eq "Paper" + expect(attributes.status).to eq "Active" + expect(attributes.veteran_appellant_deceased).to be_falsy + expect(attributes.veteran_file_number).to eq ssn + expect(attributes.veteran_full_name).to eq veteran_full_name + expect(attributes.withdrawn).to be_falsy + end + + context "finds by veteran ids" do + subject { SearchQueryService.new(veteran_ids: [veteran.id]) } + + it "finds by veteran ids" do + search_results = subject.search_by_veteran_ids + result = search_results.first.api_response + + expect(result.id).to be + expect(result.type).to eq "legacy_appeal" + end + end + end + end +end From c2312661aca1b1a493a7f23b5874a75a95ff3a81 Mon Sep 17 00:00:00 2001 From: Ron Wabukenda <130374706+ronwabVa@users.noreply.github.com> Date: Fri, 27 Sep 2024 15:59:24 -0400 Subject: [PATCH 17/17] Revert "hotfix/APPEALS-23420: search defect" (#23011) This reverts commit 25ed7f1037172a2e7ac4385e32372e8d25251297. --- .../idt/api/v2/appeals_controller.rb | 4 +- app/decorators/appeal_status_api_decorator.rb | 12 +- app/models/appeal.rb | 16 +- app/models/hearing.rb | 8 +- app/models/task.rb | 6 +- .../tasks/evidence_submission_window_task.rb | 2 +- app/services/bva_appeal_status.rb | 75 ++-- app/services/search_query_service.rb | 113 ----- .../search_query_service/api_response.rb | 3 - .../search_query_service/appeal_row.rb | 177 -------- .../search_query_service/attributes.rb | 30 -- .../search_query_service/legacy_appeal_row.rb | 164 ------- .../search_query_service/legacy_attributes.rb | 27 -- .../search_query_service/queried_appeal.rb | 133 ------ .../search_query_service/queried_hearing.rb | 90 ---- .../queried_legacy_appeal.rb | 41 -- app/services/search_query_service/query.rb | 416 ------------------ .../search_query_service/search_response.rb | 9 - .../search_query_service/vacols_query.rb | 112 ----- .../vso_user_search_results.rb | 68 --- app/workflows/case_search_results_base.rb | 118 ++--- ..._search_results_for_caseflow_veteran_id.rb | 22 - .../case_search_results_for_docket_number.rb | 6 +- ..._search_results_for_veteran_file_number.rb | 20 - spec/feature/queue/search_spec.rb | 6 +- spec/fixes/assigned_to_search_results_spec.rb | 6 +- spec/fixes/backfill_early_ama_appeal_spec.rb | 4 +- spec/services/bva_appeal_status_spec.rb | 2 +- spec/services/search_query_service_spec.rb | 318 ------------- 29 files changed, 103 insertions(+), 1905 deletions(-) delete mode 100644 app/services/search_query_service.rb delete mode 100644 app/services/search_query_service/api_response.rb delete mode 100644 app/services/search_query_service/appeal_row.rb delete mode 100644 app/services/search_query_service/attributes.rb delete mode 100644 app/services/search_query_service/legacy_appeal_row.rb delete mode 100644 app/services/search_query_service/legacy_attributes.rb delete mode 100644 app/services/search_query_service/queried_appeal.rb delete mode 100644 app/services/search_query_service/queried_hearing.rb delete mode 100644 app/services/search_query_service/queried_legacy_appeal.rb delete mode 100644 app/services/search_query_service/query.rb delete mode 100644 app/services/search_query_service/search_response.rb delete mode 100644 app/services/search_query_service/vacols_query.rb delete mode 100644 app/services/search_query_service/vso_user_search_results.rb delete mode 100644 spec/services/search_query_service_spec.rb diff --git a/app/controllers/idt/api/v2/appeals_controller.rb b/app/controllers/idt/api/v2/appeals_controller.rb index b93cf8179dc..74884231964 100644 --- a/app/controllers/idt/api/v2/appeals_controller.rb +++ b/app/controllers/idt/api/v2/appeals_controller.rb @@ -14,11 +14,11 @@ def details result = if docket_number?(case_search) CaseSearchResultsForDocketNumber.new( docket_number: case_search, user: current_user - ).api_call + ).call else CaseSearchResultsForVeteranFileNumber.new( file_number_or_ssn: case_search, user: current_user - ).api_call + ).call end render_search_results_as_json(result) diff --git a/app/decorators/appeal_status_api_decorator.rb b/app/decorators/appeal_status_api_decorator.rb index 7be8e8e75aa..a93ed1b9bbf 100644 --- a/app/decorators/appeal_status_api_decorator.rb +++ b/app/decorators/appeal_status_api_decorator.rb @@ -3,12 +3,6 @@ # Extends the Appeal model with methods for the Appeals Status API class AppealStatusApiDecorator < ApplicationDecorator - def initialize(appeal, scheduled_hearing = nil) - super(appeal) - - @scheduled_hearing = scheduled_hearing - end - def appeal_status_id "A#{id}" end @@ -168,11 +162,11 @@ def remanded_sc_decision_issues end def open_pre_docket_task? - open_tasks.any? { |task| task.is_a?(PreDocketTask) } + tasks.open.any? { |task| task.is_a?(PreDocketTask) } end def pending_schedule_hearing_task? - pending_schedule_hearing_tasks.any? + tasks.open.where(type: ScheduleHearingTask.name).any? end def hearing_pending? @@ -180,7 +174,7 @@ def hearing_pending? end def evidence_submission_hold_pending? - evidence_submission_hold_pending_tasks.any? + tasks.open.where(type: EvidenceSubmissionWindowTask.name).any? end def at_vso? diff --git a/app/models/appeal.rb b/app/models/appeal.rb index 59367fa3a36..4691f672563 100644 --- a/app/models/appeal.rb +++ b/app/models/appeal.rb @@ -308,18 +308,6 @@ def decorated_with_status AppealStatusApiDecorator.new(self) end - def open_tasks - tasks.open - end - - def pending_schedule_hearing_tasks - tasks.open.where(type: ScheduleHearingTask.name) - end - - def evidence_submission_hold_pending_tasks - tasks.open.where(type: EvidenceSubmissionWindowTask.name) - end - # :reek:RepeatedConditionals def active_request_issues_or_decision_issues decision_issues.empty? ? active_request_issues : fetch_all_decision_issues @@ -645,7 +633,7 @@ def direct_review_docket? end def active? - open_tasks.of_type(:RootTask).any? + tasks.open.of_type(:RootTask).any? end def ready_for_distribution? @@ -760,7 +748,7 @@ def substitutions end def status - @status ||= BVAAppealStatus.new(tasks: tasks) + @status ||= BVAAppealStatus.new(appeal: self) end def previously_selected_for_quality_review diff --git a/app/models/hearing.rb b/app/models/hearing.rb index 7ad17f1b3e1..6008a49d157 100644 --- a/app/models/hearing.rb +++ b/app/models/hearing.rb @@ -187,15 +187,12 @@ def advance_on_docket_motion .first end - def scheduled_for - scheduled_for_hearing_day(hearing_day, updated_by, regional_office_timezone) - end - # returns scheduled datetime object considering the timezones # @return [nil] if hearing_day is nil # @return [Time] in scheduled_in_timezone timezone - if scheduled_datetime and scheduled_in_timezone are present # @return [Time] else datetime in regional office timezone - def scheduled_for_hearing_day(hearing_day, updated_by, regional_office_timezone) + # rubocop:disable Metrics/AbcSize + def scheduled_for return nil unless hearing_day # returns datetime in scheduled_in_timezone timezone @@ -237,6 +234,7 @@ def scheduled_for_hearing_day(hearing_day, updated_by, regional_office_timezone) ) end end + # rubocop:enable Metrics/AbcSize def scheduled_for_past? scheduled_for < DateTime.yesterday.in_time_zone(regional_office_timezone) diff --git a/app/models/task.rb b/app/models/task.rb index 4c0b7066290..b2244e0f1b0 100644 --- a/app/models/task.rb +++ b/app/models/task.rb @@ -101,7 +101,7 @@ class << self; undef_method :open; end # Equivalent to .reject(&:hide_from_queue_table_view) but offloads that to the database. scope :visible_in_queue_table_view, lambda { where.not( - type: hidden_task_classes + type: Task.descendants.select(&:hide_from_queue_table_view).map(&:name) ) } @@ -138,10 +138,6 @@ class << self # With taks that are likely to need Reader to complete READER_PRIORITY_TASK_TYPES = [JudgeAssignTask.name, JudgeDecisionReviewTask.name].freeze - def hidden_task_classes - Task.descendants.select(&:hide_from_queue_table_view).map(&:name) - end - def reader_priority_task_types READER_PRIORITY_TASK_TYPES end diff --git a/app/models/tasks/evidence_submission_window_task.rb b/app/models/tasks/evidence_submission_window_task.rb index 0028fc54366..7c9cdb5f8fa 100644 --- a/app/models/tasks/evidence_submission_window_task.rb +++ b/app/models/tasks/evidence_submission_window_task.rb @@ -11,7 +11,7 @@ class EvidenceSubmissionWindowTask < Task before_validation :set_assignee - def initialize(args = {}) + def initialize(args) @end_date = args&.fetch(:end_date, nil) super(args&.except(:end_date)) end diff --git a/app/services/bva_appeal_status.rb b/app/services/bva_appeal_status.rb index c25886c15a3..1577a7ea7c6 100644 --- a/app/services/bva_appeal_status.rb +++ b/app/services/bva_appeal_status.rb @@ -3,7 +3,7 @@ # Determine the BVA workflow status of an Appeal (symbol and string) based on its Tasks. class BVAAppealStatus - attr_reader :status, :tasks + attr_reader :status SORT_KEYS = { not_distributed: 1, @@ -69,18 +69,8 @@ def attorney_task_names end end - Tasks = Struct.new( - :open, - :active, - :in_progress, - :cancelled, - :completed, - :assigned, - keyword_init: true - ) - - def initialize(tasks:) - @tasks = tasks + def initialize(appeal:) + @appeal = appeal @status = compute end @@ -96,12 +86,15 @@ def to_i SORT_KEYS[status] end - def as_json(_args = nil) + def as_json(_args) to_sym end private + attr_reader :appeal + + delegate :tasks, to: :appeal # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength def compute if open_pre_docket_task? @@ -120,7 +113,7 @@ def compute :ready_for_signature elsif active_sign_task? :signed - elsif completed_dispatch_task? && tasks.open.empty? + elsif completed_dispatch_task? && open_tasks.empty? :dispatched elsif completed_dispatch_task? :post_dispatch @@ -140,60 +133,84 @@ def compute end # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength + def open_tasks + @open_tasks ||= tasks.open + end + + def active_tasks + @active_tasks ||= tasks.active + end + + def assigned_tasks + @assigned_tasks ||= tasks.assigned + end + + def in_progress_tasks + @in_progress_tasks ||= tasks.in_progress + end + + def cancelled_tasks + @cancelled_tasks ||= tasks.cancelled + end + + def completed_tasks + @completed_tasks ||= tasks.completed + end + def open_pre_docket_task? - tasks.open.any? { |task| task.type == "PreDocketTask" } + open_tasks.any? { |task| task.is_a?(PreDocketTask) } end def open_distribution_task? - tasks.open.any? { |task| task.type == "DistributionTask" } + open_tasks.any? { |task| task.is_a?(DistributionTask) } end def open_timed_hold_task? - tasks.open.any? { |task| task.type == "TimedHoldTask" } + open_tasks.any? { |task| task.is_a?(TimedHoldTask) } end def active_judge_assign_task? - tasks.active.any? { |task| task.type == "JudgeAssignTask" } + active_tasks.any? { |task| task.is_a?(JudgeAssignTask) } end def assigned_attorney_task? - tasks.assigned.any? { |task| self.class.attorney_task_names.include?(task.type) } + assigned_tasks.any? { |task| self.class.attorney_task_names.include?(task.type) } end def active_colocated_task? - tasks.active.any? { |task| self.class.colocated_task_names.include?(task.type) } + active_tasks.any? { |task| self.class.colocated_task_names.include?(task.type) } end def attorney_task_in_progress? - tasks.in_progress.any? { |task| self.class.attorney_task_names.include?(task.type) } + in_progress_tasks.any? { |task| self.class.attorney_task_names.include?(task.type) } end def active_judge_decision_review_task? - tasks.active.any? { |task| task.type == "JudgeDecisionReviewTask" } + active_tasks.any? { |task| task.is_a?(JudgeDecisionReviewTask) } end def active_sign_task? - tasks.active.any? { |task| %w[BvaDispatchTask QualityReviewTask].include?(task.type) } + active_tasks.any? { |task| %w[BvaDispatchTask QualityReviewTask].include?(task.type) } end def completed_dispatch_task? - tasks.completed.any? { |task| task.type == "BvaDispatchTask" } + completed_tasks.any? { |task| task.is_a?(BvaDispatchTask) } end def docket_switched? # TODO: this should be updated to check that there are no active tasks once the task handling is implemented - tasks.completed.any? { |task| task.type == "DocketSwitchGrantedTask" } + completed_tasks.any? { |task| task.is_a?(DocketSwitchGrantedTask) } end def cancelled_root_task? - tasks.cancelled.any? { |task| task.type == "RootTask" } + cancelled_tasks.any? { |task| task.is_a?(RootTask) } end def misc_task? - tasks.active.any? { |task| self.class.misc_task_names.include?(task.type) } + active_tasks.any? { |task| self.class.misc_task_names.include?(task.type) } end def active_specialty_case_team_assign_task? - tasks.active.any? { |task| task.type == "SpecialtyCaseTeamAssignTask" } + active_tasks.any? { |task| task.is_a?(SpecialtyCaseTeamAssignTask) } end end diff --git a/app/services/search_query_service.rb b/app/services/search_query_service.rb deleted file mode 100644 index f3434d4c9a6..00000000000 --- a/app/services/search_query_service.rb +++ /dev/null @@ -1,113 +0,0 @@ -# frozen_string_literal: true - -class SearchQueryService - def initialize(file_number: nil, docket_number: nil, veteran_ids: nil) - @docket_number = docket_number - @file_number = file_number - @veteran_ids = veteran_ids - @queries = SearchQueryService::Query.new - end - - def search_by_veteran_file_number - combined_results - end - - def search_by_docket_number - results = ActiveRecord::Base.connection.exec_query( - sanitize( - queries.docket_number_query(docket_number) - ) - ) - - results.map do |row| - AppealRow.new(row).search_response - end - end - - def search_by_veteran_ids - combined_results - end - - private - - attr_reader :docket_number, :file_number, :queries, :veteran_ids - - def combined_results - search_results.map do |row| - if row["type"] != "legacy_appeal" - AppealRow.new(row).search_response - else - vacols_row = vacols_results.find { |result| result["vacols_id"] == row["external_id"] } - - Rails.logger.warn(no_vacols_record_warning(result)) if vacols_row.blank? - LegacyAppealRow.new(row, vacols_row || null_vacols_row).search_response - end - end - end - - def null_vacols_row - {} - end - - def no_vacols_record_warning(result) - <<-WARN - No corresponding VACOLS record found for appeal with: - id: #{result['id']} - vacols_id: #{result['vacols_id']} - searching with: - #{file_number.present? "file_number #{file_number}"} \ - #{veteran_ids.present? "veteran_ids #{veteran_ids.join(',')}"} \ - #{file_number.present? "docket_number #{docket_number}"} - WARN - end - - def vacols_ids - legacy_results.map { |result| result["external_id"] } - end - - def legacy_results - search_results.select { |result| result["type"] == "legacy_appeal" } - end - - def search_results - @search_results ||= - if file_number.present? - file_number_search_results - else - veteran_ids_search_results - end - end - - def veteran_ids_search_results - ActiveRecord::Base - .connection - .exec_query( - sanitize(queries.veteran_ids_query(veteran_ids)) - ) - .uniq { |result| result["external_id"] } - end - - def file_number_search_results - ActiveRecord::Base - .connection - .exec_query(file_number_or_ssn_query) - .uniq { |result| result["external_id"] } - end - - def file_number_or_ssn_query - sanitize( - queries.veteran_file_number_query(file_number) - ) - end - - def vacols_results - @vacols_results ||= begin - vacols_query = VACOLS::Record.sanitize_sql_array(queries.vacols_query(vacols_ids)) - VACOLS::Record.connection.exec_query(vacols_query) - end - end - - def sanitize(values) - ActiveRecord::Base.sanitize_sql_array(values) - end -end diff --git a/app/services/search_query_service/api_response.rb b/app/services/search_query_service/api_response.rb deleted file mode 100644 index 76a216b678e..00000000000 --- a/app/services/search_query_service/api_response.rb +++ /dev/null @@ -1,3 +0,0 @@ -# frozen_string_literal: true - -SearchQueryService::ApiResponse = Struct.new(:id, :type, :attributes, keyword_init: true) diff --git a/app/services/search_query_service/appeal_row.rb b/app/services/search_query_service/appeal_row.rb deleted file mode 100644 index fd6046b1216..00000000000 --- a/app/services/search_query_service/appeal_row.rb +++ /dev/null @@ -1,177 +0,0 @@ -# frozen_string_literal: true - -class SearchQueryService::AppealRow - def initialize(query_row) - @query_row = query_row - end - - def search_response - SearchQueryService::SearchResponse.new( - queried_appeal, - :appeal, - SearchQueryService::ApiResponse.new( - id: query_row["id"], - type: "appeal", - attributes: attributes - ) - ) - end - - private - - attr_reader :query_row - - # rubocop:disable Metrics/MethodLength - def attributes - SearchQueryService::Attributes.new( - aod: aod, - appellant_full_name: appellant_full_name, - assigned_to_location: queried_appeal.assigned_to_location, - assigned_attorney: assigned_attorney, - assigned_judge: assigned_judge, - caseflow_veteran_id: query_row["veteran_id"], - contested_claim: contested_claim, - decision_date: decision_date, - decision_issues: decision_issues, - docket_name: query_row["docket_type"], - docket_number: docket_number, - external_id: query_row["external_id"], - hearings: hearings, - issues: issues, - mst: mst_status, - overtime: query_row["overtime"], - pact: pact_status, - paper_case: false, - status: queried_appeal.status, - type: stream_type, - veteran_appellant_deceased: veteran_appellant_deceased, - veteran_file_number: veteran_file_number, - veteran_full_name: veteran_full_name, - withdrawn: withdrawn - ) - end - # rubocop:enable Metrics/MethodLength - - def aod - query_row["aod_granted_for_person"].present? - end - - def decision_issues - json_array("decision_issues") - end - - def docket_number - attrs, = JSON.parse query_row["appeal"] - attrs["stream_docket_number"] - end - - def decision_date - Date.parse(query_row["decision_date"]) - rescue TypeError, Date::Error - nil - end - - def appellant_full_name - FullName.new(query_row["person_first_name"], "", query_row["person_last_name"]).to_s - end - - def veteran_full_name - FullName.new(query_row["veteran_first_name"], "", query_row["veteran_last_name"]).to_s - end - - def veteran_file_number - attrs, = JSON.parse(query_row["appeal"]) - attrs["veteran_file_number"] - end - - def issue(attributes) - unless FeatureToggle.enabled?(:pact_identification) - attributes.delete("pact_status") - end - unless FeatureToggle.enabled?(:mst_identification) - attributes.delete("mst_status") - end - end - - def issues - json_array("request_issues").map do |attributes| - attributes.tap do |attrs| - issue(attrs) - end - end - end - - def hearings - json_array("hearings").map do |attrs| - AppealHearingSerializer.new( - SearchQueryService::QueriedHearing.new(attrs), - { user: RequestStore[:current_user] } - ).serializable_hash[:data][:attributes] - end - end - - def withdrawn - WithdrawnDecisionReviewPolicy.new( - Struct.new( - :active_request_issues, - :withdrawn_request_issues - ).new( - json_array("active_request_issues"), - json_array("active_request_issues") - ) - ).satisfied? - end - - def stream_type - (query_row["stream_type"] || "Original").titleize - end - - def contested_claim - json_array("active_request_issues").any? do |issue| - %w(Contested Apportionment).any? do |code| - category = issue["nonrating_issue_category"] || "" - category.include?(code) - end - end - end - - def veteran_appellant_deceased - !!query_row["date_of_death"] && !json_array("appeal").first["veteran_is_not_claimant"] - end - - def pact_status - json_array("decision_issues").any? do |issue| - issue["pact_status"] - end - end - - def mst_status - json_array("decision_issues").any? do |issue| - issue["mst_status"] - end - end - - def queried_appeal - @queried_appeal ||= begin - appeal_attrs, = JSON.parse query_row["appeal"] - - SearchQueryService::QueriedAppeal.new( - attributes: appeal_attrs, - tasks_attributes: json_array("tasks"), - hearings_attributes: json_array("hearings") - ) - end - end - - def assigned_attorney - json_array("assigned_attorney").first - end - - def assigned_judge - json_array("assigned_judge").first - end - - def json_array(key) - JSON.parse(query_row[key] || "[]") - end -end diff --git a/app/services/search_query_service/attributes.rb b/app/services/search_query_service/attributes.rb deleted file mode 100644 index 6c4487a9b1e..00000000000 --- a/app/services/search_query_service/attributes.rb +++ /dev/null @@ -1,30 +0,0 @@ -# frozen_string_literal: true - -SearchQueryService::Attributes = Struct.new( - :aod, - :power_of_attorney, - :appellant_full_name, - :assigned_attorney, - :assigned_judge, - :assigned_to_location, - :caseflow_veteran_id, - :decision_date, - :decision_issues, - :docket_name, - :docket_number, - :external_id, - :hearings, - :issues, - :mst, - :overtime, - :pact, - :paper_case, - :status, - :type, - :veteran_appellant_deceased, - :veteran_file_number, - :veteran_full_name, - :contested_claim, - :withdrawn, - keyword_init: true -) diff --git a/app/services/search_query_service/legacy_appeal_row.rb b/app/services/search_query_service/legacy_appeal_row.rb deleted file mode 100644 index b9dfe0af476..00000000000 --- a/app/services/search_query_service/legacy_appeal_row.rb +++ /dev/null @@ -1,164 +0,0 @@ -# frozen_string_literal: true - -class SearchQueryService::LegacyAppealRow - def initialize(search_row, vacols_row) - @search_row = search_row - @vacols_row = vacols_row - end - - def search_response - SearchQueryService::SearchResponse.new( - legacy_appeal, - :legacy_appeal, - SearchQueryService::ApiResponse.new( - id: search_row["id"], - type: "legacy_appeal", - attributes: attributes - ) - ) - end - - private - - attr_reader :search_row, :vacols_row - - # rubocop:disable Metrics/MethodLength - def attributes - SearchQueryService::LegacyAttributes.new( - aod: aod, - appellant_full_name: appellant_full_name, - assigned_attorney: assigned_attorney, - assigned_judge: assigned_judge, - assigned_to_location: vacols_row["bfcurloc"], - caseflow_veteran_id: search_row["veteran_id"], - decision_date: decision_date, - docket_name: "legacy", - docket_number: vacols_row["tinum"], - external_id: vacols_row["vacols_id"], - hearings: hearings, - issues: issues, - mst: mst, - overtime: search_row["overtime"], - pact: pact, - paper_case: paper_case, - status: status, - type: stream_type, - veteran_appellant_deceased: veteran_appellant_deceased, - veteran_file_number: search_row["veteran_file_number"], - veteran_full_name: veteran_full_name - ) - end - # rubocop:enable Metrics/MethodLength - - def hearings - vacols_json_array("hearings").map do |attrs| - HearingAttributes.new(attrs).call - end - end - - class HearingAttributes - def initialize(attributes) - @attributes = attributes - end - - def call - { - disposition: VACOLS::CaseHearing::HEARING_DISPOSITIONS[attributes["disposition"].try(:to_sym)], - request_type: attributes["type"], - appeal_type: VACOLS::Case::TYPES[attributes["bfac"]], - external_id: attributes["external_id"], - date: HearingMapper.datetime_based_on_type( - datetime: attributes["date"], - regional_office: regional_office(attributes["venue"]), - type: attributes["type"] - ) - } - end - - private - - attr_reader :attributes - - def regional_office(ro_key) - RegionalOffice.find!(ro_key) - rescue NotFoundError - nil - end - end - - def issues - vacols_json_array("issues").map do |attrs| - WorkQueue::LegacyIssueSerializer.new( - Issue.load_from_vacols(attrs) - ).serializable_hash[:data][:attributes] - end - end - - def assigned_attorney - json_array("assigned_attorney").first - end - - def assigned_judge - json_array("assigned_judge").first - end - - def json_array(key) - JSON.parse(search_row[key] || "[]") - end - - def vacols_json_array(key) - JSON.parse(vacols_row[key] || "[]") - end - - def veteran_appellant_deceased - search_row["date_of_death"].present? && - search_row["person_first_name"].present? - end - - def stream_type - VACOLS::Case::TYPES[vacols_row["bfac"]] - end - - def status - VACOLS::Case::STATUS[vacols_row["bfmpro"]] - end - - def paper_case - folder = Struct.new(:tivbms, :tisubj2).new( - vacols_row["tivbms"], - vacols_row["tisubj2"] - ) - AppealRepository.folder_type_from(folder) - end - - def mst - vacols_row["issues_mst_count"].to_i > 0 - end - - def pact - vacols_row["issues_pact_count"].to_i > 0 - end - - def appellant_full_name - FullName.new(vacols_row["sspare2"], "", vacols_row["sspare1"]).to_s - end - - def veteran_full_name - FullName.new(vacols_row["snamef"], "", vacols_row["snamel"]).to_s - end - - def aod - vacols_row["aod"].to_i == 1 - end - - def decision_date - AppealRepository.normalize_vacols_date(vacols_row["bfddec"]) - end - - def legacy_appeal - @legacy_appeal ||= begin - appeal_attrs, = JSON.parse search_row["appeal"] - SearchQueryService::QueriedLegacyAppeal.new(attributes: appeal_attrs) - end - end -end diff --git a/app/services/search_query_service/legacy_attributes.rb b/app/services/search_query_service/legacy_attributes.rb deleted file mode 100644 index 5ac4e1439bd..00000000000 --- a/app/services/search_query_service/legacy_attributes.rb +++ /dev/null @@ -1,27 +0,0 @@ -# frozen_string_literal: true - -SearchQueryService::LegacyAttributes = Struct.new( - :aod, - :appellant_full_name, - :assigned_attorney, - :assigned_judge, - :assigned_to_location, - :caseflow_veteran_id, - :decision_date, - :docket_name, - :docket_number, - :external_id, - :hearings, - :issues, - :mst, - :overtime, - :pact, - :paper_case, - :status, - :type, - :veteran_appellant_deceased, - :veteran_file_number, - :veteran_full_name, - :withdrawn, - keyword_init: true -) diff --git a/app/services/search_query_service/queried_appeal.rb b/app/services/search_query_service/queried_appeal.rb deleted file mode 100644 index 297ad46cdcf..00000000000 --- a/app/services/search_query_service/queried_appeal.rb +++ /dev/null @@ -1,133 +0,0 @@ -# frozen_string_literal: true - -class SearchQueryService::QueriedAppeal < SimpleDelegator - def initialize(attributes:, tasks_attributes:, hearings_attributes:) - @attributes = OpenStruct.new( - appeal: attributes, - tasks: tasks_attributes, - hearings: hearings_attributes, - root_task: attributes.delete("root_task") || {}, - claimants: attributes.delete("claimants") || [] - ) - - super(appeal) - end - - def assigned_to_location - return COPY::CASE_LIST_TABLE_POST_DECISION_LABEL if root_task&.status == Constants.TASK_STATUSES.completed - - return most_recently_updated_visible_task.assigned_to_label if most_recently_updated_visible_task - - # this condition is no longer needed since we only want active or on hold tasks - return most_recently_updated_task&.assigned_to_label if most_recently_updated_task.present? - - fetch_api_status - end - - def claimant_participant_ids - claimants.map(&:participant_id) - end - - def claimants - @claimants ||= begin - attributes.claimants.map do |attrs| - Struct.new(:participant_id).new(attrs["participant_id"]) - end - end - end - - def root_task - @root_task ||= begin - if attributes.root_task.present? - Task.new.tap do |task| - task.assign_attributes attributes.root_task - end - end - end - end - - def open_tasks - @open_tasks ||= tasks.select do |task| - Task.open_statuses.include?(task.status) - end - end - - def active? - Task.active_statuses.include?(attributes.root_task["status"]) - end - - def pending_schedule_hearing_tasks - open_tasks.select { |task| task.type == "ScheduleHearingTask" } - end - - def evidence_submission_hold_pending_tasks - open_tasks.select { |task| task.type == "EvidenceSubmissionWindowTask" } - end - - def status - BVAAppealStatus.new( - tasks: BVAAppealStatus::Tasks.new( - open: tasks.select(&:open?), - active: tasks.select(&:active?), - in_progress: tasks.select(&:in_progress?), - cancelled: tasks.select(&:cancelled?), - completed: tasks.select(&:completed?), - assigned: tasks.select(&:assigned?) - ) - ).status - end - - private - - attr_reader :attributes - - def appeal - @appeal ||= Appeal.new.tap do |appeal| - appeal.assign_attributes(attributes.appeal) - end - end - - def most_recently_updated_visible_task - visible_tasks.select { |task| Task.active_statuses.include?(task.status) }.max_by(&:updated_at) || - visible_tasks.select { |task| task.status == "on_hold" }.max_by(&:updated_at) - end - - def visible_tasks - @visible_tasks ||= tasks.reject do |task| - Task.hidden_task_classes.include?(task.type) - end - end - - def most_recently_updated_task - tasks.max_by(&:updated_at) - end - - def tasks - @tasks ||= begin - attributes.tasks.map do |attrs| - attrs["type"].constantize.new.tap do |task| - task.assign_attributes attrs - end - end - end - end - - def fetch_api_status - AppealStatusApiDecorator.new( - self, - scheduled_hearing - ).fetch_status.to_s.titleize.to_sym - end - - def scheduled_hearing - @scheduled_hearing ||= begin - hearings = attributes.hearings.map do |attrs| - SearchQueryService::QueriedHearing.new(attrs) - end - - hearings.reject(&:disposition).find do |hearing| - hearing.scheduled_for >= Time.zone.today - end - end - end -end diff --git a/app/services/search_query_service/queried_hearing.rb b/app/services/search_query_service/queried_hearing.rb deleted file mode 100644 index e179866f95b..00000000000 --- a/app/services/search_query_service/queried_hearing.rb +++ /dev/null @@ -1,90 +0,0 @@ -# frozen_string_literal: true - -class SearchQueryService::QueriedHearing < SimpleDelegator - def initialize(attributes) - @attributes = attributes - - manage_attributes! - - super(hearing) - end - - def judge - @judge ||= - if judge_attributes.present? - User.new.tap do |j| - j.assign_attributes judge_attributes - end - end - end - - def hearing_views - @hearing_views ||= - views_attributes.map do |view_attrs| - HearingView.new.tap do |v| - v.assign_attributes view_attrs - end - end - end - - def readable_request_type - Hearing::HEARING_TYPES[hearing_day&.request_type&.to_sym] - end - - def hearing_day - @hearing_day ||= HearingDay.new.tap do |hd| - hd.assign_attributes hearing_day_attributes - end - end - - def updated_by - @updated_by ||= User.new.tap do |u| - u.assign_attributes updated_by_attributes - end - end - - def virtual? - %w(pending active closed).include?( - virtual_hearing.status - ) - end - - def scheduled_for - scheduled_for_hearing_day(hearing_day, updated_by, regional_office_timezone) - end - - private - - attr_reader( - :attributes, - :hearing_day_attributes, - :updated_by_attributes, - :views_attributes, - :virtual_hearing_attributes, - :judge_attributes - ) - - def regional_office_timezone - RegionalOffice.find!(hearing_day.regional_office || "C").timezone - end - - def virtual_hearing - @virtual_hearing ||= VirtualHearing.new.tap do |vh| - vh.assign_attributes virtual_hearing_attributes - end - end - - def hearing - Hearing.new.tap do |hearing| - hearing.assign_attributes attributes - end - end - - def manage_attributes! - @hearing_day_attributes = attributes.delete("hearing_day") - @updated_by_attributes = attributes.delete("updated_by") - @views_attributes = attributes.delete("views") || [] - @virtual_hearing_attributes = attributes.delete("virtual_hearing") - @judge_attributes = attributes.delete("judge") - end -end diff --git a/app/services/search_query_service/queried_legacy_appeal.rb b/app/services/search_query_service/queried_legacy_appeal.rb deleted file mode 100644 index 50aaab31a7f..00000000000 --- a/app/services/search_query_service/queried_legacy_appeal.rb +++ /dev/null @@ -1,41 +0,0 @@ -# frozen_string_literal: true - -class SearchQueryService::QueriedLegacyAppeal < SimpleDelegator - def initialize(attributes:) - @attributes = attributes - @root_task_attributes = attributes.delete("root_task") - @veteran_attributes = attributes.delete("veteran") - - super(legacy_appeal) - end - - def veteran - @veteran ||= Veteran.new.tap do |veteran| - veteran.assign_attributes veteran_attributes - end - end - - def root_task - @root_task ||= begin - if root_task_attributes - RootTask.new.tap do |root_task| - root_task.assign_attributes root_task_attributes - end - end - end - end - - def claimant_participant_ids - veteran.participant_id - end - - private - - attr_reader :attributes, :root_task_attributes, :veteran_attributes - - def legacy_appeal - @legacy_appeal ||= LegacyAppeal.new.tap do |appeal| - appeal.assign_attributes(attributes) - end - end -end diff --git a/app/services/search_query_service/query.rb b/app/services/search_query_service/query.rb deleted file mode 100644 index b63abc2085b..00000000000 --- a/app/services/search_query_service/query.rb +++ /dev/null @@ -1,416 +0,0 @@ -# frozen_string_literal: true - -class SearchQueryService::Query - def initialize - @vacols = SearchQueryService::VacolsQuery.new - end - - def docket_number_query(docket_number) - query = <<-SQL - #{appeals_internal_query} - where a.stream_docket_number=?; - SQL - [query, docket_number] - end - - def veteran_file_number_query(file_number) - query = <<-SQL - ( - #{appeals_internal_query} - where v.ssn=? or v.file_number=? - ) - UNION - ( - #{legacy_appeals_internal_query} - where v.ssn=? or v.file_number=? - ) - SQL - num_params = 4 - [query, *[file_number].cycle(num_params).to_a] - end - - def veteran_ids_query(veteran_id) - query = <<-SQL - ( - #{appeals_internal_query} - where v.id in (?) - ) - UNION - ( - #{legacy_appeals_internal_query} - where v.id in (?) - ) - SQL - [query, veteran_id, veteran_id] - end - - def vacols_query(vacols_ids) - [vacols.query, vacols_ids] - end - - private - - attr_reader :vacols - - def legacy_appeals_internal_query - <<-SQL - select - a.id, - a.vacols_id external_id, - 'legacy_appeal' type, - null aod_granted_for_person, - 'legacy' docket_type, - ( - select - jsonb_agg(la2) - from - ( - select - la.*, - ( - select - row_to_json(t.*) - from tasks t - where - t.appeal_id=a.id and - t.type='RootTask' and - t.appeal_type='Appeal' - order by t.updated_at desc - limit 1 - ) root_task, - (select row_to_json(v.*)) veteran - from legacy_appeals la - where la.id=a.id - ) la2 - ) appeal, - dd.decision_date, - wm.overtime, - pp.first_name person_first_name, - pp.last_name person_last_name, - v.id veteran_id, - v.first_name veteran_first_name, - v.last_name veteran_last_name, - v.file_number as veteran_file_number, - v.date_of_death, - ( - select jsonb_agg(u2) from - ( - select - u.* - from tasks t - left join users u on - t.assigned_to_id=u.id and - t.assigned_to_type='User' - where - t.appeal_type = 'LegacyAppeal' and - t.appeal_id=a.id and - t.type in ('#{attorney_task_classes.join("','")}') and - t.status != '#{Constants.TASK_STATUSES.cancelled}' - order by t.created_at desc - limit 1 - ) u2 - ) assigned_attorney, - ( - select jsonb_agg(u2) from - ( - select - u.* - from tasks t - left join users u on - t.assigned_to_id=u.id and - t.assigned_to_type='User' - where - t.appeal_type = 'LegacyAppeal' and - t.appeal_id=a.id and - t.type in ('#{judge_task_classes.join("','")}') and - t.status != '#{Constants.TASK_STATUSES.cancelled}' - order by t.created_at desc - limit 1 - ) u2 - ) assigned_judge, - null request_issues, - null active_request_issues, - null withdrawn_request_issues, - null decision_issues, - null hearings_count, - '[]' hearings, - ( - select jsonb_agg(t2) from - ( - select - t.* - from tasks t - left join organizations o on o.id=t.assigned_to_id - left join users u on u.id=t.assigned_to_id - where - t.appeal_id=a.id and - t.appeal_type='LegacyAppeal' - order by updated_at desc - ) t2 - ) tasks - from legacy_appeals a - left join claimants cl on cl.decision_review_id=a.id and cl.decision_review_type='LegacyAppeal' - left join people pp on cl.participant_id=pp.participant_id - left join work_modes wm on wm.appeal_id=a.id and wm.appeal_type='LegacyAppeal' - left join decision_documents dd on dd.appeal_id=a.id and dd.appeal_type='LegacyAppeal' - left join veterans v on v.file_number=( - select - case - when right(a.vbms_id, 1) = 'C' then lpad(regexp_replace(a.vbms_id, '[^0-9]+', '', 'g'), 8, '0') - else regexp_replace(a.vbms_id, '[^0-9]+', '', 'g') - end - ) or v.ssn=( - select - case - when right(a.vbms_id, 1) = 'C' then lpad(regexp_replace(a.vbms_id, '[^0-9]+', '', 'g'), 8, '0') - else regexp_replace(a.vbms_id, '[^0-9]+', '', 'g') - end - ) - SQL - end - - def appeals_internal_query - <<-SQL - select - a.id, - a.uuid::varchar external_id, - a.stream_type type, - aod.id aod_granted_for_person, - a.docket_type, - ( - select jsonb_agg(a2) - from - ( - select - appeals.*, - ( - select - row_to_json(t.*) - from tasks t - where - t.appeal_id=a.id and - t.type='RootTask' and - t.appeal_type='Appeal' - order by updated_at desc - limit 1 - ) root_task, - ( - select jsonb_agg(c2) from - ( - select - c.id, - c.participant_id - from claimants c - where - c.decision_review_type = 'Appeal' and - c.decision_review_id=a.id - ) c2 - ) claimants - from appeals - where id=a.id - ) a2 - ) appeal, - dd.decision_date, - wm.overtime, - pp.first_name person_first_name, - pp.last_name person_last_name, - v.id veteran_id, - v.first_name veteran_first_name, - v.last_name veteran_last_name, - v.file_number as veteran_file_number, - v.date_of_death, - ( - select jsonb_agg(u2) from - ( - select - u.* - from tasks t - left join users u on - t.assigned_to_id=u.id and - t.assigned_to_type='User' - where - t.appeal_type = 'Appeal' and - t.appeal_id=a.id and - t.type in ('#{attorney_task_classes.join("','")}') and - t.status != '#{Constants.TASK_STATUSES.cancelled}' - order by t.created_at desc - limit 1 - ) u2 - ) assigned_attorney, - ( - select jsonb_agg(u2) from - ( - select - u.* - from tasks t - left join users u on - t.assigned_to_id=u.id and - t.assigned_to_type='User' - where - t.appeal_type = 'Appeal' and - t.appeal_id=a.id and - t.type in ('#{judge_task_classes.join("','")}') and - t.status != '#{Constants.TASK_STATUSES.cancelled}' - order by t.created_at desc - limit 1 - ) u2 - ) assigned_judge, - ( - select jsonb_agg(ri2) from - ( - select - ri.id, - ri.benefit_type program, - ri.notes, - ri.decision_date, - ri.nonrating_issue_category, - ri.mst_status, - ri.pact_status, - ri.mst_status_update_reason_notes mst_justification, - ri.pact_status_update_reason_notes pact_justification - from request_issues ri - where - ri.decision_review_type='Appeal' and - ri.decision_review_id=a.id - ) ri2 - ) request_issues, - ( - select jsonb_agg(ri2) from - ( - select - nonrating_issue_category - from request_issues ri - where - ri.ineligible_reason is null and - ri.closed_at is null and - (ri.split_issue_status is null or ri.split_issue_status = 'in_progress') and - ri.decision_review_type='Appeal' and - ri.decision_review_id=a.id - ) ri2 - ) active_request_issues, - ( - select jsonb_agg(ri2) from - ( - select - nonrating_issue_category - from request_issues ri - where - ri.ineligible_reason is null and - ri.closed_status = 'widthrawn' and - ri.decision_review_type='Appeal' and - ri.decision_review_id=a.id - ) ri2 - ) withdrawn_request_issues, - ( - select jsonb_agg(di2) from - ( - select - di.id, - di.disposition, - di.description, - di.benefit_type, - di.diagnostic_code, - di.mst_status, - di.pact_status, - array( - select rdi.request_issue_id - from request_decision_issues rdi - where rdi.decision_issue_id=di.id - ) request_issue_ids, - array( - select rr2 from - ( - select - rr.id, - rr.code, - rr.post_aoj - from remand_reasons rr - where rr.decision_issue_id=di.id - ) rr2 - ) remand_reasons - from decision_issues di - where - di.decision_review_type='Appeal' and - di.decision_review_id = a.id - ) di2 - ) decision_issues, - (select count(id) from hearings h where h.appeal_id=a.id) hearings_count, - ( - select jsonb_agg(h2) from - ( - select - h.*, - ( - select row_to_json(ub) - from (select * from users u where u.id=h.updated_by_id limit 1) ub - ) updated_by, - ( - select row_to_json(hd2) - from (select * from hearing_days hd where hd.id=h.hearing_day_id limit 1) hd2 - ) hearing_day, - ( - select row_to_json(vh2) - from (select * from virtual_hearings vh where vh.hearing_id=h.id limit 1) vh2 - ) virtual_hearing, - ( - select jsonb_agg(hv2) - from (select * from hearing_views hv where hv.hearing_id=h.id) hv2 - ) views, - ( - select row_to_json(j2) - from (select u.full_name from users u where u.id=h.judge_id limit 1) j2 - ) judge - from - hearings h - where - h.appeal_id=a.id - ) h2 - ) hearings, - ( - select jsonb_agg(t2) from - ( - select - t.* - from tasks t - left join organizations o on o.id=t.assigned_to_id - left join users u on u.id=t.assigned_to_id - where - t.appeal_id=a.id and - t.appeal_type='Appeal' - order by updated_at desc - ) t2 - ) tasks - from appeals a - left join claimants cl on cl.decision_review_id=a.id and cl.decision_review_type='Appeal' - left join people pp on cl.participant_id=pp.participant_id - left join work_modes wm on wm.appeal_id=a.id and wm.appeal_type='Appeal' - left join decision_documents dd on dd.appeal_id=a.id and dd.appeal_type='Appeal' - left join advance_on_docket_motions aod on aod.appeal_id=a.id and aod.person_id=pp.id and aod.appeal_type='Appeal' - left join veterans v on a.veteran_file_number=v.file_number or a.veteran_file_number=v.ssn - SQL - end - - def attorney_task_classes - [ - "AttorneyTask", - *AttorneyTask.descendants.map(&:name) - # 'AttorneyRewriteTask', - # 'AttorneyDispatchReturnTask', - # 'DocketSwitchGrantedTask', - # 'DocketSwitchDeniedTask', - ] - end - - def judge_task_classes - [ - "JudgeTask", - *JudgeTask.descendants.map(&:name) - # 'JudgeAddressMotionToVacateTask', - # 'JudgeQualityReviewTask', - # 'JudgeDispatchReturnTask', - # 'JudgeAssignTask', - # 'DocketSwitchRulingTask', - # 'JudgeDecisionReviewTask' - ] - end -end diff --git a/app/services/search_query_service/search_response.rb b/app/services/search_query_service/search_response.rb deleted file mode 100644 index 09ca32c022e..00000000000 --- a/app/services/search_query_service/search_response.rb +++ /dev/null @@ -1,9 +0,0 @@ -# frozen_string_literal: true - -SearchQueryService::SearchResponse = Struct.new(:appeal, :type, :api_response) do - def filter_restricted_info!(statuses) - if statuses.include?(api_response.attributes.status) - api_response.attributes.assigned_to_location = nil - end - end -end diff --git a/app/services/search_query_service/vacols_query.rb b/app/services/search_query_service/vacols_query.rb deleted file mode 100644 index 68a9d287451..00000000000 --- a/app/services/search_query_service/vacols_query.rb +++ /dev/null @@ -1,112 +0,0 @@ -# frozen_string_literal: true - -class SearchQueryService::VacolsQuery - def query - <<-SQL - select - aod, - "cases".bfkey vacols_id, - "cases".bfcurloc, - "cases".bfddec, - "cases".bfmpro, - "cases".bfac, - "cases".bfcorlid, - "correspondents".snamef, - "correspondents".snamel, - "correspondents".sspare1, - "correspondents".sspare2, - "correspondents".slogid, - "folders".tinum, - "folders".tivbms, - "folders".tisubj2, - (select - JSON_ARRAYAGG(JSON_OBJECT( - 'venue' value #{case_hearing_venue_select}, - 'external_id' value "h".hearing_pkseq, - 'type' value "h".hearing_type, - 'disposition' value "h".hearing_disp, - 'date' value "h".hearing_date - )) - from hearsched "h" - where "h".folder_nr="cases".bfkey - ) hearings, - (select - JSON_ARRAYAGG(JSON_OBJECT( - 'id' value "i".isskey, - 'vacols_sequence_id' value "i".issseq, - 'issprog' value "i".issprog, - 'isscode' value "i".isscode, - 'isslev1' value "i".isslev1, - 'isslev2' value "i".isslev2, - 'isslev3' value "i".isslev3, - 'issdc' value "i".issdc, - 'issdesc' value "i".issdesc, - 'issdcls' value "i".issdcls, - 'issmst' value "i".issmst, - 'isspact' value "i".isspact, - 'issprog_label' value "iss".prog_desc, - 'isscode_label' value "iss".iss_desc, - 'isslev1_label' value case when "i".isslev1 is not null then - case when "iss".lev1_code = '##' then - "vft".ftdesc else "iss".lev1_desc - end - end , - 'isslev2_label' value case when "i".isslev2 is not null then - case when "iss".lev2_code = '##' then - "vft".ftdesc else "iss".lev2_desc - end - end, - 'isslev3_label' value case when "i".isslev3 is not null then - case when "iss".lev3_code = '##' then - "vft".ftdesc else "iss".lev3_desc - end - end - )) - from issues "i" - inner join issref "iss" - on "i".issprog = "iss".prog_code - and "i".isscode = "iss".iss_code - and ("i".isslev1 is null - or "iss".lev1_code = '##' - or "i".isslev1 = "iss".lev1_code) - and ("i".isslev2 is null - or "iss".lev2_code = '##' - or "i".isslev2 = "iss".lev2_code) - and ("i".isslev3 is null - or "iss".lev3_code = '##' - or "i".isslev3 = "iss".lev3_code) - left join vftypes "vft" - on "vft".fttype = 'dg' - and (("iss".lev1_code = '##' and 'dg' || "i".isslev1 = "vft".ftkey) - or ("iss".lev2_code = '##' and 'dg' || "i".isslev2 = "vft".ftkey) - or ("iss".lev3_code = '##' and 'dg' || "i".isslev3 = "vft".ftkey)) - where "i".isskey="cases".bfkey - ) issues, - (select count("hearings".hearing_pkseq) from hearsched "hearings" where "hearings".folder_nr="cases".bfkey) hearing_count, - (select count("issues".isskey) from issues "issues" where "issues".isskey="cases".bfkey and "issues".issmst='Y') issues_mst_count, - (select count("issues".isskey) from issues "issues" where "issues".isskey="cases".bfkey and "issues".isspact='Y') issues_pact_count - from - brieff "cases" - left join folder "folders" - on "cases".bfkey="folders".ticknum - left join corres "correspondents" - on "cases".bfcorkey="correspondents".stafkey - #{VACOLS::Case::JOIN_AOD} - where - "cases".bfkey in (?) - SQL - end - - private - - def case_hearing_venue_select - <<-SQL - case - when "h".hearing_type='#{VACOLS::CaseHearing::HEARING_TYPE_LOOKUP[:video]}' AND - "h".hearing_date < '#{VACOLS::CaseHearing::VACOLS_VIDEO_HEARINGS_END_DATE}' - then #{Rails.application.config.vacols_db_name}.HEARING_VENUE("h".vdkey) - else "cases".bfregoff - end - SQL - end -end diff --git a/app/services/search_query_service/vso_user_search_results.rb b/app/services/search_query_service/vso_user_search_results.rb deleted file mode 100644 index 7b7e313b29e..00000000000 --- a/app/services/search_query_service/vso_user_search_results.rb +++ /dev/null @@ -1,68 +0,0 @@ -# frozen_string_literal: true - -class SearchQueryService::VsoUserSearchResults - def initialize(search_results:, user:) - @user = user - @search_results = search_results - - filter_restricted_results! - end - - def call - established_results.select do |result| - if result.type == :appeal - result.appeal.claimants.any? do |claimant| - vso_participant_ids.include?(poas.dig(claimant.participant_id, :participant_id)) - end - else - vso_participant_ids.include?(poas.dig(result.appeal.veteran.participant_id, :participant_id)) - end - end - end - - private - - attr_reader :search_results, :user - - RESTRICTED_STATUSES = - [ - :distributed_to_judge, - :ready_for_signature, - :on_hold, - :misc, - :unknown, - :assigned_to_attorney - ].freeze - - def filter_restricted_results! - search_results.map do |result| - result.filter_restricted_info!(RESTRICTED_STATUSES) - end - end - - def vso_participant_ids - @vso_participant_ids ||= user.vsos_user_represents.map { |poa| poa[:participant_id] } - end - - def established_results - @established_results ||= search_results.select do |result| - result.type == :legacy_appeal || result.appeal.established_at.present? - end - end - - def claimant_participant_ids - @claimant_participant_ids ||= established_results.flat_map do |result| - result.appeal.claimant_participant_ids - end.uniq - end - - def poas - Rails.logger.info "BGS Called `fetch_poas_by_participant_ids` with \"#{claimant_participant_ids.join('"')}\"" - - @poas ||= bgs.fetch_poas_by_participant_ids(claimant_participant_ids) - end - - def bgs - @bgs ||= BGSService.new - end -end diff --git a/app/workflows/case_search_results_base.rb b/app/workflows/case_search_results_base.rb index 56c7d870c1e..f8e4e1a0528 100644 --- a/app/workflows/case_search_results_base.rb +++ b/app/workflows/case_search_results_base.rb @@ -56,54 +56,18 @@ def search_call ) end - def api_call - @success = valid? - - api_search_results if success - - FormResponse.new( - success: success, - errors: errors.messages[:workflow], - extra: error_status_or_api_search_results - ) - end - protected attr_reader :status, :user - def search_page_json_appeals(appeals) - ama_appeals, legacy_appeals = appeals.partition { |appeal| appeal.is_a?(Appeal) } - - ama_hash = WorkQueue::AppealSearchSerializer.new( - ama_appeals, is_collection: true, params: { user: user } - ).serializable_hash - - legacy_hash = WorkQueue::LegacyAppealSearchSerializer.new( - legacy_appeals, is_collection: true, params: { user: user } - ).serializable_hash - - ama_hash[:data].concat(legacy_hash[:data]) - end - - def json_appeals(appeals) - ama_appeals, legacy_appeals = appeals.partition { |appeal| appeal.is_a?(Appeal) } - - ama_hash = WorkQueue::AppealSerializer.new( - ama_appeals, is_collection: true, params: { user: user } - ).serializable_hash - - legacy_hash = WorkQueue::LegacyAppealSerializer.new( - legacy_appeals, is_collection: true, params: { user: user } - ).serializable_hash - - ama_hash[:data].concat(legacy_hash[:data]) - end - def current_user_is_vso_employee? user.vso_employee? end + def appeals + AppealFinder.new(user: user).find_appeals_for_veterans(veterans_user_can_access) + end + def claim_reviews ClaimReview.find_all_visible_by_file_number(veterans_user_can_access.map(&:file_number)) end @@ -113,14 +77,10 @@ def veterans [] end - def appeals - [] - end - # Users may also view appeals with appellants whom they represent. # We use this to add these appeals back into results when the user is not on the veteran's poa. def additional_appeals_user_can_access - appeals.map(&:appeal).filter do |appeal| + appeals.filter do |appeal| appeal.veteran_is_not_claimant && user.organizations.any? do |uo| appeal.representatives.include?(uo) @@ -132,6 +92,34 @@ def veterans_user_can_access @veterans_user_can_access ||= veterans.select { |veteran| access?(veteran.file_number) } end + def json_appeals(appeals) + ama_appeals, legacy_appeals = appeals.partition { |appeal| appeal.is_a?(Appeal) } + + ama_hash = WorkQueue::AppealSerializer.new( + ama_appeals, is_collection: true, params: { user: user } + ).serializable_hash + + legacy_hash = WorkQueue::LegacyAppealSerializer.new( + legacy_appeals, is_collection: true, params: { user: user } + ).serializable_hash + + ama_hash[:data].concat(legacy_hash[:data]) + end + + def search_page_json_appeals(appeals) + ama_appeals, legacy_appeals = appeals.partition { |appeal| appeal.is_a?(Appeal) } + + ama_hash = WorkQueue::AppealSearchSerializer.new( + ama_appeals, is_collection: true, params: { user: user } + ).serializable_hash + + legacy_hash = WorkQueue::LegacyAppealSearchSerializer.new( + legacy_appeals, is_collection: true, params: { user: user } + ).serializable_hash + + ama_hash[:data].concat(legacy_hash[:data]) + end + private attr_accessor :errors @@ -154,11 +142,7 @@ def valid? def validation_hook; end def access?(file_number) - return true if !current_user_is_vso_employee? - - Rails.logger.info "BGS Called `can_access?` with \"#{file_number}\"" - - bgs.can_access?(file_number) + !current_user_is_vso_employee? || bgs.can_access?(file_number) end def bgs @@ -184,40 +168,10 @@ def error_status_or_case_search_results case_search_results end - def error_status_or_api_search_results - return { status: status } unless success - - api_search_results - end - - def error_status_or_api_case_search_results - return { status: status } unless success - - api_case_search_results - end - - def api_search_results - @api_search_results ||= { - search_results: { - appeals: json_appeals(appeal_finder_appeals), - claim_reviews: claim_reviews.map(&:search_table_ui_hash) - } - } - end - - def api_case_search_results - @api_case_search_results ||= { - case_search_results: { - appeals: search_page_json_appeals(appeal_finder_appeals), - claim_reviews: claim_reviews.map(&:search_table_ui_hash) - } - } - end - def search_results @search_results ||= { search_results: { - appeals: appeals.map(&:api_response), + appeals: json_appeals(appeals), claim_reviews: claim_reviews.map(&:search_table_ui_hash) } } @@ -226,7 +180,7 @@ def search_results def case_search_results @case_search_results ||= { case_search_results: { - appeals: appeals.map(&:api_response), + appeals: search_page_json_appeals(appeals), claim_reviews: claim_reviews.map(&:search_table_ui_hash) } } diff --git a/app/workflows/case_search_results_for_caseflow_veteran_id.rb b/app/workflows/case_search_results_for_caseflow_veteran_id.rb index b75e13dc0e3..3fe2b5604df 100644 --- a/app/workflows/case_search_results_for_caseflow_veteran_id.rb +++ b/app/workflows/case_search_results_for_caseflow_veteran_id.rb @@ -20,28 +20,6 @@ def validation_hook validate_veterans_exist end - def appeal_finder_appeals - AppealFinder.new(user: user).find_appeals_for_veterans(veterans_user_can_access) - end - - def search_results - @search_results ||= SearchQueryService.new( - veteran_ids: veterans_user_can_access.map(&:id) - ).search_by_veteran_ids - end - - def vso_user_search_results - SearchQueryService::VsoUserSearchResults.new(user: user, search_results: search_results).call - end - - def appeals - if user.vso_employee? - vso_user_search_results - else - search_results - end - end - def not_found_error { "title": "Veteran not found", diff --git a/app/workflows/case_search_results_for_docket_number.rb b/app/workflows/case_search_results_for_docket_number.rb index 78b9dc49656..6c16a50a46d 100644 --- a/app/workflows/case_search_results_for_docket_number.rb +++ b/app/workflows/case_search_results_for_docket_number.rb @@ -13,10 +13,6 @@ def claim_reviews end def appeals - SearchQueryService.new(docket_number: docket_number).search_by_docket_number - end - - def appeal_finder_appeals AppealFinder.find_appeals_by_docket_number(docket_number) end @@ -37,7 +33,7 @@ def not_found_error def veterans # Determine vet that corresponds to docket number so we can validate user can access - @file_numbers_for_appeals ||= appeals.map(&:api_response).map(&:attributes).map(&:veteran_file_number) + @file_numbers_for_appeals ||= appeals.map(&:veteran_file_number) @veterans ||= VeteranFinder.find_or_create_all(@file_numbers_for_appeals) end end diff --git a/app/workflows/case_search_results_for_veteran_file_number.rb b/app/workflows/case_search_results_for_veteran_file_number.rb index 1cd1bf6edeb..3f0f97fe466 100644 --- a/app/workflows/case_search_results_for_veteran_file_number.rb +++ b/app/workflows/case_search_results_for_veteran_file_number.rb @@ -23,26 +23,6 @@ def validate_file_number_or_ssn_presence @status = :bad_request end - def appeals - if user.vso_employee? - vso_user_search_results - else - search_results - end - end - - def vso_user_search_results - SearchQueryService::VsoUserSearchResults.new(user: user, search_results: search_results).call - end - - def search_results - @search_results ||= SearchQueryService.new(file_number: file_number_or_ssn).search_by_veteran_file_number - end - - def appeal_finder_appeals - AppealFinder.new(user: user).find_appeals_for_veterans(veterans_user_can_access) - end - def missing_veteran_file_number_or_ssn_error { "title": "Veteran file number missing", diff --git a/spec/feature/queue/search_spec.rb b/spec/feature/queue/search_spec.rb index 02e393a7350..7be69b901dd 100644 --- a/spec/feature/queue/search_spec.rb +++ b/spec/feature/queue/search_spec.rb @@ -612,9 +612,7 @@ def perform_search(docket_number = appeal.docket_number) context "when backend returns non-serialized error" do it "displays generic server error message" do - allow_any_instance_of(SearchQueryService).to( - receive(:search_by_veteran_file_number).and_raise(StandardError) - ) + allow(LegacyAppeal).to receive(:fetch_appeals_by_file_number).and_raise(StandardError) visit "/search" fill_in "searchBarEmptyList", with: appeal.sanitized_vbms_id click_on "Search" @@ -657,7 +655,7 @@ def perform_search it "shows 'Withdrawn' text on search results page" do policy = instance_double(WithdrawnDecisionReviewPolicy) - allow(WithdrawnDecisionReviewPolicy).to receive(:new).and_return policy + allow(WithdrawnDecisionReviewPolicy).to receive(:new).with(caseflow_appeal).and_return policy allow(policy).to receive(:satisfied?).and_return true perform_search diff --git a/spec/fixes/assigned_to_search_results_spec.rb b/spec/fixes/assigned_to_search_results_spec.rb index 1ef26f380a1..75765fdddd3 100644 --- a/spec/fixes/assigned_to_search_results_spec.rb +++ b/spec/fixes/assigned_to_search_results_spec.rb @@ -29,7 +29,7 @@ end it "creates tasks and other records associated with a dispatched appeal" do - expect(BVAAppealStatus.new(tasks: appeal.tasks).status).to eq :unknown # We will fix this + expect(BVAAppealStatus.new(appeal: appeal).status).to eq :unknown # We will fix this expect(appeal.root_task.status).to eq "on_hold" visit "/search?veteran_ids=#{appeal.veteran.id}" @@ -57,7 +57,7 @@ } visit "/search?veteran_ids=#{appeal.veteran.id}" expect(page).to have_content("Signed") # in the "Appellant Name" column - expect(BVAAppealStatus.new(tasks: appeal.tasks).status).to eq :signed + expect(BVAAppealStatus.new(appeal: appeal).status).to eq :signed bva_dispatcher = org_dispatch_task.children.first.assigned_to expect(page).to have_content(bva_dispatcher.css_id) # in the "Assigned To" column expect(appeal.assigned_to_location).to eq bva_dispatcher.css_id @@ -65,7 +65,7 @@ BvaDispatchTask.outcode(appeal, params, bva_dispatcher) visit "/search?veteran_ids=#{appeal.veteran.id}" expect(page).to have_content("Dispatched") # in the "Appellant Name" column - expect(BVAAppealStatus.new(tasks: appeal.tasks).status).to eq :dispatched + expect(BVAAppealStatus.new(appeal: appeal).status).to eq :dispatched expect(page).to have_content("Post-decision") # in the "Assigned To" column expect(appeal.assigned_to_location).to eq "Post-decision" expect(appeal.root_task.status).to eq "completed" diff --git a/spec/fixes/backfill_early_ama_appeal_spec.rb b/spec/fixes/backfill_early_ama_appeal_spec.rb index e26da3bfe4d..e1ed3774706 100644 --- a/spec/fixes/backfill_early_ama_appeal_spec.rb +++ b/spec/fixes/backfill_early_ama_appeal_spec.rb @@ -28,7 +28,7 @@ let(:atty_draft_date) { dispatch_date - 2.hours } it "creates tasks and other records associated with a dispatched appeal" do - expect(BVAAppealStatus.new(tasks: appeal.tasks).status).to eq :unknown # We will fix this + expect(BVAAppealStatus.new(appeal: appeal).status).to eq :unknown # We will fix this expect(appeal.root_task.status).to eq "completed" # 1. Create tasks to associate appeal with a judge and attorney @@ -134,7 +134,7 @@ decision_doc = DecisionDocument.create!(params) expect(appeal.decision_document).to eq decision_doc - expect(BVAAppealStatus.new(tasks: appeal.tasks).status).to eq :dispatched + expect(BVAAppealStatus.new(appeal: appeal).status).to eq :dispatched end end end diff --git a/spec/services/bva_appeal_status_spec.rb b/spec/services/bva_appeal_status_spec.rb index 8effec2eb4a..5bdb70c2929 100644 --- a/spec/services/bva_appeal_status_spec.rb +++ b/spec/services/bva_appeal_status_spec.rb @@ -18,7 +18,7 @@ status = pair.first sort_key = pair.last appeal = Appeal.find(appeal_id) - appeal_status = described_class.new(tasks: appeal.tasks) + appeal_status = described_class.new(appeal: appeal) expect(appeal_status.to_s).to eq(status) expect(appeal_status.to_i).to eq(sort_key.to_i + 1) # our sort keys are 1-based diff --git a/spec/services/search_query_service_spec.rb b/spec/services/search_query_service_spec.rb deleted file mode 100644 index b9d87a7a29c..00000000000 --- a/spec/services/search_query_service_spec.rb +++ /dev/null @@ -1,318 +0,0 @@ -# frozen_string_literal: true - -describe "SearchQueryService" do - let(:ssn) { "146600001" } - let(:dob) { Faker::Date.in_date_period(year: 1960) } - - let(:uuid) { SecureRandom.uuid } - let(:veteran_first_name) { Faker::Name.first_name } - let(:veteran_last_name) { Faker::Name.last_name } - let(:claimant_first_name) { Faker::Name.first_name } - let(:claimant_last_name) { Faker::Name.last_name } - let(:veteran_full_name) { FullName.new(veteran_first_name, "", veteran_last_name).to_s } - let(:claimant_full_name) { FullName.new(claimant_first_name, "", claimant_last_name).to_s } - let(:docket_type) { "hearing" } - let(:docket_number) { "240111-1111" } - - let(:descision_document_attrs) do - { - decision_date: Faker::Date.between(from: 2.years.ago, to: 1.year.ago) - } - end - - context "all data in caseflow" do - context "veteran is claimant" do - let(:veteran_attrs) do - { - ssn: ssn, - file_number: ssn, - date_of_birth: dob, - date_of_death: nil, - first_name: veteran_first_name, - middle_name: nil, - last_name: veteran_last_name - } - end - - let(:veteran) { FactoryBot.create(:veteran, veteran_attrs) } - - let(:appeal_attributes) do - { - aod_based_on_age: false, - stream_docket_number: docket_number, - veteran_file_number: ssn, - veteran: veteran, - stream_type: Constants.AMA_STREAM_TYPES.original, - uuid: uuid - } - end - - let(:judge) { create(:user, :judge) } - - let!(:appeal) do - FactoryBot.create( - :appeal, - # has hearing(s) - :hearing_docket, - :held_hearing, - :tied_to_judge, - # has decision document - :dispatched, - # has issue(s) - :with_request_issues, - :with_decision_issue, - { - associated_judge: judge, - tied_judge: judge - }.merge(appeal_attributes) - ).tap do |appeal| - appeal.decision_issues.first.update( - mst_status: true, - pact_status: true - ) - create( - :virtual_hearing, - hearing: appeal.hearings.first - ) - appeal.hearings.first.update(updated_by: judge) - appeal.hearings.first.hearing_day.update(regional_office: "RO19") - appeal.hearings.first.hearing_views.create(user_id: judge.id) - # create work mode - appeal.overtime = true - AdvanceOnDocketMotion.create( - person: appeal.claimants.first.person, - appeal: appeal - ) - end.reload - end - - context "finds by docket number" do - subject { SearchQueryService.new(docket_number: appeal.stream_docket_number) } - - it "finds by docket number" do - expect(appeal).to be_persisted - - search_results = subject.search_by_docket_number - - expect(search_results.length).to eq(1) - - result = search_results.first.api_response - - expect(result.id).to be - expect(result.type).to eq "appeal" - - attributes = result.attributes - - expect(attributes.aod).to be_truthy - expect(attributes.appellant_full_name).to eq veteran_full_name - expect(attributes.assigned_to_location).to eq appeal.assigned_to_location - expect(attributes.caseflow_veteran_id).to eq veteran.id - expect(attributes.decision_date).to eq appeal.decision_document.decision_date - expect(attributes.docket_name).to eq appeal.docket_type - expect(attributes.docket_number).to eq appeal.stream_docket_number - expect(attributes.external_id).to eq appeal.uuid - expect(attributes.hearings.length).to eq appeal.hearings.length - expect(attributes.hearings.first[:held_by]).to eq judge.full_name - expect(attributes.issues.length).to eq(appeal.request_issues.length) - expect(attributes.mst).to eq appeal.decision_issues.any?(&:mst_status) - expect(attributes.pact).to eq appeal.decision_issues.any?(&:pact_status) - expect(attributes.paper_case).to be_falsy - expect(attributes.status).to eq Appeal.find(appeal.id).status.status - expect(attributes.veteran_appellant_deceased).to be_falsy - expect(attributes.veteran_file_number).to eq ssn - expect(attributes.veteran_full_name).to eq veteran_full_name - expect(attributes.contested_claim).to be_falsy - expect(attributes.withdrawn).to eq(false) - end - end - - context "finds by file number" do - subject { SearchQueryService.new(file_number: ssn) } - - it "finds by veteran file number" do - expect(appeal).to be_persisted - - search_results = subject.search_by_veteran_file_number - - expect(search_results.length).to eq(1) - - result = search_results.first.api_response - - expect(result.id).to be - expect(result.type).to eq "appeal" - end - end - - context "finds by veteran ids" do - subject { SearchQueryService.new(veteran_ids: [veteran.id]) } - - it "finds by veteran ids" do - expect(appeal).to be_persisted - - search_results = subject.search_by_veteran_ids - - expect(search_results.length).to eq(1) - - result = search_results.first.api_response - - expect(result.id).to be - expect(result.type).to eq "appeal" - end - end - end - end - - let(:veteran_address) do - { - addrs_one_txt: nil, - addrs_two_txt: nil, - addrs_three_txt: nil, - city_nm: nil, - cntry_nm: nil, - postal_cd: nil, - zip_prefix_nbr: nil, - ptcpnt_addrs_type_nm: nil - } - end - - let(:legacy_appeal) do - create( - :legacy_appeal, - vbms_id: ssn, - vacols_case: vacols_case, - veteran_address: veteran_address - ) - end - - # must be created first for legacy_appeal factory to find it - let!(:veteran) do - create( - :veteran, - file_number: ssn, - first_name: veteran_first_name, - last_name: veteran_last_name - ) - end - - let(:vacols_decision_date) { 2.weeks.ago } - let(:vacols_case_attrs) do - { - bfkey: ssn, - bfcorkey: ssn, - bfac: "1", - bfcorlid: "100000099", - bfcurloc: "CASEFLOW", - bfddec: vacols_decision_date, - bfmpro: "ACT" - - # bfregoff: "RO18", - # bfdloout: "2024-03-26T11:13:32.000Z", - # bfcallup: "", - # bfhr: "2", - # bfdocind: "T", - } - end - - let(:issues_count) { 5 } - let(:vacols_case_issues) do - create_list( - :case_issue, - issues_count, - isspact: "Y", - issmst: "Y" - ) - end - - let(:hearings_count) { 5 } - let(:vacols_case_hearings) do - create_list( - :case_hearing, - hearings_count - ) - end - - let(:vacols_correspondent) do - create(:correspondent, vacols_correspondent_attrs) - end - - let(:vacols_folder) do - build(:folder) - end - - let(:vacols_case) do - create( - :case, - { - correspondent: vacols_correspondent, - case_issues: vacols_case_issues, - case_hearings: vacols_case_hearings, - folder: vacols_folder - }.merge(vacols_case_attrs) - ) - end - - context "when appeal is a legacy appeal with data in vacols and caseflow" do - context "when veteran is claimant" do - let(:vacols_correspondent_attrs) do - { - sspare2: veteran_first_name, - sspare1: veteran_last_name, - snamel: veteran_last_name, - snamef: veteran_first_name, - stafkey: ssn - } - end - - let!(:claimant) do - create( - :claimant, - type: "VeteranClaimant", - decision_review: legacy_appeal - ) - end - - subject { SearchQueryService.new(file_number: ssn) } - - it "finds by file number" do - search_results = subject.search_by_veteran_file_number - result = search_results.first.api_response - - expect(result.id).to be - expect(result.type).to eq "legacy_appeal" - - attributes = result.attributes - expect(attributes.docket_name).to eq "legacy" - expect(attributes.aod).to be_falsy - expect(attributes.appellant_full_name).to eq veteran_full_name - expect(attributes.assigned_to_location).to eq legacy_appeal.assigned_to_location - expect(attributes.caseflow_veteran_id).to eq veteran.id - expect(attributes.decision_date).to eq AppealRepository.normalize_vacols_date(vacols_decision_date) - expect(attributes.docket_name).to eq "legacy" - expect(attributes.docket_number).to eq vacols_folder.tinum - expect(attributes.external_id).to eq vacols_case.id - expect(attributes.hearings.length).to eq hearings_count - expect(attributes.issues.length).to eq issues_count - expect(attributes.mst).to be_truthy - expect(attributes.pact).to be_truthy - expect(attributes.paper_case).to eq "Paper" - expect(attributes.status).to eq "Active" - expect(attributes.veteran_appellant_deceased).to be_falsy - expect(attributes.veteran_file_number).to eq ssn - expect(attributes.veteran_full_name).to eq veteran_full_name - expect(attributes.withdrawn).to be_falsy - end - - context "finds by veteran ids" do - subject { SearchQueryService.new(veteran_ids: [veteran.id]) } - - it "finds by veteran ids" do - search_results = subject.search_by_veteran_ids - result = search_results.first.api_response - - expect(result.id).to be - expect(result.type).to eq "legacy_appeal" - end - end - end - end -end