diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index da9f9399d77..5909d3e55c9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -30,7 +30,7 @@ variables: # Specify the environment: loadtest, demo, exp DP3_ENV: &dp3_env placeholder_env - # Specify the branch to deploy + # Specify the branch to deploy TODO: this might be not needed. So far useless DP3_BRANCH: &dp3_branch placeholder_branch_name # Ignore branches for integration tests @@ -39,6 +39,14 @@ variables: CLIENT_IGNORE_BRANCH: &client_ignore_branch placeholder_branch_name SERVER_IGNORE_BRANCH: &server_ignore_branch placeholder_branch_name + #RUNNER_TAG: &runner_tag milmove + RUNNER_TAG: &runner_tag milmove + DOCKER_RUNNER_TAG: &docker_runner_tag eks_cluster_runner + + postgres: &postgres postgres:16.4 + #postgres: &postgres postgres:16.4 + redis: &redis redis:5.0.6 + stages: - pre_checks - build @@ -57,24 +65,42 @@ stages: - export REACT_APP_ERROR_LOGGING=otel .announce_failure: &announce_failure - - if [[ "$CI_COMMIT_BRANCH" == "main" && "$CI_JOB_STATUS" == "failed" ]]; then - echo "Announcing broken branch in GitLab CI" + #- if [[ "$CI_COMMIT_BRANCH" == "main" && "$CI_JOB_STATUS" == "failed" ]]; then + - echo $CI_COMMIT_BRANCH + - echo $CI_JOB_STATUS + - echo "Announcing broken branch in GitLab CI" + # fi + +.setup_tls_vars_dp3: &setup_tls_vars_dp3 + - | + if [[ "$DP3_ENV" == "exp" || "$DP3_ENV" == "loadtest" || "$DP3_ENV" == "demo" ]]; then + export ENV=$(echo ${DP3_ENV} | tr '[:lower:]' '[:upper:]'); + export TLS_CERT=$(eval echo \$${ENV^^}_DP3_CERT); + export TLS_KEY=$(eval echo \$${ENV^^}_DP3_KEY); + export TLS_CA=$(eval echo \$${ENV^^}_DP3_CA); fi .setup_aws_vars_dp3: &setup_aws_vars_dp3 - - if [[ "$DP3_ENV" == "exp" OR "$DP3_ENV" == "loadtest" OR "$DP3_ENV" == "demo" ]]; then - export AWS_DEFAULT_REGION=$(eval echo \$${DP3_ENV^^}_REGION) - export AWS_ACCOUNT_ID=$(eval echo \$${DP3_ENV^^}_ACCOUNT_ID) - export AWS_ACCESS_KEY_ID=$(eval echo \$${DP3_ENV^^}_ACCESS_KEY_ID) - export AWS_SECRET_ACCESS_KEY=$(eval echo \$${DP3_ENV^^}_SECRET_ACCESS_KEY) + - | + if [[ "$DP3_ENV" == "exp" || "$DP3_ENV" == "loadtest" || "$DP3_ENV" == "demo" ]]; then + export ENV=$(echo ${DP3_ENV} | tr '[:lower:]' '[:upper:]'); + export AWS_DEFAULT_REGION=$(eval echo \$${ENV^^}_REGION); + export AWS_ACCOUNT_ID=$(eval echo \$${ENV^^}_ACCOUNT_ID); + export AWS_ACCESS_KEY_ID=$(eval echo \$${ENV^^}_ACCESS_KEY_ID); + export AWS_SECRET_ACCESS_KEY=$(eval echo \$${ENV^^}_SECRET_ACCESS_KEY); fi -.setup_tls_vars_dp3: &setup_tls_vars_dp3 - - if [[ "$DP3_ENV" == "exp" OR "$DP3_ENV" == "loadtest" OR "$DP3_ENV" == "demo" ]]; then - export TLS_CERT=$(eval echo \$${DP3_ENV^^}_DP3_CERT) - export TLS_KEY=$(eval echo \$${DP3_ENV^^}_DP3_KEY) - export TLS_CA=$(eval echo \$${DP3_ENV^^}_DP3_CA) - fi +.setup_release_dp3: &setup_release_dp3 + - | + if [[ "$DP3_ENV" == "exp" || "$DP3_ENV" == "loadtest" || "$DP3_ENV" == "demo" ]]; then + export ENV=$(echo ${DP3_ENV} | tr '[:lower:]' '[:upper:]'); + export AWS_REGION=$(eval echo \$${ENV}_REGION); + export AWS_ACCOUNT_ID=$(eval echo \$${ENV}_ACCOUNT_ID); + export ECR_REPOSITORY_URI=$(echo ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com) + export APP_DOCKER_FILE=Dockerfile.dp3 + export TASK_DOCKER_FILE=Dockerfile.tasks_dp3 + export APP_ENVIRONMENT=$ENV + fi .setup_aws_vars_stg: &setup_aws_vars_stg - export AWS_DEFAULT_REGION=$STG_REGION @@ -100,14 +126,6 @@ stages: - export TLS_KEY=$PRD_MOVE_MIL_DOD_TLS_KEY - export TLS_CA=$PRD_MOVE_MIL_DOD_TLS_CA -.setup_release_dp3: &setup_release_dp3 - #if demo/loadtest/exp - - export ECR_REPOSITORY_URI=$(eval echo \$${DP3_ENV^^}_ACCOUNT_ID).dkr.ecr.$(eval echo \$${DP3_ENV^^}_REGION).amazonaws.com - - export APP_DOCKER_FILE=Dockerfile.dp3 - - export TASK_DOCKER_FILE=Dockerfile.tasks_dp3 - - export APP_ENVIRONMENT=$DPS_ENV - - echo ${ECR_REPOSITORY_URI} - .setup_release_stg: &setup_release_stg #if main - export ECR_REPOSITORY_URI=${STG_ACCOUNT_ID}.dkr.ecr.${STG_REGION}.amazonaws.com @@ -115,7 +133,6 @@ stages: - export TASK_DOCKER_FILE=Dockerfile.tasks_dp3 #TODO: update demo to stg - export APP_ENVIRONMENT=demo - - echo ${ECR_REPOSITORY_URI} .setup_release_prd: &setup_release_prd #build off prd variables @@ -124,7 +141,6 @@ stages: - export TASK_DOCKER_FILE=Dockerfile.tasks_dp3 #TODO: update exp to prod - export APP_ENVIRONMENT=exp - - echo ${ECR_REPOSITORY_URI} .kaniko_before_setup: &kaniko_before_setup # prep login for kaniko @@ -141,9 +157,93 @@ stages: .check_debug: &check_debug - if: '$debug == "true"' +.check_integration_ignore_branch: &check_integration_ignore_branch + - if: '$CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == $INTEGRATION_IGNORE_BRANCH' + +.check_integration_mtls_ignore_branch: &check_integration_mtls_ignore_branch + - if: '$CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == $INTEGRATION_MTLS_IGNORE_BRANCH' + +.check_client_ignore_branch: &check_client_ignore_branch + - if: '$CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == $CLIENT_IGNORE_BRANCH' + +.check_server_ignore_branch: &check_server_ignore_branch + - if: '$CI_COMMIT_BRANCH == "main" || $CI_COMMIT_BRANCH == $SERVER_IGNORE_BRANCH' + + +.install_yarn: &install_yarn + - | + mkdir -p /builds/milmove/mymove/.cache + mkdir -p /builds/milmove/mymove/.cache/yarn + yarn install --frozen-lockfile --cache-folder /builds/milmove/mymove/.cache/yarn + scripts/check-generated-code yarn.lock + echo "yarn check dependencies" + ./scripts/rebuild-dependencies-without-binaries + +.yarn_cache: &yarn_cache + key: + files: + - yarn.lock + paths: + - .cache/yarn + +.go_cache: &go_cache + key: + files: + - go.sum + paths: + - $GOPATH/pkg/mod + - /builds/milmove/mymove/bin + +.setup_generic_app_env_variables: &setup_generic_app_env_variables + - | + export APPLICATION=app + export DB_PASSWORD=mysecretpassword + export DB_USER_LOW_PRIV=crud + export DB_PASSWORD_LOW_PRIV=mysecretpassword + export DB_USER=postgres + export DB_HOST=localhost + export DB_PORT=5432 + export MIGRATION_MANIFEST='/builds/milmove/mymove/migrations/app/migrations_manifest.txt' + export MIGRATION_PATH='file:///builds/milmove/mymove/migrations/app/schema;file:///builds/milmove/mymove/migrations/app/secure' + export EIA_KEY=db2522a43820268a41a802a16ae9fd26 + +.setup_devseed_env_variables: &setup_devseed_env_variables + - | + export DB_NAME=dev_db + export DB_NAME_DEV=dev_db + export ENVIRONMENT=development + export DOD_CA_PACKAGE=/builds/milmove/mymove/config/tls/milmove-cert-bundle.p7b + +.setup_server_env_variables: &setup_server_env_variables + - | + echo "make server_test_build for app" + export LOGIN_GOV_SECRET_KEY=$(echo $E2E_LOGIN_GOV_SECRET_KEY | base64 --decode) + export OKTA_CUST_CLIENT_ID=notrealkey + export OKTA_CUSTOMER_SECRET_KEY=notrealkey + export OKTA_OFFICE_SECRET_KEY=notrealkey1 + export OKTA_ADMIN_SECRET_KEY=notrealkey2 + export OKTA_TENANT_ORG_URL=test-milmove.okta.mil + export GOTEST_PARALLEL=8 + export DB_PORT_TEST=5433 + export DB_NAME=test_db + export DB_NAME_TEST=test_db + export DTOD_USE_MOCK='true' + export ENV=test + export ENVIRONMENT=test + export SERVER_REPORT=1 + export COVERAGE=1 + export SERVE_API_INTERNAL='true' + export OKTA_CUSTOMER_CLIENT_ID=1q2w3e4r5t6y7u8i9o + export OKTA_ADMIN_CLIENT_ID=AQ1SW2DE3FR4G5 + export OKTA_OFFICE_CLIENT_ID=9f9f9s8s90gig9 + export OKTA_API_KEY=notrealapikey8675309 + export OKTA_OFFICE_GROUP_ID=notrealgroupId + export OKTA_CUSTOMER_GROUP_ID=notrealcustomergroupId sast: stage: pre_checks + tags: + - $RUNNER_TAG include: - template: Jobs/SAST.gitlab-ci.yml - template: Jobs/Dependency-Scanning.gitlab-ci.yml @@ -151,6 +251,8 @@ include: anti_virus: stage: pre_checks + tags: + - $RUNNER_TAG image: milmove/clamav-ci # Custom image with ClamAV pre-installed script: - pwd @@ -176,13 +278,33 @@ anti_virus: rules: - *check_main +# Prep the public folder for frontend dependency serving +# This is needed for things like pdfjs-dist +prep_server_hosted_client_deps: + stage: pre_checks + tags: + - $RUNNER_TAG + image: $DOCKER_APP_IMAGE + before_script: + - *setup_milmove_env + script: | + echo "Running prep_server_hosted_client_deps" + ./scripts/fetch-react-file-viewer-from-yarn + after_script: + - *announce_failure + artifacts: + paths: + - /builds/milmove/mymove/public + pre_deps_golang: stage: pre_checks + tags: + - $RUNNER_TAG image: $DOCKER_APP_IMAGE before_script: - *setup_milmove_env variables: - KUBERNETES_CPU_REQUEST: "2" + KUBERNETES_CPU_REQUEST: "4" KUBERNETES_MEMORY_REQUEST: "4Gi" KUBERNETES_MEMORY_LIMIT: "4Gi" script: @@ -191,10 +313,12 @@ pre_deps_golang: - make bin/swagger after_script: - *announce_failure + cache: + - <<: *go_cache artifacts: paths: - - bin/ - - swagger/ + - /builds/milmove/mymove/bin/ + - /builds/milmove/mymove/swagger/ #TODO: Optimization potential # cache: # key: "$CI_COMMIT_REF_SLUG-go" @@ -205,23 +329,17 @@ pre_deps_golang: pre_deps_yarn: stage: pre_checks + tags: + - $RUNNER_TAG image: $DOCKER_APP_IMAGE - needs: - - pre_deps_golang before_script: - *setup_milmove_env script: - - pwd - - ls bin - - yarn config set "strict-ssl" false - - yarn install --frozen-lockfile --cache-folder ~/.cache/yarn - - scripts/check-generated-code yarn.lock - - echo "Temporarily skipping yarn installation and code checks." - artifacts: - paths: - - ~/.cache/yarn + - *install_yarn + cache: + - <<: *yarn_cache after_script: - - *announce_failure + - *announce_failure check_generated_code: stage: pre_checks @@ -241,6 +359,8 @@ check_generated_code: check_tls_certificate_dp3: stage: pre_checks + tags: + - $RUNNER_TAG image: $DOCKER_APP_IMAGE # Replace with your appropriate Docker image. before_script: - *setup_aws_vars_dp3 @@ -263,7 +383,9 @@ check_tls_certificate_dp3: check_tls_certificate_stg: stage: pre_checks - image: $DOCKER_APP_IMAGE # This can reB-18585-gitlab-pipeline-work unchanged, or you can use a lightweight image since no real work is done. + tags: + - $RUNNER_TAG + image: $DOCKER_APP_IMAGE before_script: - *setup_aws_vars_stg - *setup_tls_vars_stg @@ -275,6 +397,8 @@ check_tls_certificate_stg: check_tls_certificate_prd: stage: pre_checks + tags: + - $RUNNER_TAG image: $DOCKER_APP_IMAGE before_script: - *setup_tls_vars_prd @@ -285,28 +409,79 @@ check_tls_certificate_prd: after_script: - *announce_failure +build_storybook: + stage: build + tags: + - $RUNNER_TAG + image: $DOCKER_APP_IMAGE + variables: + KUBERNETES_CPU_REQUEST: "4" + KUBERNETES_MEMORY_REQUEST: "8Gi" + KUBERNETES_MEMORY_LIMIT: "8Gi" + needs: + - pre_deps_yarn + - anti_virus + cache: + - <<: *yarn_cache + before_script: + - *setup_milmove_env + - *install_yarn + script: + - yarn build-storybook + after_script: + - *announce_failure + artifacts: + paths: + - /builds/milmove/mymove/storybook-static + rules: + - *check_main + +deploy_storybook_dp3: + stage: deploy + tags: + - $RUNNER_TAG + image: $DOCKER_APP_IMAGE + needs: + - pre_deps_yarn + - build_storybook + before_script: + - *setup_milmove_env + script: + - echo "TODO Add steps" + - echo "deploy_storybook_dp3" + after_script: + - *announce_failure + artifacts: + paths: + - /builds/milmove/mymove/storybook-static + rules: + - *check_main + compile_app_client: stage: build + tags: + - $RUNNER_TAG image: $DOCKER_APP_IMAGE + cache: + - <<: *yarn_cache variables: - KUBERNETES_CPU_REQUEST: "2" + KUBERNETES_CPU_REQUEST: "6" KUBERNETES_MEMORY_REQUEST: "8Gi" KUBERNETES_MEMORY_LIMIT: "8Gi" - before_script: *setup_milmove_env + before_script: + - *setup_milmove_env + - *install_yarn needs: - pre_deps_yarn script: - make client_build - - echo "Skipping actual build steps." artifacts: paths: - - ~/.cache/yarn - /builds/milmove/mymove/bin - /builds/milmove/mymove/build - playwright - playwright.config.js - package.json - - yarn.lock - eslint-plugin-ato expire_in: 1 week after_script: @@ -315,15 +490,22 @@ compile_app_client: compile_app_server: stage: build + tags: + - $RUNNER_TAG image: $DOCKER_APP_IMAGE + cache: + - <<: *go_cache + - <<: *yarn_cache variables: - KUBERNETES_CPU_REQUEST: "2" - KUBERNETES_MEMORY_REQUEST: "4Gi" - KUBERNETES_MEMORY_LIMIT: "4Gi" + KUBERNETES_CPU_REQUEST: "6" + KUBERNETES_MEMORY_REQUEST: "6Gi" + KUBERNETES_MEMORY_LIMIT: "8Gi" needs: - pre_deps_golang - pre_deps_yarn - before_script: *setup_milmove_env + before_script: + - *setup_milmove_env + - *install_yarn script: - make -j 4 server_build build_tools - echo "Skipping server and tools compilation." @@ -347,8 +529,621 @@ compile_app_server: after_script: - *announce_failure + +##################################### +## Test stages various conditions ## +##################################### + +pre_test: + stage: test + tags: + - $RUNNER_TAG + image: $DOCKER_APP_IMAGE + cache: + - <<: *go_cache + - <<: *yarn_cache + needs: + - pre_deps_golang + - pre_deps_yarn + - check_tls_certificate_stg + - check_tls_certificate_prd + variables: + KUBERNETES_CPU_REQUEST: "4" + KUBERNETES_MEMORY_REQUEST: "6Gi" + KUBERNETES_MEMORY_LIMIT: "6Gi" + before_script: *setup_milmove_env + script: + - export GODEBUG=asyncpreemptoff=1 + - echo "Save Baseline Spectral Lint" + - | + [ -d ~/transcom/mymove/spectral ] && cp -r ~/transcom/mymove/spectral /tmp/spectral_baseline || echo "Skipping saving baseline" + - rm -rf ~/transcom/mymove/spectral + - *install_yarn + - echo "Run pre-commit tests without golangci-lint, eslint, or prettier" + - SKIP=golangci-lint,eslint,prettier,ato-go-linter,gomod,appcontext-linter pre-commit run --all-files + - | + echo "Run pre-commit tests with ato-go-linter only" + pre-commit run -v --all-files ato-go-linter + - | + echo "Run pre-commit tests with gomod only" + pre-commit run -v --all-files gomod,appcontext-linter + - | + echo "Run pre-commit tests with appcontext-linter only" + pre-commit run -v --all-files appcontext-linter + - echo "Run pre-commit tests with golangci-lint only" + - | + echo 'export GOLANGCI_LINT_CONCURRENCY=4' >> $BASH_ENV + echo 'export GOLANGCI_LINT_VERBOSE=-v' >> $BASH_ENV + source $BASH_ENV + mkdir -p tmp/test-results/pretest + pre-commit run -v --all-files golangci-lint | tee tmp/test-results/pretest/golangci-lint.out + - echo "Run prettier, eslint, danger checks" + - yarn prettier-ci + - yarn lint + - yarn danger ci --failOnErrors + - echo "Run spectral linter on all files" + - ./scripts/ensure-spectral-lint /tmp/spectral_baseline spectral + - ./scripts/pre-commit-go-mod || exit 0 + after_script: + - *announce_failure + rules: + - *check_server_ignore_branch + +server_test: + stage: test + tags: + - $DOCKER_RUNNER_TAG + image: $DOCKER_APP_IMAGE + needs: + - pre_deps_golang + before_script: + - *setup_milmove_env + - *setup_generic_app_env_variables + - *setup_server_env_variables + services: + - name: docker:dind + alias: docker + - name: $postgres + - name: $redis + variables: + DOCKER_HOST: "tcp://docker-backend.gitlab-runner.svc.cluster.local:2375" + DOCKER_TLS_CERTDIR: "" + APPLICATION: app + # 8 since this runs on xlarge with 8 CPUs + GOTEST_PARALLEL: 8 + DB_PASSWORD: mysecretpassword + DB_USER_LOW_PRIV: crud + DB_PASSWORD_LOW_PRIV: mysecretpassword + DB_USER: postgres + DB_HOST: localhost + DB_PORT_TEST: 5433 + DB_PORT: 5432 + DB_NAME: test_db + DB_NAME_TEST: test_db + DTOD_USE_MOCK: 'true' + MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/migrations_manifest.txt' + MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/schema;file:///builds/milmove/mymove/migrations/app/secure' + EIA_KEY: db2522a43820268a41a802a16ae9fd26 # dummy key generated with openssl rand -hex 16 + ENV: test + ENVIRONMENT: test + SERVER_REPORT: 1 + COVERAGE: 1 + SERVE_API_INTERNAL: 'true' + OKTA_CUSTOMER_CLIENT_ID: 1q2w3e4r5t6y7u8i9o + OKTA_ADMIN_CLIENT_ID: AQ1SW2DE3FR4G5 + OKTA_OFFICE_CLIENT_ID: 9f9f9s8s90gig9 + OKTA_API_KEY: notrealapikey8675309 + OKTA_OFFICE_GROUP_ID: notrealgroupId + OKTA_CUSTOMER_GROUP_ID: notrealcustomergroupId + script: + - psql --version + - for i in $(seq 1 5); do go mod download && break || s=$? && sleep 5; done; (exit $s) + - scripts/check-generated-code go.sum + - make bin/swagger + - echo "server test -- TODO Add steps need to potentially pass job id to file and persist" + - make -j 2 bin/milmove bin/gotestsum + - make server_test for app + # - go install gotest.tools/gotestsum@latest + # - go mod tidy + #- bin/gotestsum --junitfile server_test_report.xml --format server_test + artifacts: + paths: + - /builds/milmove/mymove/bin/gotestsum + - /builds/milmove/mymove/tmp/test-results + when: always + reports: + junit: /builds/milmove/mymove/tmp/test-results/gotest/app/go-test-report.xml + after_script: + - *announce_failure + rules: + - *check_server_ignore_branch + +server_test_coverage: + stage: test + tags: + - $RUNNER_TAG + image: $DOCKER_APP_IMAGE + needs: + - pre_deps_golang + - server_test + before_script: *setup_milmove_env + script: + - echo "TODO understand recording stats and PR interaction" + - echo "server test coverage" + - | + echo "Ensure Test Coverage Increasing" + ./scripts/ensure-go-test-coverage \ + tmp/baseline-go-coverage/go-coverage.txt \ + tmp/test-results/gotest/app/go-coverage.txt + after_script: + - *announce_failure + rules: + - *check_server_ignore_branch + ###may need to rethink the logic and intent of this they save per the following and do some PR interaction + # only save the cache on default branch builds because we only want to + # change the baseline of test results on main builds + # + # Save the new baseline regardless of if the coverage succeeds + # or fails as a merge to main means we have a new baseline. We + # will use other means to measure if our coverage is increasing + +client_test: + stage: test + tags: + - $RUNNER_TAG + image: $DOCKER_APP_IMAGE + variables: + KUBERNETES_CPU_REQUEST: "4" + KUBERNETES_MEMORY_REQUEST: "8Gi" + KUBERNETES_MEMORY_LIMIT: "8Gi" + needs: + - pre_deps_yarn + cache: + - <<: *yarn_cache + before_script: + - *setup_milmove_env + - *install_yarn + coverage: /All files[^|]*\|[^|]*\s+([\d\.]+)/ + dependencies: + - pre_deps_yarn + script: + - echo "client test coverage" + - JEST_JUNIT_OUTPUT_DIR=jest-junit-reports yarn test:coverage -results=false >> $CI_PROJECT_DIR/coverage.output + artifacts: + when: always + reports: + junit: + - jest-junit-reports/junit.xml + paths: + - /builds/milmove/mymove/coverage + - /builds/milmove/mymove/jest-junit-reports + after_script: + - *announce_failure + rules: + - *check_client_ignore_branch + +client_test_coverage: + stage: test + tags: + - $RUNNER_TAG + image: $DOCKER_APP_IMAGE + needs: + - pre_deps_yarn + - client_test + before_script: *setup_milmove_env + # TODO: need to add cache for max coverage increase similar to this + # https://stackoverflow.com/questions/54542922/force-coverage-increase-in-gitlab-prs + script: + - echo "TODO understand recording stats and PR interaction" + - | + echo "Ensure Test Coverage Increasing" + ./scripts/ensure-js-test-coverage \ + tmp/baseline-jest-coverage/clover.xml \ + coverage/clover.xml + after_script: + - *announce_failure + rules: + - *check_client_ignore_branch + +integration_test_devseed: + stage: test + tags: + - $DOCKER_RUNNER_TAG + image: $DOCKER_APP_IMAGE + services: + - name: docker:dind + alias: docker + - name: $postgres + - name: $redis + variables: + DOCKER_HOST: "tcp://docker-backend.gitlab-runner.svc.cluster.local:2375" + DOCKER_TLS_CERTDIR: "" + APPLICATION: app + DB_PASSWORD: mysecretpassword + DB_USER_LOW_PRIV: crud + DB_PASSWORD_LOW_PRIV: mysecretpassword + DB_USER: postgres + DB_HOST: localhost + DB_PORT: 5432 + DB_NAME: dev_db + DB_NAME_DEV: dev_db + MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/migrations_manifest.txt' + MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/schema;file:///builds/milmove/mymove/migrations/app/secure' + EIA_KEY: db2522a43820268a41a802a16ae9fd26 # dummy key generated with openssl rand -hex 16 + ENVIRONMENT: development + DOD_CA_PACKAGE: /builds/milmove/mymove/config/tls/milmove-cert-bundle.p7b + POSTGRES_PASSWORD: mysecretpassword + POSTGRES_DB: test_db + needs: + - pre_deps_golang + - prep_server_hosted_client_deps + before_script: + - *setup_milmove_env + - *setup_generic_app_env_variables + - *setup_devseed_env_variables + script: + - echo "integration_test_devseed" + - | + export MOVE_MIL_DOD_CA_CERT=$(cat config/tls/devlocal-ca.pem) + export MOVE_MIL_DOD_TLS_CERT=$(cat config/tls/devlocal-https.pem) + export MOVE_MIL_DOD_TLS_KEY=$(cat config/tls/devlocal-https.key) + - make db_dev_fresh + after_script: + - *announce_failure + rules: + - *check_integration_ignore_branch + +integration_tests: + stage: test + tags: + - $RUNNER_TAG + image: $DOCKER_APP_IMAGE + needs: + - pre_deps_yarn + - pre_deps_golang + - compile_app_client + - compile_app_server + - integration_test_my + - integration_test_office + - integration_test_admin + - integration_test_devseed + before_script: *setup_milmove_env + script: + - echo "TODO Add steps" + - echo "integration_tests" + after_script: + - *announce_failure + rules: + - *check_integration_ignore_branch + +integration_test_mtls: + stage: test + tags: + - $RUNNER_TAG + image: $DOCKER_APP_IMAGE + needs: + - pre_deps_yarn + - compile_app_server + before_script: *setup_milmove_env + script: + - echo "TODO Add steps" + - echo "integration_test_mtls" + after_script: + - *announce_failure + rules: + - *check_integration_mtls_ignore_branch + +integration_test_admin: + stage: test + tags: + - $RUNNER_TAG + image: $DOCKER_APP_IMAGE + needs: + - pre_deps_yarn + - pre_deps_golang + - compile_app_client + - compile_app_server + before_script: *setup_milmove_env + script: + - echo "TODO Add steps" + - echo "integration_test_admin" + after_script: + - *announce_failure + rules: + - *check_integration_ignore_branch + +integration_test_my: + stage: test + tags: + - $RUNNER_TAG + image: $DOCKER_APP_IMAGE + needs: + - pre_deps_yarn + - pre_deps_golang + - compile_app_client + - compile_app_server + before_script: *setup_milmove_env + script: + - echo "TODO Add steps" + - echo "integration_test_my" + after_script: + - *announce_failure + rules: + - *check_integration_ignore_branch + +integration_test_office: + stage: test + tags: + - $RUNNER_TAG + image: $DOCKER_APP_IMAGE + needs: + - pre_deps_yarn + - pre_deps_golang + - compile_app_client + - compile_app_server + before_script: *setup_milmove_env + script: + - echo "TODO Add steps" + - echo "integration_test_office" + after_script: + - *announce_failure + rules: + - *check_integration_ignore_branch + + +############################################################### +## DP3 Env push and deploy stages all off of setting dp3 env ## +############################################################### +build_push_app_dp3: + stage: push + tags: + - $RUNNER_TAG + environment: DP3_ENV + image: + name: gcr.io/kaniko-project/executor:v1.14.0-debug + entrypoint: [""] + needs: + - compile_app_client + - compile_app_server + before_script: + - *setup_aws_vars_dp3 + - *setup_release_dp3 + - *kaniko_before_setup + script: + - echo "Building and Pushing app Docker image..." + - /kaniko/executor --context "${CI_PROJECT_DIR}/" --dockerfile "${CI_PROJECT_DIR}/${APP_DOCKER_FILE}" --destination "${ECR_REPOSITORY_URI}/app:$CI_COMMIT_SHORT_SHA" + after_script: + - *announce_failure + rules: + - *check_dp3 + +build_push_migrations_dp3: + stage: push + tags: + - $RUNNER_TAG + environment: DP3_ENV + image: + name: gcr.io/kaniko-project/executor:v1.14.0-debug + entrypoint: [""] + needs: + - compile_app_server + - compile_app_client + before_script: + - *setup_aws_vars_dp3 + - *setup_release_dp3 + - *kaniko_before_setup + script: + - echo "Building and Pushing migrations Docker image..." + - /kaniko/executor --context "${CI_PROJECT_DIR}/" --dockerfile "${CI_PROJECT_DIR}/Dockerfile.migrations" --destination "${ECR_REPOSITORY_URI}/app-migrations:$CI_COMMIT_SHORT_SHA" + after_script: + - *announce_failure + rules: + - *check_dp3 + +build_push_tasks_dp3: + stage: push + tags: + - $RUNNER_TAG + environment: DP3_ENV + image: + name: gcr.io/kaniko-project/executor:v1.14.0-debug + entrypoint: [""] + needs: + - compile_app_server + - compile_app_client + before_script: + - *setup_aws_vars_dp3 + - *setup_release_dp3 + - *kaniko_before_setup + script: + - echo "Building tasks Docker image..." + - /kaniko/executor --context "${CI_PROJECT_DIR}/" --dockerfile "${CI_PROJECT_DIR}/${TASK_DOCKER_FILE}" --destination "${ECR_REPOSITORY_URI}/app-tasks:$CI_COMMIT_SHORT_SHA" + after_script: + - *announce_failure + rules: + - *check_dp3 + +push_otel_collector_image_dp3: + stage: push + tags: + - $RUNNER_TAG + environment: DP3_ENV + image: + name: $DOCKER_BASE_IMAGE + entrypoint: [""] + needs: + - compile_app_server + - compile_app_client + script: + - echo "Logging in to Amazon ECR with Crane..." + - aws ecr get-login-password --region us-gov-west-1 | crane auth login ${ECR_REPOSITORY_URI} -u AWS --password-stdin + + - echo "Pulling the AWS OTel Collector image from the public registry with Crane..." + - crane pull --insecure public.ecr.aws/aws-observability/aws-otel-collector:v0.31.0 image.tar + + - echo "Pushing the image to our private ECR using Crane..." + - crane push --insecure image.tar ${ECR_REPOSITORY_URI}/otel-collector:${CI_COMMIT_SHORT_SHA} + + - echo "Cleaning up the temporary image file..." + - rm image.tar + allow_failure: false + after_script: + - *announce_failure + rules: + - *check_dp3 + +deploy_migrations_dp3: + stage: deploy + tags: + - $RUNNER_TAG + environment: DP3_ENV + image: + name: $DOCKER_APP_IMAGE + entrypoint: [""] + needs: + - build_push_migrations_dp3 + - compile_app_server + - compile_app_client + before_script: + - *setup_aws_vars_dp3 + - *setup_release_dp3 + script: + # Step 1: Get the Digest + - echo "Getting Digest from AWS" + - export ECR_DIGEST=$(aws ecr describe-images --repository-name app-migrations --image-ids imageTag=$CI_COMMIT_SHORT_SHA --query 'imageDetails[0].imageDigest' --output text) + # Step 2: Ensure exclusive execution and Snapshot + - echo "Snapshotting database" + - ./scripts/rds-snapshot-app-db "$APP_ENVIRONMENT" + # Step 3: Run migrations + - echo "Running migrations" + - ./scripts/ecs-run-app-migrations-container "${ECR_REPOSITORY_URI}/app-migrations@${ECR_DIGEST}" "${APP_ENVIRONMENT}" + after_script: + - *announce_failure + rules: + - *check_dp3 + +deploy_tasks_dp3: + stage: deploy + tags: + - $RUNNER_TAG + image: + name: $DOCKER_APP_IMAGE + entrypoint: [""] + needs: + - build_push_tasks_dp3 + - compile_app_server + - compile_app_client + before_script: + - *setup_release_dp3 + script: + - echo "Getting Digest from AWS" + - export ECR_DIGEST=$(aws ecr describe-images --repository-name app-tasks --image-ids imageTag=$CI_COMMIT_SHORT_SHA --query 'imageDetails[0].imageDigest' --output text) + - echo "Deploying GHC fuel price data task service" + - ./scripts/ecs-deploy-task-container save-ghc-fuel-price-data "${ECR_REPOSITORY_URI}/app-tasks@${ECR_DIGEST}" "${APP_ENVIRONMENT}" + - echo "Deploying payment reminder email task service" + - ./scripts/ecs-deploy-task-container send-payment-reminder "${ECR_REPOSITORY_URI}/app-tasks@${ECR_DIGEST}" "${APP_ENVIRONMENT}" + after_script: + - *announce_failure + rules: + - *check_dp3 + +deploy_app_client_tls_dp3: + stage: deploy + tags: + - $RUNNER_TAG + environment: DP3_ENV + image: + name: $DOCKER_APP_IMAGE + entrypoint: [""] + needs: + - deploy_migrations_dp3 + - push_otel_collector_image_dp3 + - compile_app_server + - compile_app_client + variables: + OPEN_TELEMETRY_SIDECAR: "true" + HEALTH_CHECK: "true" + before_script: + - *setup_aws_vars_dp3 + - *setup_release_dp3 + script: + # - echo "Comparing against deployed commit" + # - ./scripts/compare-deployed-commit "" $CI_COMMIT_SHA ${TLS_KEY} ${TLS_CERT} ${TLS_CA} + - echo "Getting Digest from AWS" + - export ECR_DIGEST=$(aws ecr describe-images --repository-name app --image-ids imageTag=$CI_COMMIT_SHORT_SHA --query 'imageDetails[0].imageDigest' --output text) + - echo "Getting otel collector Digest from AWS" + - export OTEL_ECR_DIGEST=$(aws ecr describe-images --repository-name otel-collector --image-ids imageTag=$CI_COMMIT_SHORT_SHA --query 'imageDetails[0].imageDigest' --output text) + - export OTEL_COLLECTOR_IMAGE="${ECR_REPOSITORY_URI}/otel-collector@${OTEL_ECR_DIGEST}" + - echo "Deploying app-client-tls service" + - ./scripts/ecs-deploy-service-container app-client-tls "${ECR_REPOSITORY_URI}/app@${ECR_DIGEST}" "${APP_ENVIRONMENT}" "/bin/milmove serve" + - echo "Running Health Check" + # - bin/health-checker --schemes https --hosts api.demo.dp3.us --key ${TLS_KEY} --cert ${TLS_CERT} --ca ${TLS_CA} --tries 10 --backoff 3 --log-level info --timeout 5m + # - echo "Running TLS Check" + # - bin/tls-checker --schemes https --hosts api.demo.dp3.us --key ${TLS_KEY} --cert ${TLS_CERT} --ca ${TLS_CA} --log-level info --timeout 15m + # - echo "Checking deployed commits" + # - ./scripts/check-deployed-commit "api.demo.dp3.us" "$CI_COMMIT_SHA" ${TLS_KEY} ${TLS_CERT} ${TLS_CA} + after_script: + - *announce_failure + rules: + - *check_dp3 + +deploy_app_dp3: + stage: deploy + tags: + - $RUNNER_TAG + environment: DP3_ENV + image: + name: $DOCKER_APP_IMAGE + entrypoint: [""] + needs: + - build_push_app_dp3 + - deploy_migrations_dp3 + - compile_app_server + - compile_app_client + variables: + OPEN_TELEMETRY_SIDECAR: "true" + HEALTH_CHECK: "true" + before_script: + - *setup_aws_vars_dp3 + - *setup_release_dp3 + script: + - echo "Comparing against deployed commit" + # - ./scripts/compare-deployed-commit "" "$CI_COMMIT_SHA" "$TLS_KEY" "$TLS_CERT" "$TLS_CA" + - echo "Creating .go-version file if not already present" + - | + if [ -f ".go-version" ]; then + echo ".go-version already exists, no need to re-create" + else + GO_VERSION=$(awk '/golang/ { print $2 }' .tool-versions) + echo "Creating .go-version using version ${GO_VERSION}" + echo $GO_VERSION > .go-version + fi + - echo "Getting Digest from AWS" + - export ECR_DIGEST=$(aws ecr describe-images --repository-name app --image-ids imageTag=$CI_COMMIT_SHORT_SHA --query 'imageDetails[0].imageDigest' --output text) + - echo "Getting otel collector digest from AWS" + - export OTEL_ECR_DIGEST=$(aws ecr describe-images --repository-name otel-collector --image-ids imageTag=$CI_COMMIT_SHORT_SHA --query 'imageDetails[0].imageDigest' --output text) + - export OTEL_COLLECTOR_IMAGE="${ECR_REPOSITORY_URI}/otel-collector@${OTEL_ECR_DIGEST}" + - echo "Deploying app service" + - ./scripts/ecs-deploy-service-container app "${ECR_REPOSITORY_URI}/app@${ECR_DIGEST}" "${APP_ENVIRONMENT}" "/bin/milmove serve" + - echo "Running Health Check" + # - bin/health-checker --schemes https --hosts my.demo.dp3.us,office.demo.dp3.us,admin.demo.dp3.us --tries 10 --backoff 3 --log-level info --timeout 5m + # - echo "Running TLS Check" + # - bin/tls-checker --schemes https --hosts my.demo.dp3.us,office.demo.dp3.us,admin.demo.dp3.us --log-level info --timeout 15m + # - echo "Checking deployed commits" + - ./scripts/check-deployed-commit "my.demo.dp3.us,office.demo.dp3.us,admin.demo.dp3.us" "$CI_COMMIT_SHA" + after_script: + - *announce_failure + rules: + - *check_dp3 + +######################################################## +## STG push and deploy stages all off of main only ## +######################################################## + build_push_app_stg: stage: push + tags: + - $RUNNER_TAG environment: stg image: name: gcr.io/kaniko-project/executor:v1.14.0-debug @@ -370,6 +1165,8 @@ build_push_app_stg: build_push_migrations_stg: stage: push + tags: + - $RUNNER_TAG environment: stg image: name: gcr.io/kaniko-project/executor:v1.14.0-debug @@ -391,6 +1188,8 @@ build_push_migrations_stg: build_push_tasks_stg: stage: push + tags: + - $RUNNER_TAG environment: stg image: name: gcr.io/kaniko-project/executor:v1.14.0-debug @@ -412,6 +1211,8 @@ build_push_tasks_stg: push_otel_collector_image_stg: stage: push + tags: + - $RUNNER_TAG environment: stg image: name: $DOCKER_BASE_IMAGE @@ -442,6 +1243,8 @@ push_otel_collector_image_stg: deploy_migrations_stg: stage: deploy + tags: + - $RUNNER_TAG environment: stg image: name: $DOCKER_APP_IMAGE @@ -470,6 +1273,8 @@ deploy_migrations_stg: deploy_tasks_stg: stage: deploy + tags: + - $RUNNER_TAG environment: stg image: name: $DOCKER_APP_IMAGE @@ -533,6 +1338,8 @@ deploy_app_client_tls_stg: deploy_app_stg: stage: deploy + tags: + - $RUNNER_TAG environment: stg image: name: $DOCKER_APP_IMAGE @@ -578,12 +1385,14 @@ deploy_app_stg: rules: - *check_main +############################################################################## +## PROD push and deploy stages all dependent on prod_approval manual stage ## +############################################################################## prod_approval: stage: prod_approval + tags: + - $RUNNER_TAG environment: prd_approval - image: - name: gcr.io/kaniko-project/executor:v1.14.0-debug - entrypoint: [""] needs: - compile_app_client - compile_app_server @@ -620,6 +1429,8 @@ build_push_app_prd: build_push_migrations_prd: stage: push_prd + tags: + - $RUNNER_TAG environment: prd image: name: gcr.io/kaniko-project/executor:v1.14.0-debug @@ -638,11 +1449,13 @@ build_push_migrations_prd: after_script: - *announce_failure rules: - - if: '$CI_COMMIT_BRANCH == "main"' + - *check_main build_push_tasks_prd: stage: push_prd environment: prd + tags: + - $RUNNER_TAG image: name: gcr.io/kaniko-project/executor:v1.14.0-debug entrypoint: [""] @@ -664,6 +1477,8 @@ build_push_tasks_prd: push_otel_collector_image_prd: stage: push_prd + tags: + - $RUNNER_TAG environment: prd image: name: $DOCKER_BASE_IMAGE @@ -691,11 +1506,13 @@ push_otel_collector_image_prd: after_script: - *announce_failure rules: - - if: '$CI_COMMIT_BRANCH == "main"' + - *check_main deploy_migrations_prd: stage: deploy_prd environment: prd + tags: + - $RUNNER_TAG image: name: $DOCKER_APP_IMAGE entrypoint: [""] @@ -724,6 +1541,8 @@ deploy_migrations_prd: deploy_tasks_prd: stage: deploy_prd environment: prd + tags: + - $RUNNER_TAG image: name: $DOCKER_APP_IMAGE entrypoint: [""] @@ -749,6 +1568,8 @@ deploy_tasks_prd: deploy_app_client_tls_prd: stage: deploy_prd environment: prd + tags: + - $RUNNER_TAG image: name: $DOCKER_APP_IMAGE entrypoint: [""] @@ -786,6 +1607,8 @@ deploy_app_client_tls_prd: deploy_app_prd: stage: deploy_prd + tags: + - $RUNNER_TAG environment: prd image: name: $DOCKER_APP_IMAGE @@ -829,231 +1652,4 @@ deploy_app_prd: after_script: - *announce_failure rules: - - *check_main - - -build_push_app_dp3: - stage: push - environment: DP3_ENV - image: - name: gcr.io/kaniko-project/executor:v1.14.0-debug - entrypoint: [""] - needs: - - compile_app_client - - compile_app_server - before_script: - - *setup_aws_vars_dp3 - - *setup_release_dp3 - - *kaniko_before_setup - script: - - echo "Building and Pushing app Docker image..." - - /kaniko/executor --context "${CI_PROJECT_DIR}/" --dockerfile "${CI_PROJECT_DIR}/${APP_DOCKER_FILE}" --destination "${ECR_REPOSITORY_URI}/app:$CI_COMMIT_SHORT_SHA" - after_script: - - *announce_failure - rules: - - *check_dp3 - -build_push_migrations_dp3: - stage: push - environment: DP3_ENV - image: - name: gcr.io/kaniko-project/executor:v1.14.0-debug - entrypoint: [""] - needs: - - compile_app_server - - compile_app_client - before_script: - - *setup_aws_vars_dp3 - - *setup_release_dp3 - - *kaniko_before_setup - script: - - echo "Building and Pushing migrations Docker image..." - - /kaniko/executor --context "${CI_PROJECT_DIR}/" --dockerfile "${CI_PROJECT_DIR}/Dockerfile.migrations" --destination "${ECR_REPOSITORY_URI}/app-migrations:$CI_COMMIT_SHORT_SHA" - after_script: - - *announce_failure - rules: - - *check_dp3 - -build_push_tasks_dp3: - stage: push - environment: DP3_ENV - image: - name: gcr.io/kaniko-project/executor:v1.14.0-debug - entrypoint: [""] - needs: - - compile_app_server - - compile_app_client - before_script: - - *setup_aws_vars_dp3 - - *setup_release_dp3 - - *kaniko_before_setup - script: - - echo "Building tasks Docker image..." - - /kaniko/executor --context "${CI_PROJECT_DIR}/" --dockerfile "${CI_PROJECT_DIR}/${TASK_DOCKER_FILE}" --destination "${ECR_REPOSITORY_URI}/app-tasks:$CI_COMMIT_SHORT_SHA" - after_script: - - *announce_failure - rules: - - *check_dp3 - -push_otel_collector_image_dp3: - stage: push - environment: DP3_ENV - image: - name: $DOCKER_BASE_IMAGE - entrypoint: [""] - needs: - - compile_app_server - - compile_app_client - script: - - echo "Logging in to Amazon ECR with Crane..." - - aws ecr get-login-password --region us-gov-west-1 | crane auth login ${ECR_REPOSITORY_URI} -u AWS --password-stdin - - - echo "Pulling the AWS OTel Collector image from the public registry with Crane..." - - crane pull --insecure public.ecr.aws/aws-observability/aws-otel-collector:v0.31.0 image.tar - - - echo "Pushing the image to our private ECR using Crane..." - - crane push --insecure image.tar ${ECR_REPOSITORY_URI}/otel-collector:${CI_COMMIT_SHORT_SHA} - - - echo "Cleaning up the temporary image file..." - - rm image.tar - allow_failure: false - after_script: - - *announce_failure - rules: - - *check_dp3 - -deploy_migrations_dp3: - stage: deploy - environment: DP3_ENV - image: - name: $DOCKER_APP_IMAGE - entrypoint: [""] - needs: - - build_push_migrations_dp3 - - compile_app_server - - compile_app_client - before_script: - - *setup_aws_vars_dp3 - - *setup_release_dp3 - script: - # Step 1: Get the Digest - - echo "Getting Digest from AWS" - - export ECR_DIGEST=$(aws ecr describe-images --repository-name app-migrations --image-ids imageTag=$CI_COMMIT_SHORT_SHA --query 'imageDetails[0].imageDigest' --output text) - # Step 2: Ensure exclusive execution and Snapshot - - echo "Snapshotting database" - - ./scripts/rds-snapshot-app-db "$APP_ENVIRONMENT" - # Step 3: Run migrations - - echo "Running migrations" - - ./scripts/ecs-run-app-migrations-container "${ECR_REPOSITORY_URI}/app-migrations@${ECR_DIGEST}" "${APP_ENVIRONMENT}" - after_script: - - *announce_failure - rules: - - *check_dp3 - -deploy_tasks_dp3: - stage: deploy - image: - name: $DOCKER_APP_IMAGE - entrypoint: [""] - needs: - - build_push_tasks_dp3 - - compile_app_server - - compile_app_client - before_script: - - *setup_release_dp3 - script: - - echo "Getting Digest from AWS" - - export ECR_DIGEST=$(aws ecr describe-images --repository-name app-tasks --image-ids imageTag=$CI_COMMIT_SHORT_SHA --query 'imageDetails[0].imageDigest' --output text) - - echo "Deploying GHC fuel price data task service" - - ./scripts/ecs-deploy-task-container save-ghc-fuel-price-data "${ECR_REPOSITORY_URI}/app-tasks@${ECR_DIGEST}" "${APP_ENVIRONMENT}" - - echo "Deploying payment reminder email task service" - - ./scripts/ecs-deploy-task-container send-payment-reminder "${ECR_REPOSITORY_URI}/app-tasks@${ECR_DIGEST}" "${APP_ENVIRONMENT}" - after_script: - - *announce_failure - rules: - - *check_dp3 - -deploy_app_client_tls_dp3: - stage: deploy - environment: DP3_ENV - image: - name: $DOCKER_APP_IMAGE - entrypoint: [""] - needs: - - deploy_migrations_dp3 - - push_otel_collector_image_dp3 - - compile_app_server - - compile_app_client - variables: - OPEN_TELEMETRY_SIDECAR: "true" - HEALTH_CHECK: "true" - before_script: - - *setup_aws_vars_dp3 - - *setup_release_dp3 - script: - # - echo "Comparing against deployed commit" - # - ./scripts/compare-deployed-commit "" $CI_COMMIT_SHA ${TLS_KEY} ${TLS_CERT} ${TLS_CA} - - echo "Getting Digest from AWS" - - export ECR_DIGEST=$(aws ecr describe-images --repository-name app --image-ids imageTag=$CI_COMMIT_SHORT_SHA --query 'imageDetails[0].imageDigest' --output text) - - echo "Getting otel collector Digest from AWS" - - export OTEL_ECR_DIGEST=$(aws ecr describe-images --repository-name otel-collector --image-ids imageTag=$CI_COMMIT_SHORT_SHA --query 'imageDetails[0].imageDigest' --output text) - - export OTEL_COLLECTOR_IMAGE="${ECR_REPOSITORY_URI}/otel-collector@${OTEL_ECR_DIGEST}" - - echo "Deploying app-client-tls service" - - ./scripts/ecs-deploy-service-container app-client-tls "${ECR_REPOSITORY_URI}/app@${ECR_DIGEST}" "${APP_ENVIRONMENT}" "/bin/milmove serve" - - echo "Running Health Check" - # - bin/health-checker --schemes https --hosts api.demo.dp3.us --key ${TLS_KEY} --cert ${TLS_CERT} --ca ${TLS_CA} --tries 10 --backoff 3 --log-level info --timeout 5m - # - echo "Running TLS Check" - # - bin/tls-checker --schemes https --hosts api.demo.dp3.us --key ${TLS_KEY} --cert ${TLS_CERT} --ca ${TLS_CA} --log-level info --timeout 15m - # - echo "Checking deployed commits" - # - ./scripts/check-deployed-commit "api.demo.dp3.us" "$CI_COMMIT_SHA" ${TLS_KEY} ${TLS_CERT} ${TLS_CA} - after_script: - - *announce_failure - rules: - - *check_dp3 - -deploy_app_dp3: - stage: deploy - environment: DP3_ENV - image: - name: $DOCKER_APP_IMAGE - entrypoint: [""] - needs: - - build_push_app_dp3 - - deploy_migrations_dp3 - - compile_app_server - - compile_app_client - variables: - OPEN_TELEMETRY_SIDECAR: "true" - HEALTH_CHECK: "true" - before_script: - - *setup_aws_vars_dp3 - - *setup_release_dp3 - script: - - echo "Comparing against deployed commit" - # - ./scripts/compare-deployed-commit "" "$CI_COMMIT_SHA" "$TLS_KEY" "$TLS_CERT" "$TLS_CA" - - echo "Creating .go-version file if not already present" - - | - if [ -f ".go-version" ]; then - echo ".go-version already exists, no need to re-create" - else - GO_VERSION=$(awk '/golang/ { print $2 }' .tool-versions) - echo "Creating .go-version using version ${GO_VERSION}" - echo $GO_VERSION > .go-version - fi - - echo "Getting Digest from AWS" - - export ECR_DIGEST=$(aws ecr describe-images --repository-name app --image-ids imageTag=$CI_COMMIT_SHORT_SHA --query 'imageDetails[0].imageDigest' --output text) - - echo "Getting otel collector digest from AWS" - - export OTEL_ECR_DIGEST=$(aws ecr describe-images --repository-name otel-collector --image-ids imageTag=$CI_COMMIT_SHORT_SHA --query 'imageDetails[0].imageDigest' --output text) - - export OTEL_COLLECTOR_IMAGE="${ECR_REPOSITORY_URI}/otel-collector@${OTEL_ECR_DIGEST}" - - echo "Deploying app service" - - ./scripts/ecs-deploy-service-container app "${ECR_REPOSITORY_URI}/app@${ECR_DIGEST}" "${APP_ENVIRONMENT}" "/bin/milmove serve" - - echo "Running Health Check" - # - bin/health-checker --schemes https --hosts my.demo.dp3.us,office.demo.dp3.us,admin.demo.dp3.us --tries 10 --backoff 3 --log-level info --timeout 5m - # - echo "Running TLS Check" - # - bin/tls-checker --schemes https --hosts my.demo.dp3.us,office.demo.dp3.us,admin.demo.dp3.us --log-level info --timeout 15m - # - echo "Checking deployed commits" - - ./scripts/check-deployed-commit "my.demo.dp3.us,office.demo.dp3.us,admin.demo.dp3.us" "$CI_COMMIT_SHA" - after_script: - - *announce_failure - rules: - - *check_dp3 \ No newline at end of file + - *check_main \ No newline at end of file diff --git a/config/tls/milmove-cert-bundle.p7b b/config/tls/milmove-cert-bundle.p7b index 9a53bf6c2e9..85eb6a72d7f 100644 Binary files a/config/tls/milmove-cert-bundle.p7b and b/config/tls/milmove-cert-bundle.p7b differ diff --git a/go.mod b/go.mod index e528f684f9d..264e97343fa 100644 --- a/go.mod +++ b/go.mod @@ -95,10 +95,10 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.28.0 go.opentelemetry.io/otel/trace v1.31.0 go.uber.org/zap v1.27.0 - golang.org/x/crypto v0.27.0 + golang.org/x/crypto v0.31.0 golang.org/x/net v0.29.0 golang.org/x/oauth2 v0.23.0 - golang.org/x/text v0.18.0 + golang.org/x/text v0.21.0 golang.org/x/tools v0.24.0 google.golang.org/grpc v1.68.0 gopkg.in/dnaeon/go-vcr.v3 v3.2.0 @@ -261,10 +261,10 @@ require ( golang.org/x/exp v0.0.0-20230905200255-921286631fa9 golang.org/x/image v0.18.0 // indirect golang.org/x/mod v0.20.0 // indirect - golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.25.0 // indirect - golang.org/x/term v0.24.0 // indirect - google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/term v0.27.0 // indirect + google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect google.golang.org/protobuf v1.34.2 // indirect diff --git a/go.sum b/go.sum index edfdd1c49f0..8a725675df2 100644 --- a/go.sum +++ b/go.sum @@ -723,8 +723,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= -golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= @@ -790,8 +790,8 @@ golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= -golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -831,8 +831,8 @@ golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= -golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -849,8 +849,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= -golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= -golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -863,8 +863,8 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= -golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= diff --git a/migrations/app/migrations_manifest.txt b/migrations/app/migrations_manifest.txt index 62a07c54465..7e56a0912ee 100644 --- a/migrations/app/migrations_manifest.txt +++ b/migrations/app/migrations_manifest.txt @@ -1053,6 +1053,7 @@ 20241203024453_add_ppm_max_incentive_column.up.sql 20241204155919_update_ordering_proc.up.sql 20241204210208_retroactive_update_of_ppm_max_and_estimated_incentives_prd.up.sql +20241209121924_entitlements_refactor.up.sql 20241210143143_redefine_mto_shipment_audit_table.up.sql 20241216170325_update_nts_enum_name.up.sql 20241216190428_update_get_zip_code_function_and_update_pricing_proc.up.sql @@ -1067,8 +1068,10 @@ 20241226173330_add_intl_param_values_to_service_params_table.up.sql 20241227153723_remove_empty_string_emplid_values.up.sql 20241227202424_insert_transportation_offices_camp_pendelton.up.sql +20241230150644_student_travel_weight_limit_param.up.sql 20241230190638_remove_AK_zips_from_zip3.up.sql 20241230190647_add_missing_AK_zips_to_zip3_distances.up.sql 20250103130619_revert_data_change_for_gbloc_for_ak.up.sql 20250103180420_update_pricing_proc_to_use_local_price_variable.up.sql 20250110153428_add_shipment_address_updates_to_move_history.up.sql +20250110214012_homesafeconnect_cert.up.sql diff --git a/migrations/app/schema/20241209121924_entitlements_refactor.up.sql b/migrations/app/schema/20241209121924_entitlements_refactor.up.sql new file mode 100644 index 00000000000..a8438074776 --- /dev/null +++ b/migrations/app/schema/20241209121924_entitlements_refactor.up.sql @@ -0,0 +1,815 @@ +-- See https://dp3.atlassian.net/wiki/spaces/MT/pages/2738716677/HHG+and+UB+Entitlements +-- Prep entitlements table for holding weight restricted +ALTER TABLE entitlements +ADD COLUMN IF NOT EXISTS weight_restriction int; +-- Create pay grades table to get our static entitlements.go file to be db based +CREATE TABLE IF NOT EXISTS pay_grades ( + id uuid PRIMARY KEY NOT NULL, + grade text NOT NULL UNIQUE, + grade_description text, + created_at timestamp NOT NULL DEFAULT NOW(), + updated_at timestamp NOT NULL DEFAULT NOW() +); +-- Create household goods allowances table +CREATE TABLE IF NOT EXISTS hhg_allowances ( + id uuid PRIMARY KEY NOT NULL, + pay_grade_id uuid NOT NULL UNIQUE REFERENCES pay_grades(id) ON DELETE CASCADE, + total_weight_self int NOT NULL, + total_weight_self_plus_dependents int NOT NULL, + pro_gear_weight int NOT NULL, + pro_gear_weight_spouse int NOT NULL, + created_at timestamp NOT NULL DEFAULT NOW(), + updated_at timestamp NOT NULL DEFAULT NOW() +); +-- Insert Max HHG allowance app value +-- camel case to match the existing standaloneCrateCap parameter +INSERT INTO application_parameters (id, parameter_name, parameter_value) +VALUES ( + 'D246186B-E93B-4716-B82C-6A38EA5EAB8C', + 'maxHhgAllowance', + '18000' + ); +-- Insert pay_grades and hhg_allowances +-- ACADEMY_CADET +INSERT INTO pay_grades (id, grade, grade_description) +VALUES ( + '8D8C82EA-EA8F-4D7F-9D84-8D186AB7A7C0', + 'ACADEMY_CADET', + 'Academy Cadet' + ); +INSERT INTO hhg_allowances ( + id, + pay_grade_id, + total_weight_self, + total_weight_self_plus_dependents, + pro_gear_weight, + pro_gear_weight_spouse + ) +VALUES ( + '8A43128C-B080-4D22-9BEA-6F1CBF7F7123', + ( + SELECT id + FROM pay_grades + WHERE grade = 'ACADEMY_CADET' + ), + 350, + 350, + 0, + 0 + ); +-- MIDSHIPMAN +INSERT INTO pay_grades (id, grade, grade_description) +VALUES ( + '63998729-EF74-486E-BEEA-5B519FA3812F', + 'MIDSHIPMAN', + 'Midshipman' + ); +INSERT INTO hhg_allowances ( + id, + pay_grade_id, + total_weight_self, + total_weight_self_plus_dependents, + pro_gear_weight, + pro_gear_weight_spouse + ) +VALUES ( + 'C95AA341-9261-4E14-B63B-9A7262FB8EA0', + ( + SELECT id + FROM pay_grades + WHERE grade = 'MIDSHIPMAN' + ), + 350, + 350, + 0, + 0 + ); +-- AVIATION_CADET +INSERT INTO pay_grades (id, grade, grade_description) +VALUES ( + 'DF749D7E-5007-43CD-8715-2875D281F817', + 'AVIATION_CADET', + 'Aviation Cadet' + ); +INSERT INTO hhg_allowances ( + id, + pay_grade_id, + total_weight_self, + total_weight_self_plus_dependents, + pro_gear_weight, + pro_gear_weight_spouse + ) +VALUES ( + '23D6DEF4-975E-4075-A4B2-E4DC3DF3D6FF', + ( + SELECT id + FROM pay_grades + WHERE grade = 'AVIATION_CADET' + ), + 7000, + 8000, + 2000, + 500 + ); +-- E_1 +INSERT INTO pay_grades (id, grade, grade_description) +VALUES ( + '6CB785D0-CABF-479A-A36D-A6AEC294A4D0', + 'E_1', + 'Enlisted Grade E_1' + ); +INSERT INTO hhg_allowances ( + id, + pay_grade_id, + total_weight_self, + total_weight_self_plus_dependents, + pro_gear_weight, + pro_gear_weight_spouse + ) +VALUES ( + '6CB785D0-CABF-479A-A36D-A6AEC294A4DE', + ( + SELECT id + FROM pay_grades + WHERE grade = 'E_1' + ), + 5000, + 8000, + 2000, + 500 + ); +-- E_2 +INSERT INTO pay_grades (id, grade, grade_description) +VALUES ( + '5F871C82-F259-43CC-9245-A6E18975DDE0', + 'E_2', + 'Enlisted Grade E_2' + ); +INSERT INTO hhg_allowances ( + id, + pay_grade_id, + total_weight_self, + total_weight_self_plus_dependents, + pro_gear_weight, + pro_gear_weight_spouse + ) +VALUES ( + '5F871C82-F259-43CC-9245-A6E18975DDE8', + ( + SELECT id + FROM pay_grades + WHERE grade = 'E_2' + ), + 5000, + 8000, + 2000, + 500 + ); +-- E_3 +INSERT INTO pay_grades (id, grade, grade_description) +VALUES ( + '862EB395-86D1-44AF-AD47-DEC44FBEDA30', + 'E_3', + 'Enlisted Grade E_3' + ); +INSERT INTO hhg_allowances ( + id, + pay_grade_id, + total_weight_self, + total_weight_self_plus_dependents, + pro_gear_weight, + pro_gear_weight_spouse + ) +VALUES ( + '862EB395-86D1-44AF-AD47-DEC44FBEDA3F', + ( + SELECT id + FROM pay_grades + WHERE grade = 'E_3' + ), + 5000, + 8000, + 2000, + 500 + ); +-- E_4 +INSERT INTO pay_grades (id, grade, grade_description) +VALUES ( + 'BB55F37C-3165-46BA-AD3F-9A477F699990', + 'E_4', + 'Enlisted Grade E_4' + ); +INSERT INTO hhg_allowances ( + id, + pay_grade_id, + total_weight_self, + total_weight_self_plus_dependents, + pro_gear_weight, + pro_gear_weight_spouse + ) +VALUES ( + 'BB55F37C-3165-46BA-AD3F-9A477F699991', + ( + SELECT id + FROM pay_grades + WHERE grade = 'E_4' + ), + 7000, + 8000, + 2000, + 500 + ); +-- E_5 +INSERT INTO pay_grades (id, grade, grade_description) +VALUES ( + '3F142461-DCA5-4A77-9295-92EE93371330', + 'E_5', + 'Enlisted Grade E_5' + ); +INSERT INTO hhg_allowances ( + id, + pay_grade_id, + total_weight_self, + total_weight_self_plus_dependents, + pro_gear_weight, + pro_gear_weight_spouse + ) +VALUES ( + '3F142461-DCA5-4A77-9295-92EE9337133A', + ( + SELECT id + FROM pay_grades + WHERE grade = 'E_5' + ), + 7000, + 9000, + 2000, + 500 + ); +-- E_6 +INSERT INTO pay_grades (id, grade, grade_description) +VALUES ( + '541AEC36-BD9F-4AD2-ABB4-D9B63E29DC80', + 'E_6', + 'Enlisted Grade E_6' + ); +INSERT INTO hhg_allowances ( + id, + pay_grade_id, + total_weight_self, + total_weight_self_plus_dependents, + pro_gear_weight, + pro_gear_weight_spouse + ) +VALUES ( + '541AEC36-BD9F-4AD2-ABB4-D9B63E29DC8C', + ( + SELECT id + FROM pay_grades + WHERE grade = 'E_6' + ), + 8000, + 11000, + 2000, + 500 + ); +-- E_7 +INSERT INTO pay_grades (id, grade, grade_description) +VALUES ( + '523D57A1-529C-4DFD-8C33-9CB169FD29A0', + 'E_7', + 'Enlisted Grade E_7' + ); +INSERT INTO hhg_allowances ( + id, + pay_grade_id, + total_weight_self, + total_weight_self_plus_dependents, + pro_gear_weight, + pro_gear_weight_spouse + ) +VALUES ( + '523D57A1-529C-4DFD-8C33-9CB169FD29AF', + ( + SELECT id + FROM pay_grades + WHERE grade = 'E_7' + ), + 11000, + 13000, + 2000, + 500 + ); +-- E_8 +INSERT INTO pay_grades (id, grade, grade_description) +VALUES ( + '1D909DB0-602F-4724-BD43-8F90A6660460', + 'E_8', + 'Enlisted Grade E_8' + ); +INSERT INTO hhg_allowances ( + id, + pay_grade_id, + total_weight_self, + total_weight_self_plus_dependents, + pro_gear_weight, + pro_gear_weight_spouse + ) +VALUES ( + '1D909DB0-602F-4724-BD43-8F90A666046E', + ( + SELECT id + FROM pay_grades + WHERE grade = 'E_8' + ), + 12000, + 14000, + 2000, + 500 + ); +-- E_9 +INSERT INTO pay_grades (id, grade, grade_description) +VALUES ( + 'A5FC8FD2-6F91-492B-ABE2-2157D03EC990', + 'E_9', + 'Enlisted Grade E_9' + ); +INSERT INTO hhg_allowances ( + id, + pay_grade_id, + total_weight_self, + total_weight_self_plus_dependents, + pro_gear_weight, + pro_gear_weight_spouse + ) +VALUES ( + 'A5FC8FD2-6F91-492B-ABE2-2157D03EC99B', + ( + SELECT id + FROM pay_grades + WHERE grade = 'E_9' + ), + 13000, + 15000, + 2000, + 500 + ); +-- E_9 Special Senior Enlisted +INSERT INTO pay_grades (id, grade, grade_description) +VALUES ( + '911208CC-3D13-49D6-9478-B0A3943435C0', + 'E_9_SPECIAL_SENIOR_ENLISTED', + 'Enlisted Grade E_9 Special Senior Enlisted' + ); +INSERT INTO hhg_allowances ( + id, + pay_grade_id, + total_weight_self, + total_weight_self_plus_dependents, + pro_gear_weight, + pro_gear_weight_spouse + ) +VALUES ( + 'D219899B-251F-49E9-94B3-C073C22D9D2F', + ( + SELECT id + FROM pay_grades + WHERE grade = 'E_9_SPECIAL_SENIOR_ENLISTED' + ), + 14000, + 17000, + 2000, + 500 + ); +-- O_1 (Academy Graduate) / W_1 uses same as O_1 +INSERT INTO pay_grades (id, grade, grade_description) +VALUES ( + 'B25998F4-4715-4F41-8986-4C5C8E59FC80', + 'O_1_ACADEMY_GRADUATE', + 'Officer Grade O_1 Academy Graduate' + ); +INSERT INTO hhg_allowances ( + id, + pay_grade_id, + total_weight_self, + total_weight_self_plus_dependents, + pro_gear_weight, + pro_gear_weight_spouse + ) +VALUES ( + 'B25998F4-4715-4F41-8986-4C5C8E59FC84', + ( + SELECT id + FROM pay_grades + WHERE grade = 'O_1_ACADEMY_GRADUATE' + ), + 10000, + 12000, + 2000, + 500 + ); +-- O_2 +INSERT INTO pay_grades (id, grade, grade_description) +VALUES ( + 'D1B76A01-D8E4-4BD3-98FF-FA93FF7BC790', + 'O_2', + 'Officer Grade O_2' + ); +INSERT INTO hhg_allowances ( + id, + pay_grade_id, + total_weight_self, + total_weight_self_plus_dependents, + pro_gear_weight, + pro_gear_weight_spouse + ) +VALUES ( + 'D1B76A01-D8E4-4BD3-98FF-FA93FF7BC79A', + ( + SELECT id + FROM pay_grades + WHERE grade = 'O_2' + ), + 12500, + 13500, + 2000, + 500 + ); +-- O_3 +INSERT INTO pay_grades (id, grade, grade_description) +VALUES ( + '5658D67B-D510-4226-9E56-714403BA0F10', + 'O_3', + 'Officer Grade O_3' + ); +INSERT INTO hhg_allowances ( + id, + pay_grade_id, + total_weight_self, + total_weight_self_plus_dependents, + pro_gear_weight, + pro_gear_weight_spouse + ) +VALUES ( + '5658D67B-D510-4226-9E56-714403BA0F1D', + ( + SELECT id + FROM pay_grades + WHERE grade = 'O_3' + ), + 13000, + 14500, + 2000, + 500 + ); +-- O_4 +INSERT INTO pay_grades (id, grade, grade_description) +VALUES ( + 'E83D8F8D-F70B-4DB1-99CC-DD983D2FD250', + 'O_4', + 'Officer Grade O_4' + ); +INSERT INTO hhg_allowances ( + id, + pay_grade_id, + total_weight_self, + total_weight_self_plus_dependents, + pro_gear_weight, + pro_gear_weight_spouse + ) +VALUES ( + '0991ABBC-5400-4E6C-8BC4-195F9A602E75', + ( + SELECT id + FROM pay_grades + WHERE grade = 'O_4' + ), + 14000, + 17000, + 2000, + 500 + ); +-- O_5 +INSERT INTO pay_grades (id, grade, grade_description) +VALUES ( + '3BC4B197-7897-4105-80A1-39A0378D7730', + 'O_5', + 'Officer Grade O_5' + ); +INSERT INTO hhg_allowances ( + id, + pay_grade_id, + total_weight_self, + total_weight_self_plus_dependents, + pro_gear_weight, + pro_gear_weight_spouse + ) +VALUES ( + 'E83D8F8D-F70B-4DB1-99CC-DD983D2FD25D', + ( + SELECT id + FROM pay_grades + WHERE grade = 'O_5' + ), + 16000, + 17500, + 2000, + 500 + ); +-- O_6 +INSERT INTO pay_grades (id, grade, grade_description) +VALUES ( + '455A112D-D1E0-4559-81E8-6DF664638F70', + 'O_6', + 'Officer Grade O_6' + ); +INSERT INTO hhg_allowances ( + id, + pay_grade_id, + total_weight_self, + total_weight_self_plus_dependents, + pro_gear_weight, + pro_gear_weight_spouse + ) +VALUES ( + '3BC4B197-7897-4105-80A1-39A0378D773E', + ( + SELECT id + FROM pay_grades + WHERE grade = 'O_6' + ), + 18000, + 18000, + 2000, + 500 + ); +-- O_7 +INSERT INTO pay_grades (id, grade, grade_description) +VALUES ( + 'CF664124-9BAF-4187-8F28-0908C0F0A5E0', + 'O_7', + 'Officer Grade O_7' + ); +INSERT INTO hhg_allowances ( + id, + pay_grade_id, + total_weight_self, + total_weight_self_plus_dependents, + pro_gear_weight, + pro_gear_weight_spouse + ) +VALUES ( + '455A112D-D1E0-4559-81E8-6DF664638F7C', + ( + SELECT id + FROM pay_grades + WHERE grade = 'O_7' + ), + 18000, + 18000, + 2000, + 500 + ); +-- O_8 +INSERT INTO pay_grades (id, grade, grade_description) +VALUES ( + '6E50B04A-52DC-45C9-91D9-4A7B4FA1AB20', + 'O_8', + 'Officer Grade O_8' + ); +INSERT INTO hhg_allowances ( + id, + pay_grade_id, + total_weight_self, + total_weight_self_plus_dependents, + pro_gear_weight, + pro_gear_weight_spouse + ) +VALUES ( + 'CF664124-9BAF-4187-8F28-0908C0F0A5E8', + ( + SELECT id + FROM pay_grades + WHERE grade = 'O_8' + ), + 18000, + 18000, + 2000, + 500 + ); +-- O_9 +INSERT INTO pay_grades (id, grade, grade_description) +VALUES ( + '1D6E34C3-8C6C-4D4F-8B91-F46BED3F5E80', + 'O_9', + 'Officer Grade O_9' + ); +INSERT INTO hhg_allowances ( + id, + pay_grade_id, + total_weight_self, + total_weight_self_plus_dependents, + pro_gear_weight, + pro_gear_weight_spouse + ) +VALUES ( + '6E50B04A-52DC-45C9-91D9-4A7B4FA1AB2A', + ( + SELECT id + FROM pay_grades + WHERE grade = 'O_9' + ), + 18000, + 18000, + 2000, + 500 + ); +-- O_10 +INSERT INTO pay_grades (id, grade, grade_description) +VALUES ( + '7FA938AB-1C34-4666-A878-9B989C916D1A', + 'O_10', + 'Officer Grade O_10' + ); +INSERT INTO hhg_allowances ( + id, + pay_grade_id, + total_weight_self, + total_weight_self_plus_dependents, + pro_gear_weight, + pro_gear_weight_spouse + ) +VALUES ( + '1D6E34C3-8C6C-4D4F-8B91-F46BED3F5E85', + ( + SELECT id + FROM pay_grades + WHERE grade = 'O_10' + ), + 18000, + 18000, + 2000, + 500 + ); +-- W_1 +INSERT INTO pay_grades (id, grade, grade_description) +VALUES ( + '6BADF8A0-B0EF-4E42-B827-7F63A3987A4B', + 'W_1', + 'Warrant Officer W_1' + ); +INSERT INTO hhg_allowances ( + id, + pay_grade_id, + total_weight_self, + total_weight_self_plus_dependents, + pro_gear_weight, + pro_gear_weight_spouse + ) +VALUES ( + '16F0F64F-728A-42A7-98B7-EA9BF289FE1A', + ( + SELECT id + FROM pay_grades + WHERE grade = 'W_1' + ), + 10000, + 12000, + 2000, + 500 + ); +-- W_2 +INSERT INTO pay_grades (id, grade, grade_description) +VALUES ( + 'A687A2E1-488C-4943-B9D9-3D645A2712F4', + 'W_2', + 'Warrant Officer W_2' + ); +INSERT INTO hhg_allowances ( + id, + pay_grade_id, + total_weight_self, + total_weight_self_plus_dependents, + pro_gear_weight, + pro_gear_weight_spouse + ) +VALUES ( + 'A687A2E1-488C-4943-B9D9-3D645A2712F9', + ( + SELECT id + FROM pay_grades + WHERE grade = 'W_2' + ), + 12500, + 13500, + 2000, + 500 + ); +-- W_3 +INSERT INTO pay_grades (id, grade, grade_description) +VALUES ( + '5A65FB1F-4245-4178-B6A7-CC504C9CBB37', + 'W_3', + 'Warrant Officer W_3' + ); +INSERT INTO hhg_allowances ( + id, + pay_grade_id, + total_weight_self, + total_weight_self_plus_dependents, + pro_gear_weight, + pro_gear_weight_spouse + ) +VALUES ( + '5A65FB1F-4245-4178-B6A7-CC504C9CBB38', + ( + SELECT id + FROM pay_grades + WHERE grade = 'W_3' + ), + 13000, + 14500, + 2000, + 500 + ); +-- W_4 +INSERT INTO pay_grades (id, grade, grade_description) +VALUES ( + '74DB5649-CF66-4AF8-939B-D3D7F1F6B7C6', + 'W_4', + 'Warrant Officer W_4' + ); +INSERT INTO hhg_allowances ( + id, + pay_grade_id, + total_weight_self, + total_weight_self_plus_dependents, + pro_gear_weight, + pro_gear_weight_spouse + ) +VALUES ( + '74DB5649-CF66-4AF8-939B-D3D7F1F6B7C7', + ( + SELECT id + FROM pay_grades + WHERE grade = 'W_4' + ), + 14000, + 17000, + 2000, + 500 + ); +-- W_5 +INSERT INTO pay_grades (id, grade, grade_description) +VALUES ( + 'EA8CB0E9-15FF-43B4-9E41-7168D01E7553', + 'W_5', + 'Warrant Officer W_5' + ); +INSERT INTO hhg_allowances ( + id, + pay_grade_id, + total_weight_self, + total_weight_self_plus_dependents, + pro_gear_weight, + pro_gear_weight_spouse + ) +VALUES ( + 'EA8CB0E9-15FF-43B4-9E41-7168D01E7554', + ( + SELECT id + FROM pay_grades + WHERE grade = 'W_5' + ), + 16000, + 17500, + 2000, + 500 + ); +-- CIVILIAN EMPLOYEE +INSERT INTO pay_grades (id, grade, grade_description) +VALUES ( + '9E2CB9A5-ACE3-4235-9EE7-EBE4CC2A9BC9', + 'CIVILIAN_EMPLOYEE', + 'Civilian Employee' + ); +INSERT INTO hhg_allowances ( + id, + pay_grade_id, + total_weight_self, + total_weight_self_plus_dependents, + pro_gear_weight, + pro_gear_weight_spouse + ) +VALUES ( + '9E2CB9A5-ACE3-4235-9EE7-EBE4CC2A9BC1', + ( + SELECT id + FROM pay_grades + WHERE grade = 'CIVILIAN_EMPLOYEE' + ), + 18000, + 18000, + 2000, + 500 + ); \ No newline at end of file diff --git a/migrations/app/schema/20241230150644_student_travel_weight_limit_param.up.sql b/migrations/app/schema/20241230150644_student_travel_weight_limit_param.up.sql new file mode 100644 index 00000000000..5edefb66caa --- /dev/null +++ b/migrations/app/schema/20241230150644_student_travel_weight_limit_param.up.sql @@ -0,0 +1,16 @@ +-- Prep app param table for json storage +ALTER TABLE application_parameters +ADD COLUMN IF NOT EXISTS parameter_json JSONB; + +-- Insert one-off student travel app param value for weight limits +INSERT INTO application_parameters (id, parameter_name, parameter_json) +VALUES ( + '4BEEAE29-C074-4CB6-B4AE-F222F755733C', + 'studentTravelHhgAllowance', + '{ + "TotalWeightSelf": 350, + "TotalWeightSelfPlusDependents": 350, + "ProGearWeight": 0, + "ProGearWeightSpouse": 0 + }'::jsonb + ); \ No newline at end of file diff --git a/migrations/app/secure/20250110214012_homesafeconnect_cert.up.sql b/migrations/app/secure/20250110214012_homesafeconnect_cert.up.sql new file mode 100644 index 00000000000..f9862f58a7c --- /dev/null +++ b/migrations/app/secure/20250110214012_homesafeconnect_cert.up.sql @@ -0,0 +1,4 @@ +-- Local test migration. +-- This will be run on development environments. +-- It should mirror what you intend to apply on prd/stg/exp/demo +-- DO NOT include any sensitive data. diff --git a/package.json b/package.json index c3bff3b6b4c..222b9519685 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "reselect": "^4.1.8", "sass": "^1.77.6", "swagger-client": "^3.18.5", - "swagger-ui-dist": "^5.2.0", + "swagger-ui-dist": "^5.18.2", "uswds": "2.13.3", "uuid": "^9.0.0", "webpack": "5", diff --git a/pkg/factory/entitlement_factory.go b/pkg/factory/entitlement_factory.go index 235fdaf3be6..17aff299990 100644 --- a/pkg/factory/entitlement_factory.go +++ b/pkg/factory/entitlement_factory.go @@ -1,6 +1,9 @@ package factory import ( + "fmt" + "log" + "github.com/gobuffalo/pop/v6" "github.com/transcom/mymove/pkg/gen/internalmessages" @@ -46,7 +49,6 @@ func BuildEntitlement(db *pop.Connection, customs []Customization, traits []Trai ocie := true proGearWeight := 2000 proGearWeightSpouse := 500 - ordersType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION // Create default Entitlement entitlement := models.Entitlement{ @@ -61,7 +63,30 @@ func BuildEntitlement(db *pop.Connection, customs []Customization, traits []Trai OrganizationalClothingAndIndividualEquipment: ocie, } // Set default calculated values - entitlement.SetWeightAllotment(string(*grade), ordersType) + var hhgAllowance models.HHGAllowance + if db != nil && grade != nil { + err := db. + RawQuery(` + SELECT hhg_allowances.* + FROM hhg_allowances + INNER JOIN pay_grades ON hhg_allowances.pay_grade_id = pay_grades.id + WHERE pay_grades.grade = $1 + LIMIT 1 + `, grade). + First(&hhgAllowance) + if err != nil { + // The database must not be running or the data was truncated + log.Panic(fmt.Errorf("database is not configured properly and is missing static hhg allowance and pay grade data. pay grade: %s err: %w", *order.Grade, err)) + } + } + + allotment := models.WeightAllotment{ + TotalWeightSelf: hhgAllowance.TotalWeightSelf, + TotalWeightSelfPlusDependents: hhgAllowance.TotalWeightSelfPlusDependents, + ProGearWeight: hhgAllowance.ProGearWeight, + ProGearWeightSpouse: hhgAllowance.ProGearWeightSpouse, + } + entitlement.WeightAllotted = &allotment entitlement.DBAuthorizedWeight = entitlement.AuthorizedWeight() // Overwrite default values with those from custom Entitlement diff --git a/pkg/factory/entitlement_factory_test.go b/pkg/factory/entitlement_factory_test.go index 0c6a0adee52..3ece040f039 100644 --- a/pkg/factory/entitlement_factory_test.go +++ b/pkg/factory/entitlement_factory_test.go @@ -5,9 +5,12 @@ import ( "github.com/transcom/mymove/pkg/gen/internalmessages" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services/entitlements" ) func (suite *FactorySuite) TestBuildEntitlement() { + fetcher := entitlements.NewWeightAllotmentFetcher() + suite.Run("Successful creation of default entitlement", func() { // Under test: BuildEntitlement // Mocked: None @@ -27,7 +30,10 @@ func (suite *FactorySuite) TestBuildEntitlement() { RequiredMedicalEquipmentWeight: 1000, OrganizationalClothingAndIndividualEquipment: true, } - defEnt.SetWeightAllotment("E_1", internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) + allotment, err := fetcher.GetWeightAllotment(suite.AppContextForTest(), "E_1", internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) + suite.NoError(err) + defEnt.WeightAllotted = &allotment + defEnt.DBAuthorizedWeight = defEnt.AuthorizedWeight() // FUNCTION UNDER TEST @@ -84,7 +90,9 @@ func (suite *FactorySuite) TestBuildEntitlement() { suite.Equal(custEnt.OrganizationalClothingAndIndividualEquipment, entitlement.OrganizationalClothingAndIndividualEquipment) // Set the weight allotment on the custom object so as to compare - custEnt.SetWeightAllotment("E_1", internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) + allotment, err := fetcher.GetWeightAllotment(suite.AppContextForTest(), "E_1", internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) + suite.NoError(err) + custEnt.WeightAllotted = &allotment custEnt.DBAuthorizedWeight = custEnt.AuthorizedWeight() // Check that the created object had the correct allotments set @@ -129,9 +137,10 @@ func (suite *FactorySuite) TestBuildEntitlement() { testEnt := BuildEntitlement(nil, nil, nil) // Set the weight allotment on the custom object to O_9 testEnt.DBAuthorizedWeight = nil // clear original value - testEnt.SetWeightAllotment("O_9", internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) + allotment, err := fetcher.GetWeightAllotment(suite.AppContextForTest(), "O_9", internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) + suite.NoError(err) + testEnt.WeightAllotted = &allotment testEnt.DBAuthorizedWeight = testEnt.AuthorizedWeight() - // Now DBAuthorizedWeight should be appropriate for O_9 grade // FUNCTION UNDER TEST grade := internalmessages.OrderPayGrade(models.ServiceMemberGradeO9) diff --git a/pkg/factory/order_factory.go b/pkg/factory/order_factory.go index 9bc3c61fb1d..25adb96eb78 100644 --- a/pkg/factory/order_factory.go +++ b/pkg/factory/order_factory.go @@ -1,6 +1,7 @@ package factory import ( + "fmt" "log" "time" @@ -291,7 +292,25 @@ func buildOrderWithBuildType(db *pop.Connection, customs []Customization, traits testdatagen.MergeModels(&order, cOrder) // If db is false, it's a stub. No need to create in database - if db != nil { + if db != nil && order.Grade != nil { + // Check if PayGrade already exists + var existingPayGrade models.PayGrade + err := db.Where("grade = ?", string(*order.Grade)).First(&existingPayGrade) + if err == nil { + // PayGrade exists + grade := internalmessages.OrderPayGrade(existingPayGrade.Grade) + order.Grade = internalmessages.NewOrderPayGrade(grade) + } else { + log.Panic(fmt.Errorf("database is not configured properly and is missing static hhg allowance and pay grade data. pay grade: %s err: %w", *order.Grade, err)) + } + + // Check if HHGAllowance already exists for this PayGrade + var existingHHGAllowance models.HHGAllowance + err = db.Where("pay_grade_id = ?", existingPayGrade.ID).First(&existingHHGAllowance) + if err != nil { + log.Panic(fmt.Errorf("database is not configured properly and is missing static hhg allowance and pay grade data. pay grade: %s err: %w", *order.Grade, err)) + } + mustCreate(db, &order) } diff --git a/pkg/factory/shared.go b/pkg/factory/shared.go index 037f2d352d5..eed57d130ec 100644 --- a/pkg/factory/shared.go +++ b/pkg/factory/shared.go @@ -53,6 +53,8 @@ var CustomerSupportRemark CustomType = "CustomerSupportRemark" var Document CustomType = "Document" var DutyLocation CustomType = "DutyLocation" var Entitlement CustomType = "Entitlement" +var HHGAllowance CustomType = "HHGAllowance" +var PayGrade CustomType = "PayGrade" var UBAllowance CustomType = "UBAllowances" var EvaluationReport CustomType = "EvaluationReport" var LineOfAccounting CustomType = "LineOfAccounting" @@ -121,6 +123,8 @@ var defaultTypesMap = map[string]CustomType{ "models.Document": Document, "models.DutyLocation": DutyLocation, "models.Entitlement": Entitlement, + "models.PayGrade": PayGrade, + "models.HHGAllowance": HHGAllowance, "models.UBAllowances": UBAllowance, "models.EvaluationReport": EvaluationReport, "models.LineOfAccounting": LineOfAccounting, diff --git a/pkg/gen/internalapi/embedded_spec.go b/pkg/gen/internalapi/embedded_spec.go index 0e198c6c4e6..9597705d4b0 100644 --- a/pkg/gen/internalapi/embedded_spec.go +++ b/pkg/gen/internalapi/embedded_spec.go @@ -555,6 +555,9 @@ func init() { "schema": { "$ref": "#/definitions/IndexEntitlements" } + }, + "500": { + "description": "internal server error" } } } @@ -9247,6 +9250,9 @@ func init() { "schema": { "$ref": "#/definitions/IndexEntitlements" } + }, + "500": { + "description": "internal server error" } } } diff --git a/pkg/gen/internalapi/internaloperations/entitlements/index_entitlements_responses.go b/pkg/gen/internalapi/internaloperations/entitlements/index_entitlements_responses.go index 972c901ef1d..a9358ff2691 100644 --- a/pkg/gen/internalapi/internaloperations/entitlements/index_entitlements_responses.go +++ b/pkg/gen/internalapi/internaloperations/entitlements/index_entitlements_responses.go @@ -60,3 +60,28 @@ func (o *IndexEntitlementsOK) WriteResponse(rw http.ResponseWriter, producer run panic(err) // let the recovery middleware deal with this } } + +// IndexEntitlementsInternalServerErrorCode is the HTTP code returned for type IndexEntitlementsInternalServerError +const IndexEntitlementsInternalServerErrorCode int = 500 + +/* +IndexEntitlementsInternalServerError internal server error + +swagger:response indexEntitlementsInternalServerError +*/ +type IndexEntitlementsInternalServerError struct { +} + +// NewIndexEntitlementsInternalServerError creates IndexEntitlementsInternalServerError with default headers values +func NewIndexEntitlementsInternalServerError() *IndexEntitlementsInternalServerError { + + return &IndexEntitlementsInternalServerError{} +} + +// WriteResponse to the client +func (o *IndexEntitlementsInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(500) +} diff --git a/pkg/handlers/ghcapi/api.go b/pkg/handlers/ghcapi/api.go index 3c4f3740941..cf3a4c188c9 100644 --- a/pkg/handlers/ghcapi/api.go +++ b/pkg/handlers/ghcapi/api.go @@ -14,6 +14,7 @@ import ( boatshipment "github.com/transcom/mymove/pkg/services/boat_shipment" dateservice "github.com/transcom/mymove/pkg/services/calendar" customerserviceremarks "github.com/transcom/mymove/pkg/services/customer_support_remarks" + "github.com/transcom/mymove/pkg/services/entitlements" evaluationreport "github.com/transcom/mymove/pkg/services/evaluation_report" "github.com/transcom/mymove/pkg/services/fetch" "github.com/transcom/mymove/pkg/services/ghcrateengine" @@ -59,6 +60,7 @@ import ( // NewGhcAPIHandler returns a handler for the GHC API func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { + waf := entitlements.NewWeightAllotmentFetcher() ghcSpec, err := loads.Analyzed(ghcapi.SwaggerJSON, "") if err != nil { log.Fatalln(err) @@ -185,7 +187,7 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { HandlerConfig: handlerConfig, EvaluationReportFetcher: evaluationreport.NewEvaluationReportFetcher(), MTOShipmentFetcher: mtoshipment.NewMTOShipmentFetcher(), - OrderFetcher: order.NewOrderFetcher(), + OrderFetcher: order.NewOrderFetcher(waf), ReportViolationFetcher: reportviolation.NewReportViolationFetcher(), } @@ -240,7 +242,7 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { fetch.NewFetcher(queryBuilder), handlerConfig.HHGPlanner(), moveRouter, - move.NewMoveWeights(mtoshipment.NewShipmentReweighRequester()), + move.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf), handlerConfig.NotificationSender(), paymentRequestShipmentRecalculator, addressUpdater, @@ -295,7 +297,7 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { ghcAPI.MoveTaskOrderGetMoveTaskOrderHandler = GetMoveTaskOrderHandler{ handlerConfig, - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } ghcAPI.MoveSetFinancialReviewFlagHandler = SetFinancialReviewFlagHandler{ handlerConfig, @@ -315,7 +317,7 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { } ghcAPI.OrderGetOrderHandler = GetOrdersHandler{ handlerConfig, - order.NewOrderFetcher(), + order.NewOrderFetcher(waf), } ghcAPI.OrderCounselingUpdateOrderHandler = CounselingUpdateOrderHandler{ handlerConfig, @@ -323,6 +325,7 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { } ghcAPI.OrderCreateOrderHandler = CreateOrderHandler{ handlerConfig, + waf, } ghcAPI.OrderUpdateOrderHandler = UpdateOrderHandler{ @@ -426,7 +429,7 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { mtoshipment.NewShipmentRouter(), mtoServiceItemCreator, handlerConfig.HHGPlanner(), - move.NewMoveWeights(mtoshipment.NewShipmentReweighRequester()), + move.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf), ), shipmentSITStatus, } @@ -473,7 +476,7 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { fetch.NewFetcher(queryBuilder), handlerConfig.HHGPlanner(), moveRouter, - move.NewMoveWeights(mtoshipment.NewShipmentReweighRequester()), + move.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf), handlerConfig.NotificationSender(), paymentRequestShipmentRecalculator, addressUpdater, @@ -485,7 +488,7 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { fetch.NewFetcher(queryBuilder), handlerConfig.HHGPlanner(), moveRouter, - move.NewMoveWeights(mtoshipment.NewShipmentReweighRequester()), + move.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf), handlerConfig.NotificationSender(), paymentRequestShipmentRecalculator, addressUpdater, @@ -556,21 +559,21 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { ghcAPI.QueuesGetMovesQueueHandler = GetMovesQueueHandler{ handlerConfig, - order.NewOrderFetcher(), + order.NewOrderFetcher(waf), movelocker.NewMoveUnlocker(), officeusercreator.NewOfficeUserFetcherPop(), } ghcAPI.QueuesGetDestinationRequestsQueueHandler = GetDestinationRequestsQueueHandler{ handlerConfig, - order.NewOrderFetcher(), + order.NewOrderFetcher(waf), movelocker.NewMoveUnlocker(), officeusercreator.NewOfficeUserFetcherPop(), } ghcAPI.QueuesListPrimeMovesHandler = ListPrimeMovesHandler{ handlerConfig, - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } ghcAPI.QueuesGetPaymentRequestsQueueHandler = GetPaymentRequestsQueueHandler{ @@ -582,14 +585,14 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { ghcAPI.QueuesGetServicesCounselingQueueHandler = GetServicesCounselingQueueHandler{ handlerConfig, - order.NewOrderFetcher(), + order.NewOrderFetcher(waf), movelocker.NewMoveUnlocker(), officeusercreator.NewOfficeUserFetcherPop(), } ghcAPI.QueuesGetServicesCounselingOriginListHandler = GetServicesCounselingOriginListHandler{ handlerConfig, - order.NewOrderFetcher(), + order.NewOrderFetcher(waf), officeusercreator.NewOfficeUserFetcherPop(), } diff --git a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go index 882f947c440..813bb11b112 100644 --- a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go +++ b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go @@ -629,9 +629,6 @@ func Order(order *models.Order) *ghcmessages.Order { destinationDutyLocation := DutyLocation(&order.NewDutyLocation) originDutyLocation := DutyLocation(order.OriginDutyLocation) - if order.Grade != nil && order.Entitlement != nil { - order.Entitlement.SetWeightAllotment(string(*order.Grade), order.OrdersType) - } entitlements := Entitlement(order.Entitlement) var deptIndicator ghcmessages.DeptIndicator diff --git a/pkg/handlers/ghcapi/move_task_order_test.go b/pkg/handlers/ghcapi/move_task_order_test.go index e93d756cf58..a156eb19251 100644 --- a/pkg/handlers/ghcapi/move_task_order_test.go +++ b/pkg/handlers/ghcapi/move_task_order_test.go @@ -30,6 +30,7 @@ import ( "github.com/transcom/mymove/pkg/notifications" routemocks "github.com/transcom/mymove/pkg/route/mocks" "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/services/entitlements" "github.com/transcom/mymove/pkg/services/ghcrateengine" "github.com/transcom/mymove/pkg/services/mocks" moverouter "github.com/transcom/mymove/pkg/services/move" @@ -44,7 +45,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrderHandlerIntegration() { moveTaskOrder := factory.BuildMove(suite.DB(), nil, nil) factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeMS) factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeCS) - + waf := entitlements.NewWeightAllotmentFetcher() request := httptest.NewRequest("GET", "/move-task-orders/{moveTaskOrderID}", nil) params := movetaskorderops.GetMoveTaskOrderParams{ HTTPRequest: request, @@ -53,7 +54,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrderHandlerIntegration() { handlerConfig := suite.HandlerConfig() handler := GetMoveTaskOrderHandler{ handlerConfig, - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } // Validate incoming payload: no body to validate diff --git a/pkg/handlers/ghcapi/moving_expense_test.go b/pkg/handlers/ghcapi/moving_expense_test.go index 06c8d03cfc6..5d7e1b8f3a9 100644 --- a/pkg/handlers/ghcapi/moving_expense_test.go +++ b/pkg/handlers/ghcapi/moving_expense_test.go @@ -236,7 +236,7 @@ func (suite *HandlerSuite) TestUpdateMovingExpenseHandlerIntegration() { var ppmShipment models.PPMShipment var movingExpense models.MovingExpense - suite.PreloadData(func() { + setupData := func() { var err error userUploader, err = uploader.NewUserUploader(suite.createS3HandlerConfig().FileStorer(), uploader.MaxCustomerUserUploadFileSizeLimit) @@ -270,7 +270,7 @@ func (suite *HandlerSuite) TestUpdateMovingExpenseHandlerIntegration() { }, nil) ppmShipment.MovingExpenses = append(ppmShipment.MovingExpenses, movingExpense) - }) + } setUpRequestAndParams := func(movingExpense models.MovingExpense) movingexpenseops.UpdateMovingExpenseParams { endpoint := fmt.Sprintf("/ppm-shipments/%s/moving-expense/%s", ppmShipment.ID.String(), movingExpense.ID.String()) @@ -330,6 +330,7 @@ func (suite *HandlerSuite) TestUpdateMovingExpenseHandlerIntegration() { suite.Run("Success", func() { suite.Run("Can approve a moving expense", func() { + setupData() params := setUpRequestAndParams(ppmShipment.MovingExpenses[0]) params.UpdateMovingExpense.Status = ghcmessages.PPMDocumentStatusAPPROVED @@ -352,6 +353,7 @@ func (suite *HandlerSuite) TestUpdateMovingExpenseHandlerIntegration() { }) suite.Run("Can exclude a moving expense", func() { + setupData() params := setUpRequestAndParams(ppmShipment.MovingExpenses[0]) reason := "Not a valid receipt" @@ -380,6 +382,7 @@ func (suite *HandlerSuite) TestUpdateMovingExpenseHandlerIntegration() { }) suite.Run("Can reject a moving expense", func() { + setupData() params := setUpRequestAndParams(ppmShipment.MovingExpenses[0]) reason := "Over budget!" @@ -408,6 +411,7 @@ func (suite *HandlerSuite) TestUpdateMovingExpenseHandlerIntegration() { }) suite.Run("Can update a non-storage moving expense", func() { + setupData() params := setUpRequestAndParams(ppmShipment.MovingExpenses[0]) newAmount := movingExpense.Amount.AddCents(1000) @@ -435,6 +439,7 @@ func (suite *HandlerSuite) TestUpdateMovingExpenseHandlerIntegration() { }) suite.Run("Can update a storage moving expense", func() { + setupData() storageExpenseType := models.MovingExpenseReceiptTypeStorage storageMovingExpense := factory.BuildMovingExpense(suite.DB(), []factory.Customization{ { @@ -503,6 +508,7 @@ func (suite *HandlerSuite) TestUpdateMovingExpenseHandlerIntegration() { suite.Run("Failure", func() { suite.Run("Returns a Forbidden response if the request doesn't come from the office app", func() { + setupData() params := setUpRequestAndParams(ppmShipment.MovingExpenses[0]) params.HTTPRequest = suite.AuthenticateRequest(params.HTTPRequest, ppmShipment.Shipment.MoveTaskOrder.Orders.ServiceMember) @@ -515,6 +521,7 @@ func (suite *HandlerSuite) TestUpdateMovingExpenseHandlerIntegration() { }) suite.Run("Returns a NotFound response when the moving expense is not found", func() { + setupData() params := setUpRequestAndParams(ppmShipment.MovingExpenses[0]) params.MovingExpenseID = handlers.FmtUUIDValue(uuid.Must(uuid.NewV4())) @@ -527,6 +534,7 @@ func (suite *HandlerSuite) TestUpdateMovingExpenseHandlerIntegration() { }) suite.Run("Returns a PreconditionFailed response when the eTag doesn't match the expected eTag", func() { + setupData() params := setUpRequestAndParams(ppmShipment.MovingExpenses[0]) params.IfMatch = "wrong eTag" @@ -547,6 +555,7 @@ func (suite *HandlerSuite) TestUpdateMovingExpenseHandlerIntegration() { }) suite.Run("Returns an UnprocessableEntity response when the requested updates aren't valid", func() { + setupData() params := setUpRequestAndParams(ppmShipment.MovingExpenses[0]) params.UpdateMovingExpense = &ghcmessages.UpdateMovingExpense{ diff --git a/pkg/handlers/ghcapi/mto_service_items_test.go b/pkg/handlers/ghcapi/mto_service_items_test.go index 0a38a489ba1..a0b1880ac26 100644 --- a/pkg/handlers/ghcapi/mto_service_items_test.go +++ b/pkg/handlers/ghcapi/mto_service_items_test.go @@ -20,6 +20,7 @@ import ( routemocks "github.com/transcom/mymove/pkg/route/mocks" "github.com/transcom/mymove/pkg/services/address" boatshipment "github.com/transcom/mymove/pkg/services/boat_shipment" + "github.com/transcom/mymove/pkg/services/entitlements" "github.com/transcom/mymove/pkg/services/fetch" "github.com/transcom/mymove/pkg/services/ghcrateengine" mobilehomeshipment "github.com/transcom/mymove/pkg/services/mobile_home_shipment" @@ -304,7 +305,7 @@ func (suite *HandlerSuite) createServiceItem() (models.MTOServiceItem, models.Mo } func (suite *HandlerSuite) TestUpdateMTOServiceItemStatusHandler() { - + waf := entitlements.NewWeightAllotmentFetcher() builder := query.NewQueryBuilder() fetcher := fetch.NewFetcher(builder) planner := &routemocks.Planner{} @@ -315,7 +316,7 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemStatusHandler() { false, false, ).Return(400, nil) - moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester()) + moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) // Get shipment payment request recalculator service creator := paymentrequest.NewPaymentRequestCreator(planner, ghcrateengine.NewServiceItemPricer()) @@ -736,6 +737,8 @@ func (suite *HandlerSuite) TestGetMTOServiceItemHandler() { func (suite *HandlerSuite) TestUpdateServiceItemSitEntryDateHandler() { serviceItemID := uuid.Must(uuid.FromString("f7b4b9e2-04e8-4c34-827a-df917e69caf4")) + waf := entitlements.NewWeightAllotmentFetcher() + var requestUser models.User newSitEntryDate := time.Date(2023, time.October, 10, 10, 10, 0, 0, time.UTC) @@ -768,7 +771,7 @@ func (suite *HandlerSuite) TestUpdateServiceItemSitEntryDateHandler() { false, false, ).Return(400, nil) - moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester()) + moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) // Get shipment payment request recalculator service creator := paymentrequest.NewPaymentRequestCreator(planner, ghcrateengine.NewServiceItemPricer()) diff --git a/pkg/handlers/ghcapi/mto_shipment_test.go b/pkg/handlers/ghcapi/mto_shipment_test.go index 316a2adbbd4..21bddbcf663 100644 --- a/pkg/handlers/ghcapi/mto_shipment_test.go +++ b/pkg/handlers/ghcapi/mto_shipment_test.go @@ -24,6 +24,7 @@ import ( "github.com/transcom/mymove/pkg/services" "github.com/transcom/mymove/pkg/services/address" boatshipment "github.com/transcom/mymove/pkg/services/boat_shipment" + "github.com/transcom/mymove/pkg/services/entitlements" "github.com/transcom/mymove/pkg/services/fetch" "github.com/transcom/mymove/pkg/services/ghcrateengine" mobilehomeshipment "github.com/transcom/mymove/pkg/services/mobile_home_shipment" @@ -579,6 +580,8 @@ func (suite *HandlerSuite) TestGetShipmentHandler() { } func (suite *HandlerSuite) TestApproveShipmentHandler() { + waf := entitlements.NewWeightAllotmentFetcher() + suite.Run("Returns 200 when all validations pass", func() { move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ @@ -598,7 +601,7 @@ func (suite *HandlerSuite) TestApproveShipmentHandler() { builder := query.NewQueryBuilder() moveRouter := moveservices.NewMoveRouter() planner := &routemocks.Planner{} - moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester()) + moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), mock.Anything, @@ -2093,6 +2096,7 @@ func (suite *HandlerSuite) TestRequestShipmentCancellationHandler() { func (suite *HandlerSuite) TestRequestShipmentReweighHandler() { addressUpdater := address.NewAddressUpdater() addressCreator := address.NewAddressCreator() + waf := entitlements.NewWeightAllotmentFetcher() suite.Run("Returns 200 when all validations pass", func() { move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) @@ -2129,7 +2133,7 @@ func (suite *HandlerSuite) TestRequestShipmentReweighHandler() { false, ).Return(400, nil) moveRouter := moveservices.NewMoveRouter() - moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester()) + moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) // Get shipment payment request recalculator service creator := paymentrequest.NewPaymentRequestCreator(planner, ghcrateengine.NewServiceItemPricer()) @@ -2189,7 +2193,7 @@ func (suite *HandlerSuite) TestRequestShipmentReweighHandler() { false, ).Return(400, nil) moveRouter := moveservices.NewMoveRouter() - moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester()) + moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) // Get shipment payment request recalculator service creator := paymentrequest.NewPaymentRequestCreator(planner, ghcrateengine.NewServiceItemPricer()) @@ -2246,7 +2250,7 @@ func (suite *HandlerSuite) TestRequestShipmentReweighHandler() { false, ).Return(400, nil) moveRouter := moveservices.NewMoveRouter() - moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester()) + moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) // Get shipment payment request recalculator service creator := paymentrequest.NewPaymentRequestCreator(planner, ghcrateengine.NewServiceItemPricer()) @@ -2304,7 +2308,7 @@ func (suite *HandlerSuite) TestRequestShipmentReweighHandler() { false, ).Return(400, nil) moveRouter := moveservices.NewMoveRouter() - moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester()) + moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) // Get shipment payment request recalculator service creator := paymentrequest.NewPaymentRequestCreator(planner, ghcrateengine.NewServiceItemPricer()) @@ -2363,7 +2367,7 @@ func (suite *HandlerSuite) TestRequestShipmentReweighHandler() { false, ).Return(400, nil) moveRouter := moveservices.NewMoveRouter() - moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester()) + moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) // Get shipment payment request recalculator service creator := paymentrequest.NewPaymentRequestCreator(planner, ghcrateengine.NewServiceItemPricer()) @@ -2421,7 +2425,7 @@ func (suite *HandlerSuite) TestRequestShipmentReweighHandler() { false, ).Return(400, nil) moveRouter := moveservices.NewMoveRouter() - moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester()) + moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) // Get shipment payment request recalculator service creator := paymentrequest.NewPaymentRequestCreator(planner, ghcrateengine.NewServiceItemPricer()) @@ -2689,6 +2693,8 @@ func (suite *HandlerSuite) TestReviewShipmentAddressUpdateHandler() { } func (suite *HandlerSuite) TestApproveSITExtensionHandler() { + waf := entitlements.NewWeightAllotmentFetcher() + suite.Run("Returns 200 and updates SIT days allowance when validations pass", func() { sitDaysAllowance := 20 move := factory.BuildApprovalsRequestedMove(suite.DB(), []factory.Customization{ @@ -2760,7 +2766,7 @@ func (suite *HandlerSuite) TestApproveSITExtensionHandler() { false, false, ).Return(400, nil) - moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester()) + moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) // Get shipment payment request recalculator service creator := paymentrequest.NewPaymentRequestCreator(planner, ghcrateengine.NewServiceItemPricer()) @@ -2882,6 +2888,8 @@ func (suite *HandlerSuite) TestDenySITExtensionHandler() { } func (suite *HandlerSuite) CreateApprovedSITDurationUpdate() { + waf := entitlements.NewWeightAllotmentFetcher() + suite.Run("Returns 200, creates new SIT extension, and updates SIT days allowance on shipment without an allowance when validations pass", func() { mtoShipment := factory.BuildMTOShipment(suite.DB(), nil, nil) @@ -2902,7 +2910,7 @@ func (suite *HandlerSuite) CreateApprovedSITDurationUpdate() { false, false, ).Return(400, nil) - moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester()) + moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) // Get shipment payment request recalculator service creator := paymentrequest.NewPaymentRequestCreator(planner, ghcrateengine.NewServiceItemPricer()) @@ -2987,7 +2995,7 @@ func (suite *HandlerSuite) CreateApprovedSITDurationUpdate() { false, false, ).Return(400, nil) - moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester()) + moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) // Get shipment payment request recalculator service creator := paymentrequest.NewPaymentRequestCreator(planner, ghcrateengine.NewServiceItemPricer()) @@ -4058,6 +4066,7 @@ func (suite *HandlerSuite) getUpdateShipmentParams(originalShipment models.MTOSh func (suite *HandlerSuite) TestUpdateShipmentHandler() { addressUpdater := address.NewAddressUpdater() addressCreator := address.NewAddressCreator() + waf := entitlements.NewWeightAllotmentFetcher() planner := &routemocks.Planner{} planner.On("ZipTransitDistance", @@ -4068,7 +4077,7 @@ func (suite *HandlerSuite) TestUpdateShipmentHandler() { false, ).Return(400, nil) moveRouter := moveservices.NewMoveRouter() - moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester()) + moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) // Get shipment payment request recalculator service creator := paymentrequest.NewPaymentRequestCreator(planner, ghcrateengine.NewServiceItemPricer()) diff --git a/pkg/handlers/ghcapi/orders.go b/pkg/handlers/ghcapi/orders.go index 21706535e6a..a92eb81dbc3 100644 --- a/pkg/handlers/ghcapi/orders.go +++ b/pkg/handlers/ghcapi/orders.go @@ -164,6 +164,7 @@ func (h CounselingUpdateOrderHandler) Handle( // CounselingUpdateOrderHandler create an order via POST /orders type CreateOrderHandler struct { handlers.HandlerConfig + services.WeightAllotmentFetcher } // Handle ... creates an order as requested by a services counselor @@ -237,7 +238,12 @@ func (h CreateOrderHandler) Handle(params orderop.CreateOrderParams) middleware. grade := (internalmessages.OrderPayGrade)(*payload.Grade) ordersType := (internalmessages.OrdersType)(*payload.OrdersType) - weightAllotment := models.GetWeightAllotment(grade, ordersType) + weightAllotment, err := h.WeightAllotmentFetcher.GetWeightAllotment(appCtx, string(grade), ordersType) + if err != nil { + err = apperror.NewBadDataError("Weight allotment cannot be verified") + appCtx.Logger().Error(err.Error()) + return orderop.NewCreateOrderUnprocessableEntity(), err + } weight := weightAllotment.TotalWeightSelf if *payload.HasDependents { weight = weightAllotment.TotalWeightSelfPlusDependents diff --git a/pkg/handlers/ghcapi/orders_test.go b/pkg/handlers/ghcapi/orders_test.go index 9dba706d059..ccbd4ca8aac 100644 --- a/pkg/handlers/ghcapi/orders_test.go +++ b/pkg/handlers/ghcapi/orders_test.go @@ -22,6 +22,7 @@ import ( "github.com/transcom/mymove/pkg/models/roles" routemocks "github.com/transcom/mymove/pkg/route/mocks" "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/services/entitlements" "github.com/transcom/mymove/pkg/services/ghcrateengine" "github.com/transcom/mymove/pkg/services/mocks" moverouter "github.com/transcom/mymove/pkg/services/move" @@ -36,6 +37,7 @@ import ( ) func (suite *HandlerSuite) TestCreateOrder() { + waf := entitlements.NewWeightAllotmentFetcher() sm := factory.BuildExtendedServiceMember(suite.AppContextForTest().DB(), nil, nil) officeUser := factory.BuildOfficeUserWithRoles(suite.AppContextForTest().DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) @@ -84,7 +86,7 @@ func (suite *HandlerSuite) TestCreateOrder() { fakeS3 := storageTest.NewFakeS3Storage(true) handlerConfig := suite.HandlerConfig() handlerConfig.SetFileStorer(fakeS3) - createHandler := CreateOrderHandler{handlerConfig} + createHandler := CreateOrderHandler{handlerConfig, waf} response := createHandler.Handle(params) @@ -107,6 +109,8 @@ func (suite *HandlerSuite) TestCreateOrder() { } func (suite *HandlerSuite) TestCreateOrderWithOCONUSValues() { + waf := entitlements.NewWeightAllotmentFetcher() + sm := factory.BuildExtendedServiceMember(suite.AppContextForTest().DB(), nil, nil) officeUser := factory.BuildOfficeUserWithRoles(suite.AppContextForTest().DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) @@ -160,7 +164,7 @@ func (suite *HandlerSuite) TestCreateOrderWithOCONUSValues() { fakeS3 := storageTest.NewFakeS3Storage(true) handlerConfig := suite.HandlerConfig() handlerConfig.SetFileStorer(fakeS3) - createHandler := CreateOrderHandler{handlerConfig} + createHandler := CreateOrderHandler{handlerConfig, waf} response := createHandler.Handle(params) @@ -187,7 +191,7 @@ func (suite *HandlerSuite) TestCreateOrderWithOCONUSValues() { func (suite *HandlerSuite) TestGetOrderHandlerIntegration() { officeUser := factory.BuildOfficeUserWithRoles(nil, nil, []roles.RoleType{roles.RoleTypeTOO}) - + waf := entitlements.NewWeightAllotmentFetcher() move := factory.BuildMove(suite.DB(), nil, nil) order := move.Orders request := httptest.NewRequest("GET", "/orders/{orderID}", nil) @@ -200,7 +204,7 @@ func (suite *HandlerSuite) TestGetOrderHandlerIntegration() { handlerConfig := suite.HandlerConfig() handler := GetOrdersHandler{ handlerConfig, - orderservice.NewOrderFetcher(), + orderservice.NewOrderFetcher(waf), } // Validate incoming payload: no body to validate @@ -235,7 +239,7 @@ func (suite *HandlerSuite) TestGetOrderHandlerIntegration() { func (suite *HandlerSuite) TestWeightAllowances() { suite.Run("With E-1 rank and no dependents", func() { - order := factory.BuildOrder(nil, []factory.Customization{ + order := factory.BuildOrder(suite.DB(), []factory.Customization{ { Model: models.Order{ ID: uuid.Must(uuid.NewV4()), @@ -289,7 +293,7 @@ func (suite *HandlerSuite) TestWeightAllowances() { }) suite.Run("With E-1 rank and dependents", func() { - order := factory.BuildOrder(nil, []factory.Customization{ + order := factory.BuildOrder(suite.DB(), []factory.Customization{ { Model: models.Order{ ID: uuid.Must(uuid.NewV4()), diff --git a/pkg/handlers/ghcapi/ppm_shipment_test.go b/pkg/handlers/ghcapi/ppm_shipment_test.go index d5e358feb4a..bc8093032b5 100644 --- a/pkg/handlers/ghcapi/ppm_shipment_test.go +++ b/pkg/handlers/ghcapi/ppm_shipment_test.go @@ -26,7 +26,7 @@ func (suite *HandlerSuite) TestGetPPMSITEstimatedCostHandler() { var ppmShipment models.PPMShipment newFakeSITEstimatedCost := models.CentPointer(unit.Cents(25500)) - setupPricerData := func() { + suite.PreloadData(func() { testdatagen.FetchOrMakeGHCDieselFuelPrice(suite.DB(), testdatagen.Assertions{ GHCDieselFuelPrice: models.GHCDieselFuelPrice{ FuelPriceInMillicents: unit.Millicents(281400), @@ -342,10 +342,9 @@ func (suite *HandlerSuite) TestGetPPMSITEstimatedCostHandler() { PriceCents: 63, }, }) - } + }) - suite.PreloadData(func() { - setupPricerData() + setupData := func() { sitLocationDestination := models.SITLocationTypeDestination entryDate := time.Date(2020, time.March, 15, 0, 0, 0, 0, time.UTC) mtoShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ @@ -389,7 +388,8 @@ func (suite *HandlerSuite) TestGetPPMSITEstimatedCostHandler() { mockedPlanner := &routemocks.Planner{} mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "90210", "30813", false, false).Return(2294, nil) - }) + } + setupData() setUpGetCostRequestAndParams := func() ppmsitops.GetPPMSITEstimatedCostParams { endpoint := fmt.Sprintf("/ppm-shipments/%s/sit_location/%s/sit-estimated-cost", ppmShipment.ID.String(), *ppmShipment.SITLocation) diff --git a/pkg/handlers/ghcapi/queues_test.go b/pkg/handlers/ghcapi/queues_test.go index d3feef08c8b..8899da4999f 100644 --- a/pkg/handlers/ghcapi/queues_test.go +++ b/pkg/handlers/ghcapi/queues_test.go @@ -15,6 +15,7 @@ import ( "github.com/transcom/mymove/pkg/gen/ghcmessages" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/models/roles" + "github.com/transcom/mymove/pkg/services/entitlements" movelocker "github.com/transcom/mymove/pkg/services/lock_move" "github.com/transcom/mymove/pkg/services/mocks" movefetcher "github.com/transcom/mymove/pkg/services/move" @@ -25,6 +26,8 @@ import ( ) func (suite *HandlerSuite) TestGetMoveQueuesHandler() { + waf := entitlements.NewWeightAllotmentFetcher() + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), factory.GetTraitActiveOfficeUser(), []roles.RoleType{roles.RoleTypeTOO}) factory.BuildOfficeUserWithRoles(suite.DB(), factory.GetTraitActiveOfficeUser(), []roles.RoleType{roles.RoleTypeTIO}) officeUser.User.Roles = append(officeUser.User.Roles, roles.Role{ @@ -77,7 +80,7 @@ func (suite *HandlerSuite) TestGetMoveQueuesHandler() { mockUnlocker := movelocker.NewMoveUnlocker() handler := GetMovesQueueHandler{ handlerConfig, - order.NewOrderFetcher(), + order.NewOrderFetcher(waf), mockUnlocker, officeusercreator.NewOfficeUserFetcherPop(), } @@ -109,6 +112,8 @@ func (suite *HandlerSuite) TestGetMoveQueuesHandler() { } func (suite *HandlerSuite) TestGetDestinationRequestsQueuesHandler() { + waf := entitlements.NewWeightAllotmentFetcher() + // default GBLOC is KKFA officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), factory.GetTraitActiveOfficeUser(), []roles.RoleType{roles.RoleTypeTOO}) officeUser.User.Roles = append(officeUser.User.Roles, roles.Role{ @@ -232,7 +237,7 @@ func (suite *HandlerSuite) TestGetDestinationRequestsQueuesHandler() { mockUnlocker := movelocker.NewMoveUnlocker() handler := GetDestinationRequestsQueueHandler{ handlerConfig, - order.NewOrderFetcher(), + order.NewOrderFetcher(waf), mockUnlocker, officeusercreator.NewOfficeUserFetcherPop(), } @@ -253,6 +258,7 @@ func (suite *HandlerSuite) TestGetDestinationRequestsQueuesHandler() { func (suite *HandlerSuite) TestListPrimeMovesHandler() { // Default Origin Duty Location GBLOC is KKFA move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + waf := entitlements.NewWeightAllotmentFetcher() request := httptest.NewRequest("GET", "/queues/listPrimeMoves", nil) params := queues.ListPrimeMovesParams{ @@ -261,7 +267,7 @@ func (suite *HandlerSuite) TestListPrimeMovesHandler() { handlerConfig := suite.HandlerConfig() handler := ListPrimeMovesHandler{ handlerConfig, - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } // Validate incoming payload: no body to validate @@ -376,6 +382,7 @@ func (suite *HandlerSuite) TestGetMoveQueuesBranchFilter() { officeUser.User.Roles = append(officeUser.User.Roles, roles.Role{ RoleType: roles.RoleTypeTOO, }) + waf := entitlements.NewWeightAllotmentFetcher() move := models.Move{ Status: models.MoveStatusSUBMITTED, @@ -421,7 +428,7 @@ func (suite *HandlerSuite) TestGetMoveQueuesBranchFilter() { mockUnlocker := movelocker.NewMoveUnlocker() handler := GetMovesQueueHandler{ handlerConfig, - order.NewOrderFetcher(), + order.NewOrderFetcher(waf), mockUnlocker, officeusercreator.NewOfficeUserFetcherPop(), } @@ -447,6 +454,7 @@ func (suite *HandlerSuite) TestGetMoveQueuesHandlerStatuses() { officeUser.User.Roles = append(officeUser.User.Roles, roles.Role{ RoleType: roles.RoleTypeTOO, }) + waf := entitlements.NewWeightAllotmentFetcher() // Default Origin Duty Location GBLOC is KKFA hhgMove := factory.BuildSubmittedMove(suite.DB(), nil, nil) @@ -509,7 +517,7 @@ func (suite *HandlerSuite) TestGetMoveQueuesHandlerStatuses() { mockUnlocker := movelocker.NewMoveUnlocker() handler := GetMovesQueueHandler{ handlerConfig, - order.NewOrderFetcher(), + order.NewOrderFetcher(waf), mockUnlocker, officeusercreator.NewOfficeUserFetcherPop(), } @@ -571,6 +579,7 @@ func (suite *HandlerSuite) TestGetMoveQueuesHandlerFilters() { officeUser.User.Roles = append(officeUser.User.Roles, roles.Role{ RoleType: roles.RoleTypeTOO, }) + waf := entitlements.NewWeightAllotmentFetcher() submittedMove := models.Move{ Status: models.MoveStatusSUBMITTED, @@ -663,7 +672,7 @@ func (suite *HandlerSuite) TestGetMoveQueuesHandlerFilters() { mockUnlocker := movelocker.NewMoveUnlocker() handler := GetMovesQueueHandler{ handlerConfig, - order.NewOrderFetcher(), + order.NewOrderFetcher(waf), mockUnlocker, officeusercreator.NewOfficeUserFetcherPop(), } @@ -824,6 +833,7 @@ func (suite *HandlerSuite) TestGetMoveQueuesHandlerCustomerInfoFilters() { }, }, }, nil) + waf := entitlements.NewWeightAllotmentFetcher() dutyLocation2 := factory.BuildDutyLocation(suite.DB(), nil, nil) @@ -920,7 +930,7 @@ func (suite *HandlerSuite) TestGetMoveQueuesHandlerCustomerInfoFilters() { mockUnlocker := movelocker.NewMoveUnlocker() handler := GetMovesQueueHandler{ handlerConfig, - order.NewOrderFetcher(), + order.NewOrderFetcher(waf), mockUnlocker, officeusercreator.NewOfficeUserFetcherPop(), } @@ -1056,6 +1066,7 @@ func (suite *HandlerSuite) TestGetMoveQueuesHandlerCustomerInfoFilters() { func (suite *HandlerSuite) TestGetMoveQueuesHandlerUnauthorizedRole() { officeUser := factory.BuildOfficeUserWithRoles(nil, nil, []roles.RoleType{roles.RoleTypeTIO}) + waf := entitlements.NewWeightAllotmentFetcher() request := httptest.NewRequest("GET", "/queues/moves", nil) request = suite.AuthenticateOfficeRequest(request, officeUser) @@ -1066,7 +1077,7 @@ func (suite *HandlerSuite) TestGetMoveQueuesHandlerUnauthorizedRole() { mockUnlocker := movelocker.NewMoveUnlocker() handler := GetMovesQueueHandler{ handlerConfig, - order.NewOrderFetcher(), + order.NewOrderFetcher(waf), mockUnlocker, officeusercreator.NewOfficeUserFetcherPop(), } @@ -1087,6 +1098,7 @@ func (suite *HandlerSuite) TestGetMoveQueuesHandlerUnauthorizedUser() { serviceUser.User.Roles = append(serviceUser.User.Roles, roles.Role{ RoleType: roles.RoleTypeCustomer, }) + waf := entitlements.NewWeightAllotmentFetcher() request := httptest.NewRequest("GET", "/queues/moves", nil) request = suite.AuthenticateRequest(request, serviceUser) @@ -1097,7 +1109,7 @@ func (suite *HandlerSuite) TestGetMoveQueuesHandlerUnauthorizedUser() { mockUnlocker := movelocker.NewMoveUnlocker() handler := GetMovesQueueHandler{ handlerConfig, - order.NewOrderFetcher(), + order.NewOrderFetcher(waf), mockUnlocker, officeusercreator.NewOfficeUserFetcherPop(), } @@ -1118,6 +1130,7 @@ func (suite *HandlerSuite) TestGetMoveQueuesHandlerEmptyResults() { officeUser.User.Roles = append(officeUser.User.Roles, roles.Role{ RoleType: roles.RoleTypeTOO, }) + waf := entitlements.NewWeightAllotmentFetcher() // Create an order with an origin duty location outside of office user GBLOC excludedMove := factory.BuildMove(suite.DB(), []factory.Customization{ @@ -1149,7 +1162,7 @@ func (suite *HandlerSuite) TestGetMoveQueuesHandlerEmptyResults() { mockUnlocker := movelocker.NewMoveUnlocker() handler := GetMovesQueueHandler{ handlerConfig, - order.NewOrderFetcher(), + order.NewOrderFetcher(waf), mockUnlocker, officeusercreator.NewOfficeUserFetcherPop(), } @@ -1463,6 +1476,7 @@ type servicesCounselingSubtestData struct { func (suite *HandlerSuite) makeServicesCounselingSubtestData() (subtestData *servicesCounselingSubtestData) { subtestData = &servicesCounselingSubtestData{} subtestData.officeUser = factory.BuildOfficeUserWithRoles(suite.DB(), factory.GetTraitActiveOfficeUser(), []roles.RoleType{roles.RoleTypeServicesCounselor}) + waf := entitlements.NewWeightAllotmentFetcher() submittedAt := time.Date(2021, 03, 15, 0, 0, 0, 0, time.UTC) // Default Origin Duty Location GBLOC is KKFA @@ -1659,7 +1673,7 @@ func (suite *HandlerSuite) makeServicesCounselingSubtestData() (subtestData *ser mockUnlocker := movelocker.NewMoveUnlocker() subtestData.handler = GetServicesCounselingQueueHandler{ handlerConfig, - order.NewOrderFetcher(), + order.NewOrderFetcher(waf), mockUnlocker, officeusercreator.NewOfficeUserFetcherPop(), } diff --git a/pkg/handlers/internalapi/api.go b/pkg/handlers/internalapi/api.go index dfbad6e2ba9..31010c7e3da 100644 --- a/pkg/handlers/internalapi/api.go +++ b/pkg/handlers/internalapi/api.go @@ -17,6 +17,7 @@ import ( "github.com/transcom/mymove/pkg/services/address" boatshipment "github.com/transcom/mymove/pkg/services/boat_shipment" dateservice "github.com/transcom/mymove/pkg/services/calendar" + "github.com/transcom/mymove/pkg/services/entitlements" "github.com/transcom/mymove/pkg/services/fetch" "github.com/transcom/mymove/pkg/services/ghcrateengine" mobilehomeshipment "github.com/transcom/mymove/pkg/services/mobile_home_shipment" @@ -57,6 +58,7 @@ func NewInternalAPI(handlerConfig handlers.HandlerConfig) *internalops.MymoveAPI builder := query.NewQueryBuilder() fetcher := fetch.NewFetcher(builder) moveRouter := move.NewMoveRouter() + waf := entitlements.NewWeightAllotmentFetcher() uploadCreator := upload.NewUploadCreator(handlerConfig.FileStorer()) ppmEstimator := ppmshipment.NewEstimatePPM(handlerConfig.DTODPlanner(), &paymentrequesthelper.RequestPaymentHelper{}) ppmCloseoutFetcher := ppmcloseout.NewPPMCloseoutFetcher(handlerConfig.DTODPlanner(), &paymentrequesthelper.RequestPaymentHelper{}, ppmEstimator) @@ -174,12 +176,11 @@ func NewInternalAPI(handlerConfig handlers.HandlerConfig) *internalops.MymoveAPI internalAPI.UploadsDeleteUploadHandler = DeleteUploadHandler{handlerConfig, upload.NewUploadInformationFetcher()} internalAPI.UploadsDeleteUploadsHandler = DeleteUploadsHandler{handlerConfig} - internalAPI.QueuesShowQueueHandler = ShowQueueHandler{handlerConfig} internalAPI.OfficeApproveMoveHandler = ApproveMoveHandler{handlerConfig, moveRouter} internalAPI.OfficeApproveReimbursementHandler = ApproveReimbursementHandler{handlerConfig} internalAPI.OfficeCancelMoveHandler = CancelMoveHandler{handlerConfig, moveRouter} - internalAPI.EntitlementsIndexEntitlementsHandler = IndexEntitlementsHandler{handlerConfig} + internalAPI.EntitlementsIndexEntitlementsHandler = IndexEntitlementsHandler{handlerConfig, waf} internalAPI.CalendarShowAvailableMoveDatesHandler = ShowAvailableMoveDatesHandler{handlerConfig} @@ -235,7 +236,7 @@ func NewInternalAPI(handlerConfig handlers.HandlerConfig) *internalops.MymoveAPI fetcher, handlerConfig.DTODPlanner(), moveRouter, - move.NewMoveWeights(mtoshipment.NewShipmentReweighRequester()), + move.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf), handlerConfig.NotificationSender(), paymentRequestShipmentRecalculator, addressUpdater, diff --git a/pkg/handlers/internalapi/api_test.go b/pkg/handlers/internalapi/api_test.go index ef45c139cd0..e4c243a6dd2 100644 --- a/pkg/handlers/internalapi/api_test.go +++ b/pkg/handlers/internalapi/api_test.go @@ -5,7 +5,9 @@ import ( "github.com/stretchr/testify/suite" + "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/handlers" + "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/notifications" storageTest "github.com/transcom/mymove/pkg/storage/test" "github.com/transcom/mymove/pkg/testingsuite" @@ -25,6 +27,19 @@ type HandlerSuite struct { handlers.BaseHandlerTestSuite } +func (suite *HandlerSuite) SetupSuite() { + suite.PreloadData(func() { + factory.FetchOrBuildCountry(suite.DB(), []factory.Customization{ + { + Model: models.Country{ + Country: "US", + CountryName: "UNITED STATES", + }, + }, + }, nil) + }) +} + // AfterTest completes tests by trying to close open files func (suite *HandlerSuite) AfterTest() { for _, file := range suite.TestFilesToClose() { diff --git a/pkg/handlers/internalapi/entitlements.go b/pkg/handlers/internalapi/entitlements.go index 3ae75c18eb4..ed50edbb132 100644 --- a/pkg/handlers/internalapi/entitlements.go +++ b/pkg/handlers/internalapi/entitlements.go @@ -8,6 +8,7 @@ import ( "github.com/transcom/mymove/pkg/gen/internalmessages" "github.com/transcom/mymove/pkg/handlers" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" ) func payloadForEntitlementModel(e models.WeightAllotment) internalmessages.WeightAllotment { @@ -28,13 +29,17 @@ func payloadForEntitlementModel(e models.WeightAllotment) internalmessages.Weigh // IndexEntitlementsHandler indexes entitlements type IndexEntitlementsHandler struct { handlers.HandlerConfig + services.WeightAllotmentFetcher } // Handle is the handler func (h IndexEntitlementsHandler) Handle(params entitlementop.IndexEntitlementsParams) middleware.Responder { return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, func(appCtx appcontext.AppContext) (middleware.Responder, error) { - entitlements := models.AllWeightAllotments() + entitlements, err := h.WeightAllotmentFetcher.GetAllWeightAllotments(appCtx) + if err != nil { + return entitlementop.NewIndexEntitlementsInternalServerError(), nil + } payload := make(map[string]internalmessages.WeightAllotment) for k, v := range entitlements { grade := string(k) diff --git a/pkg/handlers/internalapi/entitlements_test.go b/pkg/handlers/internalapi/entitlements_test.go index ec6829443c2..0b896da13b9 100644 --- a/pkg/handlers/internalapi/entitlements_test.go +++ b/pkg/handlers/internalapi/entitlements_test.go @@ -5,11 +5,13 @@ import ( "github.com/transcom/mymove/pkg/factory" entitlementop "github.com/transcom/mymove/pkg/gen/internalapi/internaloperations/entitlements" + "github.com/transcom/mymove/pkg/services/entitlements" ) func (suite *HandlerSuite) TestIndexEntitlementsHandlerReturns200() { // Given: a set of orders, a move, user, servicemember and a PPM + waf := entitlements.NewWeightAllotmentFetcher() ppm := factory.BuildMinimalPPMShipment(suite.DB(), nil, nil) move := factory.BuildMove(suite.DB(), nil, nil) mtoShipment := factory.BuildMTOShipmentWithMove(&move, suite.DB(), nil, nil) @@ -24,7 +26,7 @@ func (suite *HandlerSuite) TestIndexEntitlementsHandlerReturns200() { } // And: index entitlements endpoint is hit - handler := IndexEntitlementsHandler{suite.HandlerConfig()} + handler := IndexEntitlementsHandler{suite.HandlerConfig(), waf} response := handler.Handle(params) // Then: expect a 200 status code diff --git a/pkg/handlers/internalapi/move_queue_items.go b/pkg/handlers/internalapi/move_queue_items.go deleted file mode 100644 index 071ae2fba79..00000000000 --- a/pkg/handlers/internalapi/move_queue_items.go +++ /dev/null @@ -1,125 +0,0 @@ -package internalapi - -import ( - "sort" - "strings" - "time" - - "github.com/go-openapi/runtime/middleware" - "github.com/gofrs/uuid" - "go.uber.org/zap" - - "github.com/transcom/mymove/pkg/appcontext" - "github.com/transcom/mymove/pkg/apperror" - queueop "github.com/transcom/mymove/pkg/gen/internalapi/internaloperations/queues" - "github.com/transcom/mymove/pkg/gen/internalmessages" - "github.com/transcom/mymove/pkg/handlers" - "github.com/transcom/mymove/pkg/models" -) - -func payloadForMoveQueueItem(MoveQueueItem models.MoveQueueItem) *internalmessages.MoveQueueItem { - MoveQueueItemPayload := internalmessages.MoveQueueItem{ - ID: handlers.FmtUUID(MoveQueueItem.ID), - CreatedAt: handlers.FmtDateTime(MoveQueueItem.CreatedAt), - Edipi: models.StringPointer(MoveQueueItem.Edipi), - Grade: MoveQueueItem.Grade, - CustomerName: models.StringPointer(MoveQueueItem.CustomerName), - Locator: models.StringPointer(MoveQueueItem.Locator), - Status: models.StringPointer(MoveQueueItem.Status), - PpmStatus: handlers.FmtStringPtr(MoveQueueItem.PpmStatus), - OrdersType: MoveQueueItem.OrdersType, - MoveDate: handlers.FmtDatePtr(MoveQueueItem.MoveDate), - SubmittedDate: handlers.FmtDateTimePtr(MoveQueueItem.SubmittedDate), - LastModifiedDate: handlers.FmtDateTime(MoveQueueItem.LastModifiedDate), - OriginDutyLocationName: models.StringPointer(MoveQueueItem.OriginDutyLocationName), - DestinationDutyLocationName: models.StringPointer(MoveQueueItem.DestinationDutyLocationName), - PmSurveyConductedDate: handlers.FmtDateTimePtr(MoveQueueItem.PmSurveyConductedDate), - OriginGbloc: handlers.FmtStringPtr(MoveQueueItem.OriginGBLOC), - DestinationGbloc: handlers.FmtStringPtr(MoveQueueItem.DestinationGBLOC), - DeliveredDate: handlers.FmtDateTimePtr(MoveQueueItem.DeliveredDate), - InvoiceApprovedDate: handlers.FmtDateTimePtr(MoveQueueItem.InvoiceApprovedDate), - WeightAllotment: payloadForWeightAllotmentModel(models.GetWeightAllotment(*MoveQueueItem.Grade, *MoveQueueItem.OrdersType)), - BranchOfService: handlers.FmtString(MoveQueueItem.BranchOfService), - ActualMoveDate: handlers.FmtDatePtr(MoveQueueItem.ActualMoveDate), - OriginalMoveDate: handlers.FmtDatePtr(MoveQueueItem.OriginalMoveDate), - } - return &MoveQueueItemPayload -} - -// ShowQueueHandler returns a list of all MoveQueueItems in the moves queue -type ShowQueueHandler struct { - handlers.HandlerConfig -} - -// JSONDate is a time type -type JSONDate time.Time - -// UnmarshalJSON Dates without timestamps need custom unmarshalling -func (j *JSONDate) UnmarshalJSON(b []byte) error { - s := strings.Trim(string(b), "\"") - if s == "null" { - return nil - } - t, err := time.Parse("2006-01-02", s) - if err != nil { - return err - } - *j = JSONDate(t) - return nil -} - -// QueueSitData is SIT data in a queue -type QueueSitData struct { - ID uuid.UUID `json:"id"` - Status string `json:"status"` - ActualStartDate JSONDate `json:"actual_start_date"` - OutDate JSONDate `json:"out_date"` - Location string `json:"location"` -} - -// MoveQueueItems is a set of move queue items -// Implementation of a type and methods in order to use sort.Interface directly. -// This allows us to call sortQueueItemsByLastModifiedDate in the ShowQueueHandler which will -// sort the slice by the LastModfiedDate. Doing it this way allows us to avoid having reflect called -// which should act to speed the sort up. -type MoveQueueItems []models.MoveQueueItem - -func (mqi MoveQueueItems) Less(i, j int) bool { - return mqi[i].LastModifiedDate.Before(mqi[j].LastModifiedDate) -} -func (mqi MoveQueueItems) Len() int { return len(mqi) } -func (mqi MoveQueueItems) Swap(i, j int) { mqi[i], mqi[j] = mqi[j], mqi[i] } - -func sortQueueItemsByLastModifiedDate(moveQueueItems []models.MoveQueueItem) { - sort.Sort(MoveQueueItems(moveQueueItems)) -} - -// Handle retrieves a list of all MoveQueueItems in the system in the moves queue -func (h ShowQueueHandler) Handle(params queueop.ShowQueueParams) middleware.Responder { - return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, - func(appCtx appcontext.AppContext) (middleware.Responder, error) { - - if !appCtx.Session().IsOfficeUser() { - badUserErr := apperror.NewSessionError("User is not an Office user") - return queueop.NewShowQueueForbidden(), badUserErr - } - - lifecycleState := params.QueueType - - MoveQueueItems, err := models.GetMoveQueueItems(appCtx.DB(), lifecycleState) - if err != nil { - appCtx.Logger().Error("Loading Queue", zap.String("State", lifecycleState), zap.Error(err)) - return handlers.ResponseForError(appCtx.Logger(), err), err - } - - // Sorting the slice by LastModifiedDate so that the API results follow suit. - sortQueueItemsByLastModifiedDate(MoveQueueItems) - - MoveQueueItemPayloads := make([]*internalmessages.MoveQueueItem, len(MoveQueueItems)) - for i, MoveQueueItem := range MoveQueueItems { - MoveQueueItemPayload := payloadForMoveQueueItem(MoveQueueItem) - MoveQueueItemPayloads[i] = MoveQueueItemPayload - } - return queueop.NewShowQueueOK().WithPayload(MoveQueueItemPayloads), nil - }) -} diff --git a/pkg/handlers/internalapi/move_queue_items_test.go b/pkg/handlers/internalapi/move_queue_items_test.go deleted file mode 100644 index bc95161d5d0..00000000000 --- a/pkg/handlers/internalapi/move_queue_items_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package internalapi - -import ( - "net/http/httptest" - - "github.com/transcom/mymove/pkg/factory" - queueop "github.com/transcom/mymove/pkg/gen/internalapi/internaloperations/queues" - "github.com/transcom/mymove/pkg/models/roles" -) - -var statusToQueueMap = map[string]string{ - "SUBMITTED": "new", - "APPROVED": "ppm_approved", - "PAYMENT_REQUESTED": "ppm_payment_requested", - "COMPLETED": "ppm_completed", -} - -func (suite *HandlerSuite) TestShowQueueHandlerForbidden() { - for _, queueType := range statusToQueueMap { - - // Given: A non-office user - user := factory.BuildServiceMember(suite.DB(), nil, nil) - - // And: the context contains the auth values - path := "/queues/" + queueType - req := httptest.NewRequest("GET", path, nil) - req = suite.AuthenticateRequest(req, user) - - params := queueop.ShowQueueParams{ - HTTPRequest: req, - QueueType: queueType, - } - - // And: show Queue is queried - showHandler := ShowQueueHandler{suite.HandlerConfig()} - showResponse := showHandler.Handle(params) - - // Then: Expect a 403 status code - suite.Assertions.IsType(&queueop.ShowQueueForbidden{}, showResponse) - } -} - -func (suite *HandlerSuite) TestShowQueueHandlerNotFound() { - - // Given: An office user - officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) - - // And: the context contains the auth values - queueType := "queue_not_found" - path := "/queues/" + queueType - req := httptest.NewRequest("GET", path, nil) - req = suite.AuthenticateOfficeRequest(req, officeUser) - - params := queueop.ShowQueueParams{ - HTTPRequest: req, - QueueType: queueType, - } - // And: show Queue is queried - showHandler := ShowQueueHandler{suite.HandlerConfig()} - showResponse := showHandler.Handle(params) - - // Then: Expect a 404 status code - suite.CheckResponseNotFound(showResponse) -} diff --git a/pkg/handlers/internalapi/mto_shipment_test.go b/pkg/handlers/internalapi/mto_shipment_test.go index 3d971cc6a38..70636b3c2fc 100644 --- a/pkg/handlers/internalapi/mto_shipment_test.go +++ b/pkg/handlers/internalapi/mto_shipment_test.go @@ -22,6 +22,7 @@ import ( "github.com/transcom/mymove/pkg/services" "github.com/transcom/mymove/pkg/services/address" boatshipment "github.com/transcom/mymove/pkg/services/boat_shipment" + "github.com/transcom/mymove/pkg/services/entitlements" "github.com/transcom/mymove/pkg/services/fetch" "github.com/transcom/mymove/pkg/services/ghcrateengine" mobilehomeshipment "github.com/transcom/mymove/pkg/services/mobile_home_shipment" @@ -736,6 +737,7 @@ func (suite *HandlerSuite) TestUpdateMTOShipmentHandler() { testMTOShipmentObjects := suite.setUpMTOShipmentObjects() planner := &routemocks.Planner{} + waf := entitlements.NewWeightAllotmentFetcher() planner.On("TransitDistance", mock.AnythingOfType("*appcontext.appContext"), @@ -743,7 +745,7 @@ func (suite *HandlerSuite) TestUpdateMTOShipmentHandler() { mock.Anything, ).Return(400, nil) - moveWeights := moverouter.NewMoveWeights(mtoshipment.NewShipmentReweighRequester()) + moveWeights := moverouter.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) // Get shipment payment request recalculator service creator := paymentrequest.NewPaymentRequestCreator(planner, ghcrateengine.NewServiceItemPricer()) diff --git a/pkg/handlers/internalapi/orders.go b/pkg/handlers/internalapi/orders.go index 4e20908ae2d..729ad0e03ff 100644 --- a/pkg/handlers/internalapi/orders.go +++ b/pkg/handlers/internalapi/orders.go @@ -19,6 +19,7 @@ import ( "github.com/transcom/mymove/pkg/handlers/internalapi/internal/payloads" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/services/entitlements" "github.com/transcom/mymove/pkg/storage" "github.com/transcom/mymove/pkg/uploader" ) @@ -195,9 +196,18 @@ func (h CreateOrdersHandler) Handle(params ordersop.CreateOrdersParams) middlewa grade := payload.Grade + if payload.OrdersType == nil { + errMsg := "missing required field: OrdersType" + return handlers.ResponseForError(appCtx.Logger(), errors.New(errMsg)), apperror.NewBadDataError("missing required field: OrdersType") + } + // Calculate the entitlement for the order ordersType := payload.OrdersType - weightAllotment := models.GetWeightAllotment(*grade, *ordersType) + waf := entitlements.NewWeightAllotmentFetcher() + weightAllotment, err := waf.GetWeightAllotment(appCtx, string(*grade), *ordersType) + if err != nil { + return handlers.ResponseForError(appCtx.Logger(), err), err + } weight := weightAllotment.TotalWeightSelf if *payload.HasDependents { weight = weightAllotment.TotalWeightSelfPlusDependents @@ -247,11 +257,6 @@ func (h CreateOrdersHandler) Handle(params ordersop.CreateOrdersParams) middlewa deptIndicator = &converted } - if payload.OrdersType == nil { - errMsg := "missing required field: OrdersType" - return handlers.ResponseForError(appCtx.Logger(), errors.New(errMsg)), apperror.NewBadDataError("missing required field: OrdersType") - } - contractor, err := models.FetchGHCPrimeContractor(appCtx.DB()) if err != nil { return handlers.ResponseForError(appCtx.Logger(), err), err @@ -442,7 +447,11 @@ func (h UpdateOrdersHandler) Handle(params ordersop.UpdateOrdersParams) middlewa // Check if the grade or dependents are receiving an update if hasEntitlementChanged(order, payload.OrdersType, payload.Grade, payload.DependentsUnderTwelve, payload.DependentsTwelveAndOver, payload.AccompaniedTour) { - weightAllotment := models.GetWeightAllotment(*payload.Grade, *payload.OrdersType) + waf := entitlements.NewWeightAllotmentFetcher() + weightAllotment, err := waf.GetWeightAllotment(appCtx, string(*payload.Grade), *payload.OrdersType) + if err != nil { + return handlers.ResponseForError(appCtx.Logger(), err), err + } weight := weightAllotment.TotalWeightSelf if *payload.HasDependents { weight = weightAllotment.TotalWeightSelfPlusDependents diff --git a/pkg/handlers/internalapi/orders_test.go b/pkg/handlers/internalapi/orders_test.go index 59df8daf475..bf1b4a4303c 100644 --- a/pkg/handlers/internalapi/orders_test.go +++ b/pkg/handlers/internalapi/orders_test.go @@ -17,6 +17,7 @@ import ( "github.com/transcom/mymove/pkg/handlers" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/services/entitlements" "github.com/transcom/mymove/pkg/services/mocks" "github.com/transcom/mymove/pkg/services/move" orderservice "github.com/transcom/mymove/pkg/services/order" @@ -25,16 +26,6 @@ import ( ) func (suite *HandlerSuite) TestCreateOrder() { - suite.PreloadData(func() { - factory.FetchOrBuildCountry(suite.DB(), []factory.Customization{ - { - Model: models.Country{ - Country: "US", - CountryName: "UNITED STATES", - }, - }, - }, nil) - }) sm := factory.BuildExtendedServiceMember(suite.DB(), nil, nil) suite.Run("can create conus and oconus orders", func() { testCases := []struct { @@ -93,6 +84,7 @@ func (suite *HandlerSuite) TestCreateOrder() { DepartmentIndicator: internalmessages.NewDeptIndicator(deptIndicator), Grade: models.ServiceMemberGradeE1.Pointer(), } + if tc.isOconus { payload.AccompaniedTour = models.BoolPointer(true) payload.DependentsTwelveAndOver = models.Int64Pointer(5) @@ -584,7 +576,7 @@ func (suite *HandlerSuite) TestUploadAmendedOrdersHandlerIntegration() { } func (suite *HandlerSuite) TestUpdateOrdersHandler() { - + waf := entitlements.NewWeightAllotmentFetcher() suite.Run("Can update CONUS and OCONUS orders", func() { testCases := []struct { isOconus bool @@ -689,13 +681,15 @@ func (suite *HandlerSuite) TestUpdateOrdersHandler() { suite.NoError(err) suite.Equal(payload.Grade, updatedOrder.Grade) suite.Equal(*okResponse.Payload.AuthorizedWeight, int64(7000)) // E4 authorized weight is 7000, make sure we return that in the response - expectedUpdatedOrderWeightAllotment := models.GetWeightAllotment(*updatedOrder.Grade, updatedOrder.OrdersType) + expectedUpdatedOrderWeightAllotment, err := waf.GetWeightAllotment(suite.AppContextForTest(), string(*updatedOrder.Grade), updatedOrder.OrdersType) + suite.NoError(err) expectedUpdatedOrderAuthorizedWeight := expectedUpdatedOrderWeightAllotment.TotalWeightSelf if *payload.HasDependents { expectedUpdatedOrderAuthorizedWeight = expectedUpdatedOrderWeightAllotment.TotalWeightSelfPlusDependents } - expectedOriginalOrderWeightAllotment := models.GetWeightAllotment(*order.Grade, updatedOrder.OrdersType) + expectedOriginalOrderWeightAllotment, err := waf.GetWeightAllotment(suite.AppContextForTest(), string(*order.Grade), updatedOrder.OrdersType) + suite.NoError(err) expectedOriginalOrderAuthorizedWeight := expectedOriginalOrderWeightAllotment.TotalWeightSelf if *payload.HasDependents { expectedUpdatedOrderAuthorizedWeight = expectedOriginalOrderWeightAllotment.TotalWeightSelfPlusDependents diff --git a/pkg/handlers/internalapi/ppm_shipment_test.go b/pkg/handlers/internalapi/ppm_shipment_test.go index d01813b5ef8..b9e20809025 100644 --- a/pkg/handlers/internalapi/ppm_shipment_test.go +++ b/pkg/handlers/internalapi/ppm_shipment_test.go @@ -798,7 +798,7 @@ func (suite *HandlerSuite) TestResubmitPPMShipmentDocumentationHandlerIntegratio var shipmentNeedsCloseout models.PPMShipment var needsCloseoutSM models.ServiceMember - suite.PreloadData(func() { + setupPPMData := func() { shipmentNeedsResubmitted = factory.BuildPPMShipmentThatNeedsToBeResubmitted(suite.DB(), userUploader, nil) shipmentNeedsResubmitted.SubmittedAt = &submissionTime suite.NoError(suite.DB().Save(&shipmentNeedsResubmitted)) @@ -806,7 +806,7 @@ func (suite *HandlerSuite) TestResubmitPPMShipmentDocumentationHandlerIntegratio shipmentNeedsCloseout = factory.BuildPPMShipmentThatNeedsCloseout(suite.DB(), nil, nil) needsCloseoutSM = shipmentNeedsCloseout.Shipment.MoveTaskOrder.Orders.ServiceMember - }) + } setUpParamsAndHandler := func(ppmShipment models.PPMShipment, serviceMember models.ServiceMember, payload *internalmessages.SavePPMShipmentSignedCertification) (ppmops.ResubmitPPMShipmentDocumentationParams, ResubmitPPMShipmentDocumentationHandler) { endpoint := fmt.Sprintf( @@ -840,6 +840,7 @@ func (suite *HandlerSuite) TestResubmitPPMShipmentDocumentationHandlerIntegratio } suite.Run("Returns an error if the PPM shipment is not found", func() { + setupPPMData() shipmentWithUnknownID := models.PPMShipment{ ID: uuid.Must(uuid.NewV4()), SignedCertification: &models.SignedCertification{ @@ -863,6 +864,7 @@ func (suite *HandlerSuite) TestResubmitPPMShipmentDocumentationHandlerIntegratio }) suite.Run("Returns an error if the signed certification is not found", func() { + setupPPMData() shipmentWithUnknownSignedCert := models.PPMShipment{ ID: shipmentNeedsResubmitted.ID, SignedCertification: &models.SignedCertification{ @@ -886,6 +888,7 @@ func (suite *HandlerSuite) TestResubmitPPMShipmentDocumentationHandlerIntegratio }) suite.Run("Returns an error if the PPM shipment is not in the right status", func() { + setupPPMData() params, handler := setUpParamsAndHandler(shipmentNeedsCloseout, needsCloseoutSM, &internalmessages.SavePPMShipmentSignedCertification{ CertificationText: handlers.FmtString("certification text"), Signature: handlers.FmtString("signature"), @@ -909,6 +912,7 @@ func (suite *HandlerSuite) TestResubmitPPMShipmentDocumentationHandlerIntegratio }) suite.Run("Can successfully resubmit a PPM shipment for close out", func() { + setupPPMData() newCertText := "new certification text" newSignature := "new signature" newSignDate := time.Now().AddDate(0, 0, 1) diff --git a/pkg/handlers/internalapi/weight_allotment.go b/pkg/handlers/internalapi/weight_allotment.go deleted file mode 100644 index 90e2058c53f..00000000000 --- a/pkg/handlers/internalapi/weight_allotment.go +++ /dev/null @@ -1,16 +0,0 @@ -package internalapi - -import ( - "github.com/transcom/mymove/pkg/gen/internalmessages" - "github.com/transcom/mymove/pkg/handlers" - "github.com/transcom/mymove/pkg/models" -) - -func payloadForWeightAllotmentModel(allotment models.WeightAllotment) *internalmessages.WeightAllotment { - return &internalmessages.WeightAllotment{ - ProGearWeight: handlers.FmtInt64(int64(allotment.ProGearWeight)), - ProGearWeightSpouse: handlers.FmtInt64(int64(allotment.ProGearWeightSpouse)), - TotalWeightSelf: handlers.FmtInt64(int64(allotment.TotalWeightSelf)), - TotalWeightSelfPlusDependents: handlers.FmtInt64(int64(allotment.TotalWeightSelfPlusDependents)), - } -} diff --git a/pkg/handlers/pptasapi/api.go b/pkg/handlers/pptasapi/api.go index 7278f68c0dd..b3e6a418831 100644 --- a/pkg/handlers/pptasapi/api.go +++ b/pkg/handlers/pptasapi/api.go @@ -9,6 +9,7 @@ import ( pptasops "github.com/transcom/mymove/pkg/gen/pptasapi/pptasoperations" "github.com/transcom/mymove/pkg/handlers" paymentrequesthelper "github.com/transcom/mymove/pkg/payment_request" + "github.com/transcom/mymove/pkg/services/entitlements" lineofaccounting "github.com/transcom/mymove/pkg/services/line_of_accounting" "github.com/transcom/mymove/pkg/services/move" "github.com/transcom/mymove/pkg/services/ppmshipment" @@ -18,6 +19,7 @@ import ( func NewPPTASAPI(handlerConfig handlers.HandlerConfig) *pptasops.MymoveAPI { pptasSpec, err := loads.Analyzed(pptasapi.SwaggerJSON, "") + waf := entitlements.NewWeightAllotmentFetcher() if err != nil { log.Fatalln(err) } @@ -31,7 +33,7 @@ func NewPPTASAPI(handlerConfig handlers.HandlerConfig) *pptasops.MymoveAPI { pptasAPI.MovesPptasReportsHandler = PPTASReportsHandler{ HandlerConfig: handlerConfig, - PPTASReportListFetcher: report.NewPPTASReportListFetcher(ppmEstimator, moveFetcher, tacFetcher, loaFetcher), + PPTASReportListFetcher: report.NewPPTASReportListFetcher(ppmEstimator, moveFetcher, tacFetcher, loaFetcher, waf), } return pptasAPI diff --git a/pkg/handlers/primeapi/api.go b/pkg/handlers/primeapi/api.go index a96b47ac5f2..4eab1923c9f 100644 --- a/pkg/handlers/primeapi/api.go +++ b/pkg/handlers/primeapi/api.go @@ -11,6 +11,7 @@ import ( paperwork "github.com/transcom/mymove/pkg/paperwork" paymentrequesthelper "github.com/transcom/mymove/pkg/payment_request" "github.com/transcom/mymove/pkg/services/address" + "github.com/transcom/mymove/pkg/services/entitlements" "github.com/transcom/mymove/pkg/services/fetch" "github.com/transcom/mymove/pkg/services/ghcrateengine" "github.com/transcom/mymove/pkg/services/move" @@ -41,13 +42,15 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primeoperations.MymoveAP if err != nil { log.Fatalln(err) } + waf := entitlements.NewWeightAllotmentFetcher() + primeAPI := primeoperations.NewMymoveAPI(primeSpec) queryBuilder := query.NewQueryBuilder() moveRouter := move.NewMoveRouter() addressCreator := address.NewAddressCreator() portLocationFetcher := portlocation.NewPortLocationFetcher() shipmentFetcher := mtoshipment.NewMTOShipmentFetcher() - moveWeights := move.NewMoveWeights(mtoshipment.NewShipmentReweighRequester()) + moveWeights := move.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) uploadCreator := upload.NewUploadCreator(handlerConfig.FileStorer()) ppmEstimator := ppmshipment.NewEstimatePPM(handlerConfig.DTODPlanner(), &paymentrequesthelper.RequestPaymentHelper{}) serviceItemUpdater := mtoserviceitem.NewMTOServiceItemUpdater(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) @@ -79,12 +82,12 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primeoperations.MymoveAP primeAPI.MoveTaskOrderListMovesHandler = ListMovesHandler{ handlerConfig, - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } primeAPI.MoveTaskOrderGetMoveTaskOrderHandler = GetMoveTaskOrderHandler{ handlerConfig, - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } primeAPI.MoveTaskOrderCreateExcessWeightRecordHandler = CreateExcessWeightRecordHandler{ @@ -187,7 +190,7 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primeoperations.MymoveAP primeAPI.MoveTaskOrderDownloadMoveOrderHandler = DownloadMoveOrderHandler{ handlerConfig, move.NewMoveSearcher(), - order.NewOrderFetcher(), + order.NewOrderFetcher(waf), primeDownloadMoveUploadPDFGenerator, } diff --git a/pkg/handlers/primeapi/move_task_order_test.go b/pkg/handlers/primeapi/move_task_order_test.go index b659b9386d9..f1005d9f41e 100644 --- a/pkg/handlers/primeapi/move_task_order_test.go +++ b/pkg/handlers/primeapi/move_task_order_test.go @@ -21,6 +21,7 @@ import ( "github.com/transcom/mymove/pkg/models" routemocks "github.com/transcom/mymove/pkg/route/mocks" "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/services/entitlements" "github.com/transcom/mymove/pkg/services/fetch" "github.com/transcom/mymove/pkg/services/ghcrateengine" "github.com/transcom/mymove/pkg/services/mocks" @@ -36,6 +37,8 @@ import ( ) func (suite *HandlerSuite) TestListMovesHandler() { + waf := entitlements.NewWeightAllotmentFetcher() + suite.Run("Test returns updated with no amendments count", func() { now := time.Now() lastFetch := now.Add(-time.Second) @@ -59,7 +62,7 @@ func (suite *HandlerSuite) TestListMovesHandler() { // Validate incoming payload: no body to validate // make the request - handler := ListMovesHandler{HandlerConfig: handlerConfig, MoveTaskOrderFetcher: movetaskorder.NewMoveTaskOrderFetcher()} + handler := ListMovesHandler{HandlerConfig: handlerConfig, MoveTaskOrderFetcher: movetaskorder.NewMoveTaskOrderFetcher(waf)} response := handler.Handle(params) suite.IsNotErrResponse(response) @@ -131,7 +134,7 @@ func (suite *HandlerSuite) TestListMovesHandler() { // Validate incoming payload: no body to validate // make the request - handler := ListMovesHandler{HandlerConfig: handlerConfig, MoveTaskOrderFetcher: movetaskorder.NewMoveTaskOrderFetcher()} + handler := ListMovesHandler{HandlerConfig: handlerConfig, MoveTaskOrderFetcher: movetaskorder.NewMoveTaskOrderFetcher(waf)} response := handler.Handle(params) suite.IsNotErrResponse(response) @@ -150,6 +153,7 @@ func (suite *HandlerSuite) TestListMovesHandler() { func (suite *HandlerSuite) TestGetMoveTaskOrder() { request := httptest.NewRequest("GET", "/move-task-orders/{moveTaskOrderID}", nil) + waf := entitlements.NewWeightAllotmentFetcher() verifyAddressFields := func(address *models.Address, payload *primemessages.Address) { suite.Equal(address.ID.String(), payload.ID.String()) @@ -169,7 +173,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Success with Prime-available move by ID", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) @@ -200,7 +204,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Success with Prime-available move by Locator", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) params := movetaskorderops.GetMoveTaskOrderParams{ @@ -230,7 +234,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Success returns reweighs on shipments if they exist", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) params := movetaskorderops.GetMoveTaskOrderParams{ @@ -284,7 +288,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Success - returns sit extensions on shipments if they exist", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) params := movetaskorderops.GetMoveTaskOrderParams{ @@ -342,7 +346,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Success - filters shipments handled by an external vendor", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) @@ -397,7 +401,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Success - returns shipment with attached PpmShipment", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) ppmShipment := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ @@ -433,7 +437,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { // This tests fields that aren't other structs and Addresses handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) destinationAddress := factory.BuildAddress(suite.DB(), nil, nil) @@ -548,7 +552,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Success - returns all the fields associated with StorageFacility within MtoShipments", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) params := movetaskorderops.GetMoveTaskOrderParams{ @@ -604,7 +608,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Success - returns all the fields associated with Agents within MtoShipments", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) params := movetaskorderops.GetMoveTaskOrderParams{ @@ -655,7 +659,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Success - return all base fields assoicated with the getMoveTaskOrder", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } now := time.Now() aWeekAgo := now.AddDate(0, 0, -7) @@ -707,7 +711,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Success - return all Order fields assoicated with the getMoveTaskOrder", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } currentAddress := factory.BuildAddress(suite.DB(), nil, nil) successMove := factory.BuildAvailableToPrimeMove(suite.DB(), []factory.Customization{ @@ -794,10 +798,10 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.NotNil(ordersPayload.OriginDutyLocation.ETag) }) - suite.Run("Success - return all PaymentRequests fields assoicated with the getMoveTaskOrder", func() { + suite.Run("Success - return all PaymentRequests fields associated with the getMoveTaskOrder", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) @@ -938,10 +942,20 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.NoError(movePayload.Validate(strfmt.Default)) suite.Len(movePayload.PaymentRequests, 2) - paymentRequestPayload := movePayload.PaymentRequests[0] + var paymentRequestPayload *primemessages.PaymentRequest + // Correctly grab the payment request by id + for _, pr := range movePayload.PaymentRequests { + if pr.ID.String() == paymentRequest.ID.String() { + paymentRequestPayload = pr + break + } + } + suite.NotNil(paymentRequestPayload) suite.Equal(paymentRequest.ID.String(), paymentRequestPayload.ID.String()) suite.Equal(successMove.ID.String(), paymentRequestPayload.MoveTaskOrderID.String()) suite.Equal(paymentRequest.IsFinal, *paymentRequestPayload.IsFinal) + suite.NotNil(paymentRequest.RejectionReason) + suite.NotNil(paymentRequestPayload.RejectionReason) suite.Equal(*paymentRequest.RejectionReason, *paymentRequestPayload.RejectionReason) suite.Equal(paymentRequest.Status.String(), string(paymentRequestPayload.Status)) suite.Equal(paymentRequest.PaymentRequestNumber, paymentRequestPayload.PaymentRequestNumber) @@ -989,7 +1003,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Success - return all MTOServiceItemBasic fields assoicated with the getMoveTaskOrder", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) @@ -1068,7 +1082,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Success - return all MTOServiceItemOriginSIT fields assoicated with the getMoveTaskOrder", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) @@ -1182,7 +1196,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Success - return all MTOServiceItemDestSIT fields assoicated with the getMoveTaskOrder", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) @@ -1307,7 +1321,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Success - return all MTOServiceItemShuttle fields assoicated with the getMoveTaskOrder", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) @@ -1393,7 +1407,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Success - return all MTOServiceItemDomesticCrating fields assoicated with the getMoveTaskOrder", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) @@ -1521,7 +1535,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Failure 'Not Found' for non-available move", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } failureMove := factory.BuildMove(suite.DB(), nil, nil) // default is not available to Prime params := movetaskorderops.GetMoveTaskOrderParams{ diff --git a/pkg/handlers/primeapi/mto_shipment_test.go b/pkg/handlers/primeapi/mto_shipment_test.go index 917e10cdfc6..7e5539b18fd 100644 --- a/pkg/handlers/primeapi/mto_shipment_test.go +++ b/pkg/handlers/primeapi/mto_shipment_test.go @@ -22,6 +22,7 @@ import ( routemocks "github.com/transcom/mymove/pkg/route/mocks" "github.com/transcom/mymove/pkg/services" "github.com/transcom/mymove/pkg/services/address" + "github.com/transcom/mymove/pkg/services/entitlements" "github.com/transcom/mymove/pkg/services/fetch" "github.com/transcom/mymove/pkg/services/ghcrateengine" "github.com/transcom/mymove/pkg/services/mocks" @@ -199,6 +200,8 @@ func (suite *HandlerSuite) TestUpdateMTOShipmentStatusHandler() { builder := query.NewQueryBuilder() fetcher := fetch.NewFetcher(builder) planner := &routemocks.Planner{} + waf := entitlements.NewWeightAllotmentFetcher() + planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), mock.Anything, @@ -209,7 +212,7 @@ func (suite *HandlerSuite) TestUpdateMTOShipmentStatusHandler() { moveRouter := moveservices.NewMoveRouter() addressUpdater := address.NewAddressUpdater() addressCreator := address.NewAddressCreator() - moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester()) + moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) // Get shipment payment request recalculator service creator := paymentrequest.NewPaymentRequestCreator(planner, ghcrateengine.NewServiceItemPricer()) statusUpdater := paymentrequest.NewPaymentRequestStatusUpdater(query.NewQueryBuilder()) diff --git a/pkg/handlers/primeapi/payloads/model_to_payload.go b/pkg/handlers/primeapi/payloads/model_to_payload.go index 7fb7aaf2447..e4b759cd0e5 100644 --- a/pkg/handlers/primeapi/payloads/model_to_payload.go +++ b/pkg/handlers/primeapi/payloads/model_to_payload.go @@ -182,9 +182,6 @@ func Order(order *models.Order) *primemessages.Order { } destinationDutyLocation := DutyLocation(&order.NewDutyLocation) originDutyLocation := DutyLocation(order.OriginDutyLocation) - if order.Grade != nil && order.Entitlement != nil { - order.Entitlement.SetWeightAllotment(string(*order.Grade), order.OrdersType) - } var grade string if order.Grade != nil { diff --git a/pkg/handlers/primeapi/payloads/model_to_payload_test.go b/pkg/handlers/primeapi/payloads/model_to_payload_test.go index dc0707e5b06..a643a414699 100644 --- a/pkg/handlers/primeapi/payloads/model_to_payload_test.go +++ b/pkg/handlers/primeapi/payloads/model_to_payload_test.go @@ -13,6 +13,7 @@ import ( "github.com/transcom/mymove/pkg/gen/primemessages" "github.com/transcom/mymove/pkg/handlers" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services/entitlements" "github.com/transcom/mymove/pkg/storage/test" "github.com/transcom/mymove/pkg/unit" ) @@ -279,6 +280,7 @@ func (suite *PayloadsSuite) TestSitExtension() { } func (suite *PayloadsSuite) TestEntitlement() { + waf := entitlements.NewWeightAllotmentFetcher() suite.Run("Success - Returns the entitlement payload with only required fields", func() { entitlement := models.Entitlement{ @@ -340,8 +342,9 @@ func (suite *PayloadsSuite) TestEntitlement() { // TotalWeight needs to read from the internal weightAllotment, in this case 7000 lbs w/o dependents and // 9000 lbs with dependents - entitlement.SetWeightAllotment(string(models.ServiceMemberGradeE5), internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) - + allotment, err := waf.GetWeightAllotment(suite.AppContextForTest(), string(models.ServiceMemberGradeE5), internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) + suite.NoError(err) + entitlement.WeightAllotted = &allotment payload := Entitlement(&entitlement) suite.Equal(strfmt.UUID(entitlement.ID.String()), payload.ID) @@ -381,7 +384,9 @@ func (suite *PayloadsSuite) TestEntitlement() { // TotalWeight needs to read from the internal weightAllotment, in this case 7000 lbs w/o dependents and // 9000 lbs with dependents - entitlement.SetWeightAllotment(string(models.ServiceMemberGradeE5), internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) + allotment, err := waf.GetWeightAllotment(suite.AppContextForTest(), string(models.ServiceMemberGradeE5), internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) + suite.NoError(err) + entitlement.WeightAllotted = &allotment payload := Entitlement(&entitlement) diff --git a/pkg/handlers/primeapiv2/api.go b/pkg/handlers/primeapiv2/api.go index 72468b5a5f4..44e8ca916ef 100644 --- a/pkg/handlers/primeapiv2/api.go +++ b/pkg/handlers/primeapiv2/api.go @@ -11,6 +11,7 @@ import ( paymentrequesthelper "github.com/transcom/mymove/pkg/payment_request" "github.com/transcom/mymove/pkg/services/address" boatshipment "github.com/transcom/mymove/pkg/services/boat_shipment" + "github.com/transcom/mymove/pkg/services/entitlements" "github.com/transcom/mymove/pkg/services/fetch" "github.com/transcom/mymove/pkg/services/ghcrateengine" mobilehomeshipment "github.com/transcom/mymove/pkg/services/mobile_home_shipment" @@ -31,6 +32,7 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primev2operations.Mymove fetcher := fetch.NewFetcher(builder) queryBuilder := query.NewQueryBuilder() moveRouter := move.NewMoveRouter() + waf := entitlements.NewWeightAllotmentFetcher() primeSpec, err := loads.Analyzed(primev2api.SwaggerJSON, "") if err != nil { @@ -44,7 +46,7 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primev2operations.Mymove primeAPIV2.MoveTaskOrderGetMoveTaskOrderHandler = GetMoveTaskOrderHandler{ handlerConfig, - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } signedCertificationCreator := signedcertification.NewSignedCertificationCreator() @@ -90,7 +92,7 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primev2operations.Mymove paymentrequest.NewPaymentRequestStatusUpdater(queryBuilder), ) paymentRequestShipmentRecalculator := paymentrequest.NewPaymentRequestShipmentRecalculator(paymentRequestRecalculator) - moveWeights := move.NewMoveWeights(mtoshipment.NewShipmentReweighRequester()) + moveWeights := move.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) addressUpdater := address.NewAddressUpdater() mtoShipmentUpdater := mtoshipment.NewPrimeMTOShipmentUpdater( builder, diff --git a/pkg/handlers/primeapiv2/move_task_order_test.go b/pkg/handlers/primeapiv2/move_task_order_test.go index 9636b898359..9d736c5b8d5 100644 --- a/pkg/handlers/primeapiv2/move_task_order_test.go +++ b/pkg/handlers/primeapiv2/move_task_order_test.go @@ -13,6 +13,7 @@ import ( "github.com/transcom/mymove/pkg/gen/primev2messages" "github.com/transcom/mymove/pkg/handlers" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services/entitlements" movetaskorder "github.com/transcom/mymove/pkg/services/move_task_order" "github.com/transcom/mymove/pkg/testdatagen" "github.com/transcom/mymove/pkg/unit" @@ -20,6 +21,7 @@ import ( func (suite *HandlerSuite) TestGetMoveTaskOrder() { request := httptest.NewRequest("GET", "/move-task-orders/{moveTaskOrderID}", nil) + waf := entitlements.NewWeightAllotmentFetcher() verifyAddressFields := func(address *models.Address, payload *primev2messages.Address) { suite.Equal(address.ID.String(), payload.ID.String()) @@ -39,7 +41,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Success with Prime-available move by ID", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) @@ -70,7 +72,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Success with Prime-available move by Locator", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) params := movetaskorderops.GetMoveTaskOrderParams{ @@ -100,7 +102,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Success returns reweighs on shipments if they exist", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) params := movetaskorderops.GetMoveTaskOrderParams{ @@ -154,7 +156,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Success - returns sit extensions on shipments if they exist", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) params := movetaskorderops.GetMoveTaskOrderParams{ @@ -212,7 +214,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Success - filters shipments handled by an external vendor", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) @@ -267,7 +269,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Success - returns shipment with attached PpmShipment", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) ppmShipment := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ @@ -303,7 +305,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { // This tests fields that aren't other structs and Addresses handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) destinationAddress := factory.BuildAddress(suite.DB(), nil, nil) @@ -418,7 +420,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Success - returns all the fields associated with StorageFacility within MtoShipments", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) params := movetaskorderops.GetMoveTaskOrderParams{ @@ -474,7 +476,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Success - returns all the fields associated with Agents within MtoShipments", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) params := movetaskorderops.GetMoveTaskOrderParams{ @@ -525,7 +527,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Success - return all base fields assoicated with the getMoveTaskOrder", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } now := time.Now() aWeekAgo := now.AddDate(0, 0, -7) @@ -578,7 +580,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Success - return all Order fields assoicated with the getMoveTaskOrder", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } currentAddress := factory.BuildAddress(suite.DB(), nil, nil) successMove := factory.BuildAvailableToPrimeMove(suite.DB(), []factory.Customization{ @@ -673,7 +675,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Success - return all PaymentRequests fields assoicated with the getMoveTaskOrder", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) @@ -862,7 +864,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Success - return all MTOServiceItemBasic fields assoicated with the getMoveTaskOrder", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) @@ -941,7 +943,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Success - return all MTOServiceItemOriginSIT fields assoicated with the getMoveTaskOrder", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) @@ -1055,7 +1057,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Success - return all MTOServiceItemDestSIT fields assoicated with the getMoveTaskOrder", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) @@ -1180,7 +1182,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Success - return all MTOServiceItemShuttle fields assoicated with the getMoveTaskOrder", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) @@ -1266,7 +1268,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Success - return all MTOServiceItemDomesticCrating fields assoicated with the getMoveTaskOrder", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) @@ -1394,7 +1396,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Failure 'Not Found' for non-available move", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } failureMove := factory.BuildMove(suite.DB(), nil, nil) // default is not available to Prime params := movetaskorderops.GetMoveTaskOrderParams{ diff --git a/pkg/handlers/primeapiv2/payloads/model_to_payload.go b/pkg/handlers/primeapiv2/payloads/model_to_payload.go index 8fad91b99ac..7f51632462e 100644 --- a/pkg/handlers/primeapiv2/payloads/model_to_payload.go +++ b/pkg/handlers/primeapiv2/payloads/model_to_payload.go @@ -110,9 +110,6 @@ func Order(order *models.Order) *primev2messages.Order { } destinationDutyLocation := DutyLocation(&order.NewDutyLocation) originDutyLocation := DutyLocation(order.OriginDutyLocation) - if order.Grade != nil && order.Entitlement != nil { - order.Entitlement.SetWeightAllotment(string(*order.Grade), order.OrdersType) - } var grade string if order.Grade != nil { diff --git a/pkg/handlers/primeapiv2/payloads/model_to_payload_test.go b/pkg/handlers/primeapiv2/payloads/model_to_payload_test.go index 572f4f5dee8..ed333f6fd95 100644 --- a/pkg/handlers/primeapiv2/payloads/model_to_payload_test.go +++ b/pkg/handlers/primeapiv2/payloads/model_to_payload_test.go @@ -13,6 +13,7 @@ import ( "github.com/transcom/mymove/pkg/gen/primev2messages" "github.com/transcom/mymove/pkg/handlers" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services/entitlements" "github.com/transcom/mymove/pkg/unit" ) @@ -270,6 +271,7 @@ func (suite *PayloadsSuite) TestSitExtension() { } func (suite *PayloadsSuite) TestEntitlement() { + waf := entitlements.NewWeightAllotmentFetcher() suite.Run("Success - Returns the entitlement payload with only required fields", func() { entitlement := models.Entitlement{ @@ -331,7 +333,9 @@ func (suite *PayloadsSuite) TestEntitlement() { // TotalWeight needs to read from the internal weightAllotment, in this case 7000 lbs w/o dependents and // 9000 lbs with dependents - entitlement.SetWeightAllotment(string(models.ServiceMemberGradeE5), internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) + allotment, err := waf.GetWeightAllotment(suite.AppContextForTest(), string(models.ServiceMemberGradeE5), internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) + suite.NoError(err) + entitlement.WeightAllotted = &allotment payload := Entitlement(&entitlement) @@ -372,7 +376,9 @@ func (suite *PayloadsSuite) TestEntitlement() { // TotalWeight needs to read from the internal weightAllotment, in this case 7000 lbs w/o dependents and // 9000 lbs with dependents - entitlement.SetWeightAllotment(string(models.ServiceMemberGradeE5), internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) + allotment, err := waf.GetWeightAllotment(suite.AppContextForTest(), string(models.ServiceMemberGradeE5), internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) + suite.NoError(err) + entitlement.WeightAllotted = &allotment payload := Entitlement(&entitlement) diff --git a/pkg/handlers/primeapiv3/api.go b/pkg/handlers/primeapiv3/api.go index bb530557a74..e46f49c8ad1 100644 --- a/pkg/handlers/primeapiv3/api.go +++ b/pkg/handlers/primeapiv3/api.go @@ -11,6 +11,7 @@ import ( paymentrequesthelper "github.com/transcom/mymove/pkg/payment_request" "github.com/transcom/mymove/pkg/services/address" boatshipment "github.com/transcom/mymove/pkg/services/boat_shipment" + "github.com/transcom/mymove/pkg/services/entitlements" "github.com/transcom/mymove/pkg/services/fetch" "github.com/transcom/mymove/pkg/services/ghcrateengine" mobilehomeshipment "github.com/transcom/mymove/pkg/services/mobile_home_shipment" @@ -31,6 +32,7 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primev3operations.Mymove fetcher := fetch.NewFetcher(builder) queryBuilder := query.NewQueryBuilder() moveRouter := move.NewMoveRouter() + waf := entitlements.NewWeightAllotmentFetcher() primeSpec, err := loads.Analyzed(primev3api.SwaggerJSON, "") if err != nil { @@ -45,7 +47,7 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primev3operations.Mymove primeAPIV3.MoveTaskOrderGetMoveTaskOrderHandler = GetMoveTaskOrderHandler{ handlerConfig, - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), mtoshipment.NewMTOShipmentRateAreaFetcher(), } @@ -80,7 +82,7 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primev3operations.Mymove paymentrequest.NewPaymentRequestStatusUpdater(queryBuilder), ) paymentRequestShipmentRecalculator := paymentrequest.NewPaymentRequestShipmentRecalculator(paymentRequestRecalculator) - moveWeights := move.NewMoveWeights(mtoshipment.NewShipmentReweighRequester()) + moveWeights := move.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) mtoShipmentUpdater := mtoshipment.NewPrimeMTOShipmentUpdater( builder, fetcher, diff --git a/pkg/handlers/primeapiv3/move_task_order_test.go b/pkg/handlers/primeapiv3/move_task_order_test.go index b1585307e7b..14b5c5dc9db 100644 --- a/pkg/handlers/primeapiv3/move_task_order_test.go +++ b/pkg/handlers/primeapiv3/move_task_order_test.go @@ -17,6 +17,7 @@ import ( "github.com/transcom/mymove/pkg/handlers" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/services/entitlements" "github.com/transcom/mymove/pkg/services/mocks" movetaskorder "github.com/transcom/mymove/pkg/services/move_task_order" mtoshipment "github.com/transcom/mymove/pkg/services/mto_shipment" @@ -26,6 +27,7 @@ import ( func (suite *HandlerSuite) TestGetMoveTaskOrder() { request := httptest.NewRequest("GET", "/move-task-orders/{moveTaskOrderID}", nil) + waf := entitlements.NewWeightAllotmentFetcher() verifyAddressFields := func(address *models.Address, payload *primev3messages.Address) { suite.Equal(address.ID.String(), payload.ID.String()) @@ -50,7 +52,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { ).Return(nil, nil) handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), mockShipmentRateAreaFinder, } return handler @@ -1367,7 +1369,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Failure 'Not Found' for non-available move", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), mtoshipment.NewMTOShipmentRateAreaFetcher(), } failureMove := factory.BuildMove(suite.DB(), nil, nil) // default is not available to Prime @@ -1400,7 +1402,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { // This tests fields that aren't other structs and Addresses handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), mockShipmentRateAreaFinder, } successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) @@ -1518,7 +1520,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { // This tests fields that aren't other structs and Addresses handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), mockShipmentRateAreaFinder, } successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) diff --git a/pkg/handlers/primeapiv3/mto_shipment_test.go b/pkg/handlers/primeapiv3/mto_shipment_test.go index 05db5c0f98d..9a096216deb 100644 --- a/pkg/handlers/primeapiv3/mto_shipment_test.go +++ b/pkg/handlers/primeapiv3/mto_shipment_test.go @@ -24,6 +24,7 @@ import ( "github.com/transcom/mymove/pkg/services" "github.com/transcom/mymove/pkg/services/address" boatshipment "github.com/transcom/mymove/pkg/services/boat_shipment" + "github.com/transcom/mymove/pkg/services/entitlements" "github.com/transcom/mymove/pkg/services/fetch" "github.com/transcom/mymove/pkg/services/ghcrateengine" mobilehomeshipment "github.com/transcom/mymove/pkg/services/mobile_home_shipment" @@ -94,6 +95,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { ) shipmentCreator := shipmentorchestrator.NewShipmentCreator(mtoShipmentCreator, ppmShipmentCreator, boatShipmentCreator, mobileHomeShipmentCreator, shipmentRouter, moveTaskOrderUpdater) mockCreator := mocks.ShipmentCreator{} + waf := entitlements.NewWeightAllotmentFetcher() var pickupAddress primev3messages.Address var secondaryPickupAddress primev3messages.Address @@ -106,7 +108,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { statusUpdater := paymentrequest.NewPaymentRequestStatusUpdater(query.NewQueryBuilder()) recalculator := paymentrequest.NewPaymentRequestRecalculator(creator, statusUpdater) paymentRequestShipmentRecalculator := paymentrequest.NewPaymentRequestShipmentRecalculator(recalculator) - moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester()) + moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) ppmShipmentUpdater := ppmshipment.NewPPMShipmentUpdater(&ppmEstimator, addressCreator, addressUpdater) boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() diff --git a/pkg/handlers/primeapiv3/payloads/model_to_payload.go b/pkg/handlers/primeapiv3/payloads/model_to_payload.go index 3b076b41bf3..22664cc7780 100644 --- a/pkg/handlers/primeapiv3/payloads/model_to_payload.go +++ b/pkg/handlers/primeapiv3/payloads/model_to_payload.go @@ -137,9 +137,6 @@ func Order(order *models.Order) *primev3messages.Order { } destinationDutyLocation := DutyLocation(&order.NewDutyLocation) originDutyLocation := DutyLocation(order.OriginDutyLocation) - if order.Grade != nil && order.Entitlement != nil { - order.Entitlement.SetWeightAllotment(string(*order.Grade), order.OrdersType) - } var grade string if order.Grade != nil { diff --git a/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go b/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go index 666233d4f52..345ee203b0b 100644 --- a/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go +++ b/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go @@ -16,6 +16,7 @@ import ( "github.com/transcom/mymove/pkg/handlers" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/services/entitlements" "github.com/transcom/mymove/pkg/unit" ) @@ -513,7 +514,7 @@ func (suite *PayloadsSuite) TestSitExtension() { } func (suite *PayloadsSuite) TestEntitlement() { - + waf := entitlements.NewWeightAllotmentFetcher() suite.Run("Success - Returns the entitlement payload with only required fields", func() { entitlement := models.Entitlement{ ID: uuid.Must(uuid.NewV4()), @@ -574,7 +575,9 @@ func (suite *PayloadsSuite) TestEntitlement() { // TotalWeight needs to read from the internal weightAllotment, in this case 7000 lbs w/o dependents and // 9000 lbs with dependents - entitlement.SetWeightAllotment(string(models.ServiceMemberGradeE5), internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) + allotment, err := waf.GetWeightAllotment(suite.AppContextForTest(), string(models.ServiceMemberGradeE5), internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) + suite.NoError(err) + entitlement.WeightAllotted = &allotment payload := Entitlement(&entitlement) @@ -616,7 +619,9 @@ func (suite *PayloadsSuite) TestEntitlement() { // TotalWeight needs to read from the internal weightAllotment, in this case 7000 lbs w/o dependents and // 9000 lbs with dependents - entitlement.SetWeightAllotment(string(models.ServiceMemberGradeE5), internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) + allotment, err := waf.GetWeightAllotment(suite.AppContextForTest(), string(models.ServiceMemberGradeE5), internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) + suite.NoError(err) + entitlement.WeightAllotted = &allotment payload := Entitlement(&entitlement) diff --git a/pkg/handlers/supportapi/api.go b/pkg/handlers/supportapi/api.go index 6544b5125f6..e60ec489162 100644 --- a/pkg/handlers/supportapi/api.go +++ b/pkg/handlers/supportapi/api.go @@ -12,6 +12,7 @@ import ( "github.com/transcom/mymove/pkg/handlers" paymentrequesthelper "github.com/transcom/mymove/pkg/payment_request" "github.com/transcom/mymove/pkg/services/address" + "github.com/transcom/mymove/pkg/services/entitlements" "github.com/transcom/mymove/pkg/services/fetch" "github.com/transcom/mymove/pkg/services/ghcrateengine" "github.com/transcom/mymove/pkg/services/invoice" @@ -40,6 +41,7 @@ func NewSupportAPIHandler(handlerConfig handlers.HandlerConfig) http.Handler { if err != nil { log.Fatalln(err) } + waf := entitlements.NewWeightAllotmentFetcher() supportAPI := supportops.NewMymoveAPI(supportSpec) @@ -47,7 +49,7 @@ func NewSupportAPIHandler(handlerConfig handlers.HandlerConfig) http.Handler { supportAPI.MoveTaskOrderListMTOsHandler = ListMTOsHandler{ handlerConfig, - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } signedCertificationCreator := signedcertification.NewSignedCertificationCreator() @@ -69,7 +71,7 @@ func NewSupportAPIHandler(handlerConfig handlers.HandlerConfig) http.Handler { supportAPI.MoveTaskOrderGetMoveTaskOrderHandler = GetMoveTaskOrderHandlerFunc{ handlerConfig, - movetaskorder.NewMoveTaskOrderFetcher()} + movetaskorder.NewMoveTaskOrderFetcher(waf)} supportAPI.MoveTaskOrderCreateMoveTaskOrderHandler = CreateMoveTaskOrderHandler{ handlerConfig, diff --git a/pkg/handlers/supportapi/internal/payloads/model_to_payload.go b/pkg/handlers/supportapi/internal/payloads/model_to_payload.go index 5e43cd2070b..12ebfd4b8a1 100644 --- a/pkg/handlers/supportapi/internal/payloads/model_to_payload.go +++ b/pkg/handlers/supportapi/internal/payloads/model_to_payload.go @@ -85,9 +85,6 @@ func Order(order *models.Order) *supportmessages.Order { destinationDutyLocation := DutyLocation(&order.NewDutyLocation) originDutyLocation := DutyLocation(order.OriginDutyLocation) uploadedOrders := Document(&order.UploadedOrders) - if order.Grade != nil && order.Entitlement != nil { - order.Entitlement.SetWeightAllotment(string(*order.Grade), order.OrdersType) - } reportByDate := strfmt.Date(order.ReportByDate) issueDate := strfmt.Date(order.IssueDate) diff --git a/pkg/handlers/supportapi/internal/payloads/model_to_payload_test.go b/pkg/handlers/supportapi/internal/payloads/model_to_payload_test.go index 05852f8b087..eeb93bc0394 100644 --- a/pkg/handlers/supportapi/internal/payloads/model_to_payload_test.go +++ b/pkg/handlers/supportapi/internal/payloads/model_to_payload_test.go @@ -6,22 +6,36 @@ import ( "github.com/go-openapi/strfmt" "github.com/gofrs/uuid" - "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" "github.com/transcom/mymove/pkg/etag" "github.com/transcom/mymove/pkg/gen/internalmessages" "github.com/transcom/mymove/pkg/handlers" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/notifications" + "github.com/transcom/mymove/pkg/services/entitlements" + "github.com/transcom/mymove/pkg/testingsuite" ) -func TestOrder(_ *testing.T) { - order := &models.Order{} - Order(order) +// HandlerSuite is an abstraction of our original suite +type PayloadsSuite struct { + handlers.BaseHandlerTestSuite } -func TestEntitlement(t *testing.T) { +// TestHandlerSuite creates our test suite +func TestHandlerSuite(t *testing.T) { + hs := &PayloadsSuite{ + BaseHandlerTestSuite: handlers.NewBaseHandlerTestSuite(notifications.NewStubNotificationSender("milmovelocal"), testingsuite.CurrentPackage(), + testingsuite.WithPerTestTransaction()), + } - t.Run("Success - Returns the entitlement payload with only required fields", func(t *testing.T) { + suite.Run(t, hs) + hs.PopTestSuite.TearDown() +} + +func (suite *PayloadsSuite) TestEntitlement() { + waf := entitlements.NewWeightAllotmentFetcher() + suite.Run("Success - Returns the entitlement payload with only required fields", func() { entitlement := models.Entitlement{ ID: uuid.Must(uuid.NewV4()), DependentsAuthorized: nil, @@ -41,27 +55,27 @@ func TestEntitlement(t *testing.T) { payload := Entitlement(&entitlement) - assert.Equal(t, strfmt.UUID(entitlement.ID.String()), payload.ID) - assert.Equal(t, int64(0), payload.RequiredMedicalEquipmentWeight) - assert.Equal(t, false, payload.OrganizationalClothingAndIndividualEquipment) - assert.Equal(t, int64(0), payload.ProGearWeight) - assert.Equal(t, int64(0), payload.ProGearWeightSpouse) - assert.NotEmpty(t, payload.ETag) - assert.Equal(t, etag.GenerateEtag(entitlement.UpdatedAt), payload.ETag) + suite.Equal(strfmt.UUID(entitlement.ID.String()), payload.ID) + suite.Equal(int64(0), payload.RequiredMedicalEquipmentWeight) + suite.Equal(false, payload.OrganizationalClothingAndIndividualEquipment) + suite.Equal(int64(0), payload.ProGearWeight) + suite.Equal(int64(0), payload.ProGearWeightSpouse) + suite.NotEmpty(payload.ETag) + suite.Equal(etag.GenerateEtag(entitlement.UpdatedAt), payload.ETag) - assert.Nil(t, payload.AuthorizedWeight) - assert.Nil(t, payload.DependentsAuthorized) - assert.Nil(t, payload.NonTemporaryStorage) - assert.Nil(t, payload.PrivatelyOwnedVehicle) + suite.Nil(payload.AuthorizedWeight) + suite.Nil(payload.DependentsAuthorized) + suite.Nil(payload.NonTemporaryStorage) + suite.Nil(payload.PrivatelyOwnedVehicle) /* These fields are defaulting to zero if they are nil in the model */ - assert.Equal(t, int64(0), payload.StorageInTransit) - assert.Equal(t, int64(0), payload.TotalDependents) - assert.Equal(t, int64(0), payload.TotalWeight) - assert.Equal(t, int64(0), *payload.UnaccompaniedBaggageAllowance) + suite.Equal(int64(0), payload.StorageInTransit) + suite.Equal(int64(0), payload.TotalDependents) + suite.Equal(int64(0), payload.TotalWeight) + suite.Equal(int64(0), *payload.UnaccompaniedBaggageAllowance) }) - t.Run("Success - Returns the entitlement payload with all optional fields populated", func(t *testing.T) { + suite.Run("Success - Returns the entitlement payload with all optional fields populated", func() { entitlement := models.Entitlement{ ID: uuid.Must(uuid.NewV4()), DependentsAuthorized: handlers.FmtBool(true), @@ -81,28 +95,30 @@ func TestEntitlement(t *testing.T) { // TotalWeight needs to read from the internal weightAllotment, in this case 7000 lbs w/o dependents and // 9000 lbs with dependents - entitlement.SetWeightAllotment(string(models.ServiceMemberGradeE5), internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) + allotment, err := waf.GetWeightAllotment(suite.AppContextForTest(), string(models.ServiceMemberGradeE5), internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) + suite.NoError(err) + entitlement.WeightAllotted = &allotment payload := Entitlement(&entitlement) - assert.Equal(t, strfmt.UUID(entitlement.ID.String()), payload.ID) - assert.True(t, *payload.DependentsAuthorized) - assert.Equal(t, int64(2), payload.TotalDependents) - assert.True(t, *payload.NonTemporaryStorage) - assert.True(t, *payload.PrivatelyOwnedVehicle) - assert.Equal(t, int64(10000), *payload.AuthorizedWeight) - assert.Equal(t, int64(400), *payload.UnaccompaniedBaggageAllowance) - assert.Equal(t, int64(9000), payload.TotalWeight) - assert.Equal(t, int64(45), payload.StorageInTransit) - assert.Equal(t, int64(500), payload.RequiredMedicalEquipmentWeight) - assert.Equal(t, true, payload.OrganizationalClothingAndIndividualEquipment) - assert.Equal(t, int64(1000), payload.ProGearWeight) - assert.Equal(t, int64(750), payload.ProGearWeightSpouse) - assert.NotEmpty(t, payload.ETag) - assert.Equal(t, etag.GenerateEtag(entitlement.UpdatedAt), payload.ETag) + suite.Equal(strfmt.UUID(entitlement.ID.String()), payload.ID) + suite.True(*payload.DependentsAuthorized) + suite.Equal(int64(2), payload.TotalDependents) + suite.True(*payload.NonTemporaryStorage) + suite.True(*payload.PrivatelyOwnedVehicle) + suite.Equal(int64(10000), *payload.AuthorizedWeight) + suite.Equal(int64(400), *payload.UnaccompaniedBaggageAllowance) + suite.Equal(int64(9000), payload.TotalWeight) + suite.Equal(int64(45), payload.StorageInTransit) + suite.Equal(int64(500), payload.RequiredMedicalEquipmentWeight) + suite.Equal(true, payload.OrganizationalClothingAndIndividualEquipment) + suite.Equal(int64(1000), payload.ProGearWeight) + suite.Equal(int64(750), payload.ProGearWeightSpouse) + suite.NotEmpty(payload.ETag) + suite.Equal(etag.GenerateEtag(entitlement.UpdatedAt), payload.ETag) }) - t.Run("Success - Returns the entitlement payload with total weight self when dependents are not authorized", func(t *testing.T) { + suite.Run("Success - Returns the entitlement payload with total weight self when dependents are not authorized", func() { entitlement := models.Entitlement{ ID: uuid.Must(uuid.NewV4()), DependentsAuthorized: handlers.FmtBool(false), @@ -122,24 +138,26 @@ func TestEntitlement(t *testing.T) { // TotalWeight needs to read from the internal weightAllotment, in this case 7000 lbs w/o dependents and // 9000 lbs with dependents - entitlement.SetWeightAllotment(string(models.ServiceMemberGradeE5), internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) + allotment, err := waf.GetWeightAllotment(suite.AppContextForTest(), string(models.ServiceMemberGradeE5), internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) + suite.NoError(err) + entitlement.WeightAllotted = &allotment payload := Entitlement(&entitlement) - assert.Equal(t, strfmt.UUID(entitlement.ID.String()), payload.ID) - assert.False(t, *payload.DependentsAuthorized) - assert.Equal(t, int64(2), payload.TotalDependents) - assert.True(t, *payload.NonTemporaryStorage) - assert.True(t, *payload.PrivatelyOwnedVehicle) - assert.Equal(t, int64(10000), *payload.AuthorizedWeight) - assert.Equal(t, int64(400), *payload.UnaccompaniedBaggageAllowance) - assert.Equal(t, int64(7000), payload.TotalWeight) - assert.Equal(t, int64(45), payload.StorageInTransit) - assert.Equal(t, int64(500), payload.RequiredMedicalEquipmentWeight) - assert.Equal(t, true, payload.OrganizationalClothingAndIndividualEquipment) - assert.Equal(t, int64(1000), payload.ProGearWeight) - assert.Equal(t, int64(750), payload.ProGearWeightSpouse) - assert.NotEmpty(t, payload.ETag) - assert.Equal(t, etag.GenerateEtag(entitlement.UpdatedAt), payload.ETag) + suite.Equal(strfmt.UUID(entitlement.ID.String()), payload.ID) + suite.False(*payload.DependentsAuthorized) + suite.Equal(int64(2), payload.TotalDependents) + suite.True(*payload.NonTemporaryStorage) + suite.True(*payload.PrivatelyOwnedVehicle) + suite.Equal(int64(10000), *payload.AuthorizedWeight) + suite.Equal(int64(400), *payload.UnaccompaniedBaggageAllowance) + suite.Equal(int64(7000), payload.TotalWeight) + suite.Equal(int64(45), payload.StorageInTransit) + suite.Equal(int64(500), payload.RequiredMedicalEquipmentWeight) + suite.Equal(true, payload.OrganizationalClothingAndIndividualEquipment) + suite.Equal(int64(1000), payload.ProGearWeight) + suite.Equal(int64(750), payload.ProGearWeightSpouse) + suite.NotEmpty(payload.ETag) + suite.Equal(etag.GenerateEtag(entitlement.UpdatedAt), payload.ETag) }) } diff --git a/pkg/handlers/supportapi/move_task_order_test.go b/pkg/handlers/supportapi/move_task_order_test.go index b1241de3f32..f5663ae8794 100644 --- a/pkg/handlers/supportapi/move_task_order_test.go +++ b/pkg/handlers/supportapi/move_task_order_test.go @@ -18,6 +18,7 @@ import ( "github.com/transcom/mymove/pkg/models" routemocks "github.com/transcom/mymove/pkg/route/mocks" "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/services/entitlements" "github.com/transcom/mymove/pkg/services/ghcrateengine" "github.com/transcom/mymove/pkg/services/mocks" moverouter "github.com/transcom/mymove/pkg/services/move" @@ -31,6 +32,7 @@ import ( func (suite *HandlerSuite) TestListMTOsHandler() { // unavailable MTO factory.BuildMove(suite.DB(), nil, nil) + waf := entitlements.NewWeightAllotmentFetcher() moveTaskOrder := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) @@ -55,7 +57,7 @@ func (suite *HandlerSuite) TestListMTOsHandler() { handler := ListMTOsHandler{ HandlerConfig: handlerConfig, - MoveTaskOrderFetcher: movetaskorder.NewMoveTaskOrderFetcher(), + MoveTaskOrderFetcher: movetaskorder.NewMoveTaskOrderFetcher(waf), } response := handler.Handle(params) @@ -227,10 +229,11 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { HTTPRequest: request, MoveTaskOrderID: move.ID.String(), } + waf := entitlements.NewWeightAllotmentFetcher() handlerConfig := suite.HandlerConfig() handler := GetMoveTaskOrderHandlerFunc{handlerConfig, - movetaskorder.NewMoveTaskOrderFetcher(), + movetaskorder.NewMoveTaskOrderFetcher(waf), } response := handler.Handle(params) suite.IsNotErrResponse(response) diff --git a/pkg/models/application_parameters.go b/pkg/models/application_parameters.go index dccf21fd10f..083605c9383 100644 --- a/pkg/models/application_parameters.go +++ b/pkg/models/application_parameters.go @@ -1,6 +1,7 @@ package models import ( + "encoding/json" "time" "github.com/gobuffalo/pop/v6" @@ -10,12 +11,13 @@ import ( // ApplicationParameters is a model representing application parameters and holds parameter values and parameter names stored in the database type ApplicationParameters struct { - ID uuid.UUID `json:"id" db:"id"` - ValidationCode *string `json:"validation_code" db:"validation_code"` - ParameterName *string `json:"parameter_name" db:"parameter_name"` - ParameterValue *string `json:"parameter_value" db:"parameter_value"` - CreatedAt time.Time `json:"created_at" db:"created_at"` - UpdatedAt time.Time `json:"updated_at" db:"updated_at"` + ID uuid.UUID `json:"id" db:"id"` + ValidationCode *string `json:"validation_code" db:"validation_code"` + ParameterName *string `json:"parameter_name" db:"parameter_name"` + ParameterValue *string `json:"parameter_value" db:"parameter_value"` + ParameterJson *json.RawMessage `json:"parameter_json" db:"parameter_json"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + UpdatedAt time.Time `json:"updated_at" db:"updated_at"` } func (a ApplicationParameters) TableName() string { diff --git a/pkg/models/entitlements.go b/pkg/models/entitlements.go deleted file mode 100644 index 94fa25f6ef2..00000000000 --- a/pkg/models/entitlements.go +++ /dev/null @@ -1,365 +0,0 @@ -package models - -import ( - "fmt" - - "github.com/pkg/errors" - - "github.com/transcom/mymove/pkg/appcontext" - "github.com/transcom/mymove/pkg/gen/internalmessages" -) - -// WeightAllotment represents the weights allotted for a rank -type WeightAllotment struct { - TotalWeightSelf int - TotalWeightSelfPlusDependents int - ProGearWeight int - ProGearWeightSpouse int - UnaccompaniedBaggageAllowance int -} - -// the midshipman entitlement is shared with service academy cadet -var midshipman = WeightAllotment{ - TotalWeightSelf: 350, - TotalWeightSelfPlusDependents: 350, - ProGearWeight: 0, - ProGearWeightSpouse: 0, -} - -var aviationCadet = WeightAllotment{ - TotalWeightSelf: 7000, - TotalWeightSelfPlusDependents: 8000, - ProGearWeight: 2000, - ProGearWeightSpouse: 500, -} - -var e1 = WeightAllotment{ - TotalWeightSelf: 5000, - TotalWeightSelfPlusDependents: 8000, - ProGearWeight: 2000, - ProGearWeightSpouse: 500, -} - -var e2 = WeightAllotment{ - TotalWeightSelf: 5000, - TotalWeightSelfPlusDependents: 8000, - ProGearWeight: 2000, - ProGearWeightSpouse: 500, -} - -var e3 = WeightAllotment{ - TotalWeightSelf: 5000, - TotalWeightSelfPlusDependents: 8000, - ProGearWeight: 2000, - ProGearWeightSpouse: 500, -} - -var e4 = WeightAllotment{ - TotalWeightSelf: 7000, - TotalWeightSelfPlusDependents: 8000, - ProGearWeight: 2000, - ProGearWeightSpouse: 500, -} - -var e5 = WeightAllotment{ - TotalWeightSelf: 7000, - TotalWeightSelfPlusDependents: 9000, - ProGearWeight: 2000, - ProGearWeightSpouse: 500, -} - -var e6 = WeightAllotment{ - TotalWeightSelf: 8000, - TotalWeightSelfPlusDependents: 11000, - ProGearWeight: 2000, - ProGearWeightSpouse: 500, -} - -var e7 = WeightAllotment{ - TotalWeightSelf: 11000, - TotalWeightSelfPlusDependents: 13000, - ProGearWeight: 2000, - ProGearWeightSpouse: 500, -} - -var e8 = WeightAllotment{ - TotalWeightSelf: 12000, - TotalWeightSelfPlusDependents: 14000, - ProGearWeight: 2000, - ProGearWeightSpouse: 500, -} - -var e9 = WeightAllotment{ - TotalWeightSelf: 13000, - TotalWeightSelfPlusDependents: 15000, - ProGearWeight: 2000, - ProGearWeightSpouse: 500, -} - -var e9SpecialSeniorEnlisted = WeightAllotment{ - TotalWeightSelf: 14000, - TotalWeightSelfPlusDependents: 17000, - ProGearWeight: 2000, - ProGearWeightSpouse: 500, -} - -// O-1 through O-5 share their entitlements with W-1 through W-5 -var o1W1AcademyGraduate = WeightAllotment{ - TotalWeightSelf: 10000, - TotalWeightSelfPlusDependents: 12000, - ProGearWeight: 2000, - ProGearWeightSpouse: 500, -} - -var o2W2 = WeightAllotment{ - TotalWeightSelf: 12500, - TotalWeightSelfPlusDependents: 13500, - ProGearWeight: 2000, - ProGearWeightSpouse: 500, -} - -var o3W3 = WeightAllotment{ - TotalWeightSelf: 13000, - TotalWeightSelfPlusDependents: 14500, - ProGearWeight: 2000, - ProGearWeightSpouse: 500, -} - -var o4W4 = WeightAllotment{ - TotalWeightSelf: 14000, - TotalWeightSelfPlusDependents: 17000, - ProGearWeight: 2000, - ProGearWeightSpouse: 500, -} - -var o5W5 = WeightAllotment{ - TotalWeightSelf: 16000, - TotalWeightSelfPlusDependents: 17500, - ProGearWeight: 2000, - ProGearWeightSpouse: 500, -} - -var o6 = WeightAllotment{ - TotalWeightSelf: 18000, - TotalWeightSelfPlusDependents: 18000, - ProGearWeight: 2000, - ProGearWeightSpouse: 500, -} - -var o7 = WeightAllotment{ - TotalWeightSelf: 18000, - TotalWeightSelfPlusDependents: 18000, - ProGearWeight: 2000, - ProGearWeightSpouse: 500, -} - -var o8 = WeightAllotment{ - TotalWeightSelf: 18000, - TotalWeightSelfPlusDependents: 18000, - ProGearWeight: 2000, - ProGearWeightSpouse: 500, -} - -var o9 = WeightAllotment{ - TotalWeightSelf: 18000, - TotalWeightSelfPlusDependents: 18000, - ProGearWeight: 2000, - ProGearWeightSpouse: 500, -} - -var o10 = WeightAllotment{ - TotalWeightSelf: 18000, - TotalWeightSelfPlusDependents: 18000, - ProGearWeight: 2000, - ProGearWeightSpouse: 500, -} - -var civilianEmployee = WeightAllotment{ - TotalWeightSelf: 18000, - TotalWeightSelfPlusDependents: 18000, - ProGearWeight: 2000, - ProGearWeightSpouse: 500, -} - -// allotment by orders type -var studentTravel = WeightAllotment{ - TotalWeightSelf: 350, - TotalWeightSelfPlusDependents: 350, - ProGearWeight: 0, - ProGearWeightSpouse: 0, -} - -var entitlements = map[internalmessages.OrderPayGrade]WeightAllotment{ - ServiceMemberGradeACADEMYCADET: midshipman, - ServiceMemberGradeAVIATIONCADET: aviationCadet, - ServiceMemberGradeE1: e1, - ServiceMemberGradeE2: e2, - ServiceMemberGradeE3: e3, - ServiceMemberGradeE4: e4, - ServiceMemberGradeE5: e5, - ServiceMemberGradeE6: e6, - ServiceMemberGradeE7: e7, - ServiceMemberGradeE8: e8, - ServiceMemberGradeE9: e9, - ServiceMemberGradeE9SPECIALSENIORENLISTED: e9SpecialSeniorEnlisted, - ServiceMemberGradeMIDSHIPMAN: midshipman, - ServiceMemberGradeO1ACADEMYGRADUATE: o1W1AcademyGraduate, - ServiceMemberGradeO2: o2W2, - ServiceMemberGradeO3: o3W3, - ServiceMemberGradeO4: o4W4, - ServiceMemberGradeO5: o5W5, - ServiceMemberGradeO6: o6, - ServiceMemberGradeO7: o7, - ServiceMemberGradeO8: o8, - ServiceMemberGradeO9: o9, - ServiceMemberGradeO10: o10, - ServiceMemberGradeW1: o1W1AcademyGraduate, - ServiceMemberGradeW2: o2W2, - ServiceMemberGradeW3: o3W3, - ServiceMemberGradeW4: o4W4, - ServiceMemberGradeW5: o5W5, - ServiceMemberGradeCIVILIANEMPLOYEE: civilianEmployee, -} - -var entitlementsByOrdersType = map[internalmessages.OrdersType]WeightAllotment{ - internalmessages.OrdersTypeSTUDENTTRAVEL: studentTravel, -} - -func getEntitlement(grade internalmessages.OrderPayGrade) (WeightAllotment, error) { - if entitlement, ok := entitlements[grade]; ok { - return entitlement, nil - } - return WeightAllotment{}, fmt.Errorf("no entitlement found for pay grade %s", grade) -} - -func getEntitlementByOrdersType(ordersType internalmessages.OrdersType) (WeightAllotment, error) { - if entitlement, ok := entitlementsByOrdersType[ordersType]; ok { - return entitlement, nil - } - return WeightAllotment{}, fmt.Errorf("no entitlement found for orders type %s", ordersType) -} - -// AllWeightAllotments returns all the weight allotments for each rank. -func AllWeightAllotments() map[internalmessages.OrderPayGrade]WeightAllotment { - return entitlements -} - -// GetWeightAllotment returns the weight allotments for a given pay grade or an orders type. -func GetWeightAllotment(grade internalmessages.OrderPayGrade, ordersType internalmessages.OrdersType) WeightAllotment { - var entitlement WeightAllotment - var err error - - if ordersType == internalmessages.OrdersTypeSTUDENTTRAVEL { // currently only applies to student travel order that limits overall authorized weight - entitlement, err = getEntitlementByOrdersType(ordersType) - } else { - entitlement, err = getEntitlement(grade) - } - if err != nil { - return WeightAllotment{} - } - return entitlement -} - -// GetUBWeightAllowance returns the UB weight allowance for a UB shipment, part of the overall entitlements for an order -func GetUBWeightAllowance(appCtx appcontext.AppContext, originDutyLocationIsOconus *bool, newDutyLocationIsOconus *bool, branch *ServiceMemberAffiliation, grade *internalmessages.OrderPayGrade, orderType *internalmessages.OrdersType, dependentsAuthorized *bool, isAccompaniedTour *bool, dependentsUnderTwelve *int, dependentsTwelveAndOver *int) (int, error) { - originDutyLocationIsOconusValue := false - if originDutyLocationIsOconus != nil { - originDutyLocationIsOconusValue = *originDutyLocationIsOconus - } - newDutyLocationIsOconusValue := false - if newDutyLocationIsOconus != nil { - newDutyLocationIsOconusValue = *newDutyLocationIsOconus - } - branchOfService := "" - if branch != nil { - branchOfService = string(*branch) - } - orderPayGrade := "" - if grade != nil { - orderPayGrade = string(*grade) - } - typeOfOrder := "" - if orderType != nil { - typeOfOrder = string(*orderType) - } - dependentsAreAuthorized := false - if dependentsAuthorized != nil { - dependentsAreAuthorized = *dependentsAuthorized - } - isAnAccompaniedTour := false - if isAccompaniedTour != nil { - isAnAccompaniedTour = *isAccompaniedTour - } - underTwelveDependents := 0 - if dependentsUnderTwelve != nil { - underTwelveDependents = *dependentsUnderTwelve - } - twelveAndOverDependents := 0 - if dependentsTwelveAndOver != nil { - twelveAndOverDependents = *dependentsTwelveAndOver - } - - // only calculate UB allowance if either origin or new duty locations are OCONUS - if originDutyLocationIsOconusValue || newDutyLocationIsOconusValue { - - const civilianBaseUBAllowance = 350 - const dependents12AndOverUBAllowance = 350 - const depedentsUnder12UBAllowance = 175 - const maxWholeFamilyCivilianUBAllowance = 2000 - const studentTravelMaxAllowance = 350 - ubAllowance := 0 - - if typeOfOrder == string(internalmessages.OrdersTypeSTUDENTTRAVEL) { - ubAllowance = studentTravelMaxAllowance - } else if orderPayGrade == string(internalmessages.OrderPayGradeCIVILIANEMPLOYEE) && dependentsAreAuthorized && underTwelveDependents == 0 && twelveAndOverDependents == 0 { - ubAllowance = civilianBaseUBAllowance - } else if orderPayGrade == string(internalmessages.OrderPayGradeCIVILIANEMPLOYEE) && dependentsAreAuthorized && (underTwelveDependents > 0 || twelveAndOverDependents > 0) { - ubAllowance = civilianBaseUBAllowance - // for each dependent 12 and older, add an additional 350 lbs to the civilian's baggage allowance - ubAllowance += twelveAndOverDependents * dependents12AndOverUBAllowance - // for each dependent under 12, add an additional 175 lbs to the civilian's baggage allowance - ubAllowance += underTwelveDependents * depedentsUnder12UBAllowance - // max allowance of 2,000 lbs for entire family - if ubAllowance > maxWholeFamilyCivilianUBAllowance { - ubAllowance = maxWholeFamilyCivilianUBAllowance - } - } else { - if typeOfOrder == string(internalmessages.OrdersTypeLOCALMOVE) { - // no UB allowance for local moves - return 0, nil - } else if typeOfOrder != string(internalmessages.OrdersTypeTEMPORARYDUTY) { - // all order types other than temporary duty are treated as permanent change of station types for the lookup - typeOfOrder = string(internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) - } - // space force members entitled to the same allowance as air force members - if branchOfService == AffiliationSPACEFORCE.String() { - branchOfService = AffiliationAIRFORCE.String() - } - // e9 special senior enlisted members entitled to the same allowance as e9 members - if orderPayGrade == string(ServiceMemberGradeE9SPECIALSENIORENLISTED) { - orderPayGrade = string(ServiceMemberGradeE9) - } - - var baseUBAllowance UBAllowances - err := appCtx.DB().Where("branch = ? AND grade = ? AND orders_type = ? AND dependents_authorized = ? AND accompanied_tour = ?", branchOfService, orderPayGrade, typeOfOrder, dependentsAreAuthorized, isAnAccompaniedTour).First(&baseUBAllowance) - if err != nil { - if errors.Cause(err).Error() == RecordNotFoundErrorString { - message := fmt.Sprintf("No UB allowance entry found in ub_allowances table for branch: %s, grade: %s, orders_type: %s, dependents_authorized: %t, accompanied_tour: %t.", branchOfService, orderPayGrade, typeOfOrder, dependentsAreAuthorized, isAnAccompaniedTour) - appCtx.Logger().Info(message) - return 0, nil - } - return 0, err - } - if baseUBAllowance.UBAllowance != nil { - ubAllowance = *baseUBAllowance.UBAllowance - return ubAllowance, nil - } else { - return 0, nil - } - } - return ubAllowance, nil - } else { - appCtx.Logger().Info("No OCONUS duty location found for orders, no UB allowance calculated as part of order entitlement.") - return 0, nil - } -} diff --git a/pkg/models/entitlements_test.go b/pkg/models/entitlements_test.go deleted file mode 100644 index 6ab3e576bd8..00000000000 --- a/pkg/models/entitlements_test.go +++ /dev/null @@ -1,278 +0,0 @@ -package models_test - -import ( - "github.com/transcom/mymove/pkg/gen/internalmessages" - "github.com/transcom/mymove/pkg/models" -) - -const civilianBaseUBAllowanceTestConstant = 350 -const dependents12AndOverUBAllowanceTestConstant = 350 -const depedentsUnder12UBAllowanceTestConstant = 175 -const maxWholeFamilyCivilianUBAllowanceTestConstant = 2000 - -func (suite *ModelSuite) TestGetEntitlementWithValidValues() { - E1 := models.ServiceMemberGradeE1 - ordersType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION - - suite.Run("E1 with dependents", func() { - E1FullLoad := models.GetWeightAllotment(E1, ordersType) - suite.Assertions.Equal(8000, E1FullLoad.TotalWeightSelfPlusDependents) - }) - - suite.Run("E1 without dependents", func() { - E1Solo := models.GetWeightAllotment(E1, ordersType) - suite.Assertions.Equal(5000, E1Solo.TotalWeightSelf) - }) - - suite.Run("E1 Pro Gear", func() { - E1ProGear := models.GetWeightAllotment(E1, ordersType) - suite.Assertions.Equal(2000, E1ProGear.ProGearWeight) - }) - - suite.Run("E1 Pro Gear Spouse", func() { - E1ProGearSpouse := models.GetWeightAllotment(E1, ordersType) - suite.Assertions.Equal(500, E1ProGearSpouse.ProGearWeightSpouse) - }) -} - -func (suite *ModelSuite) TestGetUBWeightAllowanceIsZero() { - appCtx := suite.AppContextForTest() - branch := models.AffiliationMARINES - originDutyLocationIsOconus := false - newDutyLocationIsOconus := false - grade := models.ServiceMemberGradeE1 - orderType := internalmessages.OrdersTypeLOCALMOVE - dependentsAuthorized := true - isAccompaniedTour := true - dependentsUnderTwelve := 2 - dependentsTwelveAndOver := 1 - - suite.Run("UB allowance is zero when origin and new duty location are both CONUS", func() { - ubAllowance, err := models.GetUBWeightAllowance(appCtx, &originDutyLocationIsOconus, &newDutyLocationIsOconus, &branch, &grade, &orderType, &dependentsAuthorized, &isAccompaniedTour, &dependentsUnderTwelve, &dependentsTwelveAndOver) - suite.NoError(err) - suite.Assertions.Equal(0, ubAllowance) - }) - - suite.Run("UB allowance is zero for orders type OrdersTypeLOCALMOVE", func() { - ubAllowance, err := models.GetUBWeightAllowance(appCtx, &originDutyLocationIsOconus, &newDutyLocationIsOconus, &branch, &grade, &orderType, &dependentsAuthorized, &isAccompaniedTour, &dependentsUnderTwelve, &dependentsTwelveAndOver) - suite.NoError(err) - suite.Assertions.Equal(0, ubAllowance) - }) - - originDutyLocationIsOconus = true - orderType = internalmessages.OrdersTypePERMANENTCHANGEOFSTATION - dependentsAuthorized = false - suite.Run("UB allowance is zero for nonexistent combination of branch, grade, orders_type, dependents_authorized, and accompanied_tour", func() { - ubAllowance, err := models.GetUBWeightAllowance(appCtx, &originDutyLocationIsOconus, &newDutyLocationIsOconus, &branch, &grade, &orderType, &dependentsAuthorized, &isAccompaniedTour, &dependentsUnderTwelve, &dependentsTwelveAndOver) - suite.NoError(err) - suite.Assertions.Equal(0, ubAllowance) - }) -} - -func (suite *ModelSuite) TestGetUBWeightAllowanceCivilians() { - appCtx := suite.AppContextForTest() - branch := models.AffiliationCOASTGUARD - originDutyLocationIsOconus := true - newDutyLocationIsOconus := false - grade := models.ServiceMemberGradeCIVILIANEMPLOYEE - orderType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION - dependentsAuthorized := true - isAccompaniedTour := true - - dependentsUnderTwelve := 0 - dependentsTwelveAndOver := 0 - suite.Run("UB allowance is calculated for Civilian Employee pay grade with no dependents", func() { - ubAllowance, err := models.GetUBWeightAllowance(appCtx, &originDutyLocationIsOconus, &newDutyLocationIsOconus, &branch, &grade, &orderType, &dependentsAuthorized, &isAccompaniedTour, &dependentsUnderTwelve, &dependentsTwelveAndOver) - suite.NoError(err) - suite.Assertions.Equal(civilianBaseUBAllowanceTestConstant, ubAllowance) - }) - - dependentsUnderTwelve = 0 - dependentsTwelveAndOver = 2 - suite.Run("UB allowance is calculated for Civilian Employee pay grade when dependentsUnderTwelve is 0 and dependentsTwelveAndOver is > 0", func() { - ubAllowance, err := models.GetUBWeightAllowance(appCtx, &originDutyLocationIsOconus, &newDutyLocationIsOconus, &branch, &grade, &orderType, &dependentsAuthorized, &isAccompaniedTour, &dependentsUnderTwelve, &dependentsTwelveAndOver) - suite.NoError(err) - civilianPlusDependentsTotalBaggageAllowance := civilianBaseUBAllowanceTestConstant + (dependentsUnderTwelve * depedentsUnder12UBAllowanceTestConstant) + (dependentsTwelveAndOver * dependents12AndOverUBAllowanceTestConstant) - suite.Assertions.Equal(1050, civilianPlusDependentsTotalBaggageAllowance) - suite.Assertions.Equal(civilianPlusDependentsTotalBaggageAllowance, ubAllowance) - }) - - dependentsUnderTwelve = 3 - dependentsTwelveAndOver = 0 - suite.Run("UB allowance is calculated for Civilian Employee pay grade when dependentsUnderTwelve is > 0 and dependentsTwelveAndOver is 0", func() { - ubAllowance, err := models.GetUBWeightAllowance(appCtx, &originDutyLocationIsOconus, &newDutyLocationIsOconus, &branch, &grade, &orderType, &dependentsAuthorized, &isAccompaniedTour, &dependentsUnderTwelve, &dependentsTwelveAndOver) - suite.NoError(err) - civilianPlusDependentsTotalBaggageAllowance := civilianBaseUBAllowanceTestConstant + (dependentsUnderTwelve * depedentsUnder12UBAllowanceTestConstant) + (dependentsTwelveAndOver * dependents12AndOverUBAllowanceTestConstant) - suite.Assertions.Equal(875, civilianPlusDependentsTotalBaggageAllowance) - suite.Assertions.Equal(civilianPlusDependentsTotalBaggageAllowance, ubAllowance) - }) - - dependentsUnderTwelve = 2 - dependentsTwelveAndOver = 1 - suite.Run("UB allowance is calculated for Civilian Employee pay grade", func() { - ubAllowance, err := models.GetUBWeightAllowance(appCtx, &originDutyLocationIsOconus, &newDutyLocationIsOconus, &branch, &grade, &orderType, &dependentsAuthorized, &isAccompaniedTour, &dependentsUnderTwelve, &dependentsTwelveAndOver) - suite.NoError(err) - civilianPlusDependentsTotalBaggageAllowance := civilianBaseUBAllowanceTestConstant + (dependentsUnderTwelve * depedentsUnder12UBAllowanceTestConstant) + (dependentsTwelveAndOver * dependents12AndOverUBAllowanceTestConstant) - suite.Assertions.Equal(1050, civilianPlusDependentsTotalBaggageAllowance) - suite.Assertions.Equal(civilianPlusDependentsTotalBaggageAllowance, ubAllowance) - }) - - dependentsUnderTwelve = 3 - dependentsTwelveAndOver = 4 - // this combination of depdendents would tally up to 2275 pounds normally - // however, we limit the max ub allowance for a family to 2000 - suite.Run("UB allowance is set to 2000 for the max weight for a family", func() { - ubAllowance, err := models.GetUBWeightAllowance(appCtx, &originDutyLocationIsOconus, &newDutyLocationIsOconus, &branch, &grade, &orderType, &dependentsAuthorized, &isAccompaniedTour, &dependentsUnderTwelve, &dependentsTwelveAndOver) - suite.NoError(err) - civilianPlusDependentsTotalBaggageAllowance := civilianBaseUBAllowanceTestConstant + (dependentsUnderTwelve * depedentsUnder12UBAllowanceTestConstant) + (dependentsTwelveAndOver * dependents12AndOverUBAllowanceTestConstant) - suite.Assertions.Equal(2275, civilianPlusDependentsTotalBaggageAllowance) - suite.Assertions.NotEqual(civilianPlusDependentsTotalBaggageAllowance, ubAllowance) - suite.Assertions.Equal(maxWholeFamilyCivilianUBAllowanceTestConstant, ubAllowance) - }) - - orderType = internalmessages.OrdersTypeSTUDENTTRAVEL - // This should limit the ub allowance to 350 lbs because it is a Student Travel order type - suite.Run("UB allowance is set to 350 for Student Travel orders", func() { - ubAllowance, err := models.GetUBWeightAllowance(appCtx, &originDutyLocationIsOconus, &newDutyLocationIsOconus, &branch, &grade, &orderType, &dependentsAuthorized, &isAccompaniedTour, &dependentsUnderTwelve, &dependentsTwelveAndOver) - suite.NoError(err) - suite.Assertions.Equal(350, ubAllowance) - }) -} - -func (suite *ModelSuite) TestGetUBWeightAllowanceEdgeCases() { - - appCtx := suite.AppContextForTest() - branch := models.AffiliationAIRFORCE - originDutyLocationIsOconus := true - newDutyLocationIsOconus := false - grade := models.ServiceMemberGradeE1 - orderType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION - dependentsAuthorized := true - isAccompaniedTour := true - dependentsUnderTwelve := 0 - dependentsTwelveAndOver := 0 - - suite.Run("Air Force gets a UB allowance", func() { - ubAllowance, err := models.GetUBWeightAllowance(appCtx, &originDutyLocationIsOconus, &newDutyLocationIsOconus, &branch, &grade, &orderType, &dependentsAuthorized, &isAccompaniedTour, &dependentsUnderTwelve, &dependentsTwelveAndOver) - suite.NoError(err) - suite.Assertions.Equal(2000, ubAllowance) - }) - - branch = models.AffiliationSPACEFORCE - suite.Run("Space Force gets the same UB allowance as Air Force", func() { - ubAllowance, err := models.GetUBWeightAllowance(appCtx, &originDutyLocationIsOconus, &newDutyLocationIsOconus, &branch, &grade, &orderType, &dependentsAuthorized, &isAccompaniedTour, &dependentsUnderTwelve, &dependentsTwelveAndOver) - suite.NoError(err) - suite.Assertions.Equal(2000, ubAllowance) - }) - - branch = models.AffiliationNAVY - grade = models.ServiceMemberGradeE9 - suite.Run("Pay grade E9 gets a UB allowance", func() { - ubAllowance, err := models.GetUBWeightAllowance(appCtx, &originDutyLocationIsOconus, &newDutyLocationIsOconus, &branch, &grade, &orderType, &dependentsAuthorized, &isAccompaniedTour, &dependentsUnderTwelve, &dependentsTwelveAndOver) - suite.NoError(err) - suite.Assertions.Equal(2000, ubAllowance) - }) - - grade = models.ServiceMemberGradeE9SPECIALSENIORENLISTED - suite.Run("Pay grade E9 Special Senior Enlisted and pay grade E9 get the same UB allowance", func() { - ubAllowance, err := models.GetUBWeightAllowance(appCtx, &originDutyLocationIsOconus, &newDutyLocationIsOconus, &branch, &grade, &orderType, &dependentsAuthorized, &isAccompaniedTour, &dependentsUnderTwelve, &dependentsTwelveAndOver) - suite.NoError(err) - suite.Assertions.Equal(2000, ubAllowance) - }) -} - -func (suite *ModelSuite) TestGetUBWeightAllowanceWithValidValues() { - appCtx := suite.AppContextForTest() - branch := models.AffiliationMARINES - originDutyLocationIsOconus := true - newDutyLocationIsOconus := false - grade := models.ServiceMemberGradeE1 - orderType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION - dependentsAuthorized := true - isAccompaniedTour := true - dependentsUnderTwelve := 2 - dependentsTwelveAndOver := 4 - - suite.Run("UB allowance is calculated when origin duty location is OCONUS", func() { - ubAllowance, err := models.GetUBWeightAllowance(appCtx, &originDutyLocationIsOconus, &newDutyLocationIsOconus, &branch, &grade, &orderType, &dependentsAuthorized, &isAccompaniedTour, &dependentsUnderTwelve, &dependentsTwelveAndOver) - suite.NoError(err) - suite.Assertions.Equal(2000, ubAllowance) - }) - - originDutyLocationIsOconus = false - newDutyLocationIsOconus = true - suite.Run("UB allowance is calculated when new duty location is OCONUS", func() { - ubAllowance, err := models.GetUBWeightAllowance(appCtx, &originDutyLocationIsOconus, &newDutyLocationIsOconus, &branch, &grade, &orderType, &dependentsAuthorized, &isAccompaniedTour, &dependentsUnderTwelve, &dependentsTwelveAndOver) - suite.NoError(err) - suite.Assertions.Equal(2000, ubAllowance) - }) - - suite.Run("OCONUS, Marines, E1, PCS, dependents are authorized, is accompanied = 2000 lbs", func() { - ubAllowance, err := models.GetUBWeightAllowance(appCtx, &originDutyLocationIsOconus, &newDutyLocationIsOconus, &branch, &grade, &orderType, &dependentsAuthorized, &isAccompaniedTour, &dependentsUnderTwelve, &dependentsTwelveAndOver) - suite.NoError(err) - suite.Assertions.Equal(2000, ubAllowance) - }) - - suite.Run("OCONUS, Marines, E1, PCS, dependents are authorized, is accompanied = 2000 lbs", func() { - ubAllowance, err := models.GetUBWeightAllowance(appCtx, &originDutyLocationIsOconus, &newDutyLocationIsOconus, &branch, &grade, &orderType, &dependentsAuthorized, &isAccompaniedTour, &dependentsUnderTwelve, &dependentsTwelveAndOver) - suite.NoError(err) - suite.Assertions.Equal(2000, ubAllowance) - }) - - branch = models.AffiliationAIRFORCE - suite.Run("OCONUS, Air Force, E1, PCS, dependents are authorized, is accompanied = 2000 lbs", func() { - ubAllowance, err := models.GetUBWeightAllowance(appCtx, &originDutyLocationIsOconus, &newDutyLocationIsOconus, &branch, &grade, &orderType, &dependentsAuthorized, &isAccompaniedTour, &dependentsUnderTwelve, &dependentsTwelveAndOver) - suite.NoError(err) - suite.Assertions.Equal(2000, ubAllowance) - }) - - orderType = internalmessages.OrdersTypeTEMPORARYDUTY - dependentsAuthorized = false - isAccompaniedTour = false - suite.Run("OCONUS, Air Force, E1, Temporary Duty, dependents are NOT authorized, is NOT accompanied = 400 lbs", func() { - ubAllowance, err := models.GetUBWeightAllowance(appCtx, &originDutyLocationIsOconus, &newDutyLocationIsOconus, &branch, &grade, &orderType, &dependentsAuthorized, &isAccompaniedTour, &dependentsUnderTwelve, &dependentsTwelveAndOver) - suite.NoError(err) - suite.Assertions.Equal(400, ubAllowance) - }) - - grade = models.ServiceMemberGradeW2 - retirementOrderType := internalmessages.OrdersTypeRETIREMENT - orderType = internalmessages.OrdersTypePERMANENTCHANGEOFSTATION - suite.Run("Orders type of Retirement returns same entitlement value as the PCS orders type in the database", func() { - ubAllowance, err := models.GetUBWeightAllowance(appCtx, &originDutyLocationIsOconus, &newDutyLocationIsOconus, &branch, &grade, &retirementOrderType, &dependentsAuthorized, &isAccompaniedTour, &dependentsUnderTwelve, &dependentsTwelveAndOver) - suite.NoError(err) - suite.Assertions.Equal(600, ubAllowance) - }) - - orderType = internalmessages.OrdersTypeTEMPORARYDUTY - suite.Run("OCONUS, Air Force, W1, Temporary Duty, dependents are NOT authorized, is NOT accompanied = 600", func() { - ubAllowance, err := models.GetUBWeightAllowance(appCtx, &originDutyLocationIsOconus, &newDutyLocationIsOconus, &branch, &grade, &orderType, &dependentsAuthorized, &isAccompaniedTour, &dependentsUnderTwelve, &dependentsTwelveAndOver) - suite.NoError(err) - suite.Assertions.Equal(600, ubAllowance) - }) -} - -func (suite *ModelSuite) TestGetEntitlementByOrdersTypeWithValidValues() { - E1 := models.ServiceMemberGradeE1 - ordersType := internalmessages.OrdersTypeSTUDENTTRAVEL - - suite.Run("Student Travel with dependents", func() { - STFullLoad := models.GetWeightAllotment(E1, ordersType) - suite.Assertions.Equal(350, STFullLoad.TotalWeightSelfPlusDependents) - }) - - suite.Run("Student Travel without dependents", func() { - STSolo := models.GetWeightAllotment(E1, ordersType) - suite.Assertions.Equal(350, STSolo.TotalWeightSelf) - }) - - suite.Run("Student Travel Pro Gear", func() { - STProGear := models.GetWeightAllotment(E1, ordersType) - suite.Assertions.Equal(0, STProGear.ProGearWeight) - }) - - suite.Run("Student Travel Pro Gear Spouse", func() { - STProGearSpouse := models.GetWeightAllotment(E1, ordersType) - suite.Assertions.Equal(0, STProGearSpouse.ProGearWeightSpouse) - }) -} diff --git a/pkg/models/ghc_entitlements.go b/pkg/models/ghc_entitlements.go index 383caada3a7..f56c915ad19 100644 --- a/pkg/models/ghc_entitlements.go +++ b/pkg/models/ghc_entitlements.go @@ -1,13 +1,16 @@ package models import ( + "fmt" "time" "github.com/gobuffalo/pop/v6" "github.com/gobuffalo/validate/v3" "github.com/gobuffalo/validate/v3/validators" "github.com/gofrs/uuid" + "github.com/pkg/errors" + "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/gen/internalmessages" ) @@ -31,6 +34,7 @@ type Entitlement struct { OrganizationalClothingAndIndividualEquipment bool `db:"organizational_clothing_and_individual_equipment"` ProGearWeight int `db:"pro_gear_weight"` ProGearWeightSpouse int `db:"pro_gear_weight_spouse"` + WeightRestriction *int `db:"weight_restriction"` CreatedAt time.Time `db:"created_at"` UpdatedAt time.Time `db:"updated_at"` } @@ -66,15 +70,6 @@ func (e *Entitlement) Validate(*pop.Connection) (*validate.Errors, error) { return validate.Validate(vs...), nil } -// SetWeightAllotment sets the weight allotment -// TODO probably want to reconsider keeping grade a string rather than enum -// TODO and possibly consider creating ghc specific GetWeightAllotment should the two -// TODO diverge in the future -func (e *Entitlement) SetWeightAllotment(grade string, ordersType internalmessages.OrdersType) { - wa := GetWeightAllotment(internalmessages.OrderPayGrade(grade), ordersType) - e.WeightAllotted = &wa -} - // WeightAllotment returns the weight allotment func (e *Entitlement) WeightAllotment() *WeightAllotment { return e.WeightAllotted @@ -129,3 +124,116 @@ func (e *Entitlement) UBWeightAllowance() *int { return nil } } + +// GetUBWeightAllowance returns the UB weight allowance for a UB shipment, part of the overall entitlements for an order +func GetUBWeightAllowance(appCtx appcontext.AppContext, originDutyLocationIsOconus *bool, newDutyLocationIsOconus *bool, branch *ServiceMemberAffiliation, grade *internalmessages.OrderPayGrade, orderType *internalmessages.OrdersType, dependentsAuthorized *bool, isAccompaniedTour *bool, dependentsUnderTwelve *int, dependentsTwelveAndOver *int) (int, error) { + originDutyLocationIsOconusValue := false + if originDutyLocationIsOconus != nil { + originDutyLocationIsOconusValue = *originDutyLocationIsOconus + } + newDutyLocationIsOconusValue := false + if newDutyLocationIsOconus != nil { + newDutyLocationIsOconusValue = *newDutyLocationIsOconus + } + branchOfService := "" + if branch != nil { + branchOfService = string(*branch) + } + orderPayGrade := "" + if grade != nil { + orderPayGrade = string(*grade) + } + typeOfOrder := "" + if orderType != nil { + typeOfOrder = string(*orderType) + } + dependentsAreAuthorized := false + if dependentsAuthorized != nil { + dependentsAreAuthorized = *dependentsAuthorized + } + isAnAccompaniedTour := false + if isAccompaniedTour != nil { + isAnAccompaniedTour = *isAccompaniedTour + } + underTwelveDependents := 0 + if dependentsUnderTwelve != nil { + underTwelveDependents = *dependentsUnderTwelve + } + twelveAndOverDependents := 0 + if dependentsTwelveAndOver != nil { + twelveAndOverDependents = *dependentsTwelveAndOver + } + + // only calculate UB allowance if either origin or new duty locations are OCONUS + if originDutyLocationIsOconusValue || newDutyLocationIsOconusValue { + + const civilianBaseUBAllowance = 350 + const dependents12AndOverUBAllowance = 350 + const depedentsUnder12UBAllowance = 175 + const maxWholeFamilyCivilianUBAllowance = 2000 + const studentTravelMaxAllowance = 350 + ubAllowance := 0 + + if typeOfOrder == string(internalmessages.OrdersTypeSTUDENTTRAVEL) { + ubAllowance = studentTravelMaxAllowance + } else if orderPayGrade == string(internalmessages.OrderPayGradeCIVILIANEMPLOYEE) && dependentsAreAuthorized && underTwelveDependents == 0 && twelveAndOverDependents == 0 { + ubAllowance = civilianBaseUBAllowance + } else if orderPayGrade == string(internalmessages.OrderPayGradeCIVILIANEMPLOYEE) && dependentsAreAuthorized && (underTwelveDependents > 0 || twelveAndOverDependents > 0) { + ubAllowance = civilianBaseUBAllowance + // for each dependent 12 and older, add an additional 350 lbs to the civilian's baggage allowance + ubAllowance += twelveAndOverDependents * dependents12AndOverUBAllowance + // for each dependent under 12, add an additional 175 lbs to the civilian's baggage allowance + ubAllowance += underTwelveDependents * depedentsUnder12UBAllowance + // max allowance of 2,000 lbs for entire family + if ubAllowance > maxWholeFamilyCivilianUBAllowance { + ubAllowance = maxWholeFamilyCivilianUBAllowance + } + } else { + if typeOfOrder == string(internalmessages.OrdersTypeLOCALMOVE) { + // no UB allowance for local moves + return 0, nil + } else if typeOfOrder != string(internalmessages.OrdersTypeTEMPORARYDUTY) { + // all order types other than temporary duty are treated as permanent change of station types for the lookup + typeOfOrder = string(internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) + } + // space force members entitled to the same allowance as air force members + if branchOfService == AffiliationSPACEFORCE.String() { + branchOfService = AffiliationAIRFORCE.String() + } + // e9 special senior enlisted members entitled to the same allowance as e9 members + if orderPayGrade == string(ServiceMemberGradeE9SPECIALSENIORENLISTED) { + orderPayGrade = string(ServiceMemberGradeE9) + } + + var baseUBAllowance UBAllowances + err := appCtx.DB().Where("branch = ? AND grade = ? AND orders_type = ? AND dependents_authorized = ? AND accompanied_tour = ?", branchOfService, orderPayGrade, typeOfOrder, dependentsAreAuthorized, isAnAccompaniedTour).First(&baseUBAllowance) + if err != nil { + if errors.Cause(err).Error() == RecordNotFoundErrorString { + message := fmt.Sprintf("No UB allowance entry found in ub_allowances table for branch: %s, grade: %s, orders_type: %s, dependents_authorized: %t, accompanied_tour: %t.", branchOfService, orderPayGrade, typeOfOrder, dependentsAreAuthorized, isAnAccompaniedTour) + appCtx.Logger().Info(message) + return 0, nil + } + return 0, err + } + if baseUBAllowance.UBAllowance != nil { + ubAllowance = *baseUBAllowance.UBAllowance + return ubAllowance, nil + } else { + return 0, nil + } + } + return ubAllowance, nil + } else { + appCtx.Logger().Info("No OCONUS duty location found for orders, no UB allowance calculated as part of order entitlement.") + return 0, nil + } +} + +// WeightAllotment represents the weights allotted for a rank +type WeightAllotment struct { + TotalWeightSelf int + TotalWeightSelfPlusDependents int + ProGearWeight int + ProGearWeightSpouse int + UnaccompaniedBaggageAllowance int +} diff --git a/pkg/models/ghc_entitlements_test.go b/pkg/models/ghc_entitlements_test.go index d5e56ca96b7..f3cd227f2f1 100644 --- a/pkg/models/ghc_entitlements_test.go +++ b/pkg/models/ghc_entitlements_test.go @@ -1,7 +1,6 @@ package models_test import ( - "github.com/transcom/mymove/pkg/gen/internalmessages" "github.com/transcom/mymove/pkg/models" ) @@ -14,23 +13,6 @@ func (suite *ModelSuite) TestAuthorizedWeightWhenExistsInDB() { suite.Equal(entitlement.DBAuthorizedWeight, entitlement.AuthorizedWeight()) } -func (suite *ModelSuite) TestAuthorizedWeightWhenNotInDBAndHaveWeightAllotment() { - suite.Run("with no dependents authorized, TotalWeightSelf is AuthorizedWeight", func() { - entitlement := models.Entitlement{} - entitlement.SetWeightAllotment("E_1", internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) - - suite.Equal(entitlement.WeightAllotment().TotalWeightSelf, *entitlement.AuthorizedWeight()) - }) - - suite.Run("with dependents authorized, TotalWeightSelfPlusDependents is AuthorizedWeight", func() { - dependentsAuthorized := true - entitlement := models.Entitlement{DependentsAuthorized: &dependentsAuthorized} - entitlement.SetWeightAllotment("E_1", internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) - - suite.Equal(entitlement.WeightAllotment().TotalWeightSelfPlusDependents, *entitlement.AuthorizedWeight()) - }) -} - func (suite *ModelSuite) TestProGearAndProGearSpouseWeight() { suite.Run("no validation errors for ProGearWeight and ProGearSpouseWeight", func() { entitlement := models.Entitlement{ diff --git a/pkg/models/hhg_allowance.go b/pkg/models/hhg_allowance.go new file mode 100644 index 00000000000..d9c3972637f --- /dev/null +++ b/pkg/models/hhg_allowance.go @@ -0,0 +1,43 @@ +package models + +import ( + "time" + + "github.com/gobuffalo/pop/v6" + "github.com/gobuffalo/validate/v3" + "github.com/gobuffalo/validate/v3/validators" + "github.com/gofrs/uuid" +) + +// HHGAllowance the allowance in weights for a given pay grade +type HHGAllowance struct { + ID uuid.UUID `json:"id" db:"id"` + PayGradeID uuid.UUID `json:"pay_grade_id" db:"pay_grade_id"` + PayGrade PayGrade `belongs_to:"pay_grades" fk_id:"pay_grade_id"` + TotalWeightSelf int `json:"total_weight_self" db:"total_weight_self"` + TotalWeightSelfPlusDependents int `json:"total_weight_self_plus_dependents" db:"total_weight_self_plus_dependents"` + ProGearWeight int `json:"pro_gear_weight" db:"pro_gear_weight"` + ProGearWeightSpouse int `json:"pro_gear_weight_spouse" db:"pro_gear_weight_spouse"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + UpdatedAt time.Time `json:"updated_at" db:"updated_at"` +} + +// Validate gets run every time you call a "pop.Validate*" method +func (h HHGAllowance) Validate(_ *pop.Connection) (*validate.Errors, error) { + return validate.Validate( + &validators.UUIDIsPresent{Name: "PayGradeID", Field: h.PayGradeID}, + // Make sure we don't somehow get a negative value + &validators.IntIsGreaterThan{Name: "TotalWeightSelf", Field: h.TotalWeightSelf, Compared: -1}, + &validators.IntIsGreaterThan{Name: "TotalWeightSelfPlusDependents", Field: h.TotalWeightSelfPlusDependents, Compared: -1}, + &validators.IntIsGreaterThan{Name: "ProGearWeight", Field: h.ProGearWeight, Compared: -1}, + &validators.IntIsGreaterThan{Name: "ProGearWeightSpouse", Field: h.ProGearWeightSpouse, Compared: -1}, + ), nil +} + +// HHGAllowances is a slice of HHGAllowance +type HHGAllowances []HHGAllowance + +// TableName overrides the table name used by Pop. +func (h HHGAllowance) TableName() string { + return "hhg_allowances" +} diff --git a/pkg/models/hhg_allowance_test.go b/pkg/models/hhg_allowance_test.go new file mode 100644 index 00000000000..f3df8346a95 --- /dev/null +++ b/pkg/models/hhg_allowance_test.go @@ -0,0 +1,53 @@ +package models_test + +import ( + "github.com/gofrs/uuid" + + m "github.com/transcom/mymove/pkg/models" +) + +func (suite *ModelSuite) TestBasicHHGAllowanceInstantiation() { + + newHHGAllowance := &m.HHGAllowance{ + PayGradeID: uuid.Must(uuid.NewV4()), + TotalWeightSelf: 5000, + TotalWeightSelfPlusDependents: 8000, + ProGearWeight: 2000, + ProGearWeightSpouse: 500, + } + + verrs, err := newHHGAllowance.Validate(nil) + + suite.NoError(err) + suite.False(verrs.HasAny(), "Error validating model") +} + +func (suite *ModelSuite) TestEmptyHHGAllowanceInstantiation() { + newHHGAllowance := m.HHGAllowance{} + + expErrors := map[string][]string{ + "pay_grade_id": {"PayGradeID can not be blank."}, + } + + suite.verifyValidationErrors(&newHHGAllowance, expErrors) +} + +// Test validation fields that pass when empty but fail with faulty values +func (suite *ModelSuite) TestFaultyHHGAllowanceInstantiation() { + newHHGAllowance := m.HHGAllowance{ + PayGradeID: uuid.Must(uuid.NewV4()), + TotalWeightSelf: -1, + TotalWeightSelfPlusDependents: -1, + ProGearWeight: -1, + ProGearWeightSpouse: -1, + } + + expErrors := map[string][]string{ + "total_weight_self": {"-1 is not greater than -1."}, + "total_weight_self_plus_dependents": {"-1 is not greater than -1."}, + "pro_gear_weight": {"-1 is not greater than -1."}, + "pro_gear_weight_spouse": {"-1 is not greater than -1."}, + } + + suite.verifyValidationErrors(&newHHGAllowance, expErrors) +} diff --git a/pkg/models/order.go b/pkg/models/order.go index 84d5c215349..476b96b1d43 100644 --- a/pkg/models/order.go +++ b/pkg/models/order.go @@ -1,6 +1,9 @@ package models import ( + "bytes" + "encoding/json" + "fmt" "sort" "time" @@ -185,6 +188,18 @@ func (o *Order) Cancel() error { return nil } +var ordersTypeToAllotmentAppParamName = map[internalmessages.OrdersType]string{ + internalmessages.OrdersTypeSTUDENTTRAVEL: "studentTravelHhgAllowance", +} + +// Helper func to enforce strict unmarshal of application param values into a given interface +func strictUnmarshal(data []byte, v interface{}) error { + decoder := json.NewDecoder(bytes.NewReader(data)) + // Fail on unknown fields + decoder.DisallowUnknownFields() + return decoder.Decode(v) +} + // FetchOrderForUser returns orders only if it is allowed for the given user to access those orders. func FetchOrderForUser(db *pop.Connection, session *auth.Session, id uuid.UUID) (Order, error) { var order Order @@ -209,6 +224,52 @@ func FetchOrderForUser(db *pop.Connection, session *auth.Session, id uuid.UUID) return Order{}, err } + // Conduct allotment lookup + if order.Entitlement != nil && order.Grade != nil { + switch order.OrdersType { + case internalmessages.OrdersTypeSTUDENTTRAVEL: + // We currently store orders allotment overrides as an application parameter + // as it is a current one-off use case introduced by E-06189 + var jsonData json.RawMessage + if paramName, ok := ordersTypeToAllotmentAppParamName[order.OrdersType]; ok { + err := db.RawQuery(` + SELECT parameter_json + FROM application_parameters + WHERE parameter_name = $1 + `, paramName).First(&jsonData) + + if err != nil { + return Order{}, fmt.Errorf("failed to fetch weight allotment for orders type %s: %w", order.OrdersType, err) + } + + // Convert the JSON data to the WeightAllotment struct + err = strictUnmarshal(jsonData, &order.Entitlement.WeightAllotted) + if err != nil { + return Order{}, fmt.Errorf("failed to parse weight allotment JSON for orders type %s: %w", order.OrdersType, err) + } + } + default: + var hhgAllowance HHGAllowance + err = db.RawQuery(` + SELECT hhg_allowances.* + FROM hhg_allowances + INNER JOIN pay_grades ON hhg_allowances.pay_grade_id = pay_grades.id + WHERE pay_grades.grade = $1 + LIMIT 1 + `, order.Grade).First(&hhgAllowance) + if err != nil { + return Order{}, fmt.Errorf("failed to parse weight allotment JSON for orders type %s: %w", order.OrdersType, err) + } + order.Entitlement.WeightAllotted = &WeightAllotment{ + TotalWeightSelf: hhgAllowance.TotalWeightSelf, + TotalWeightSelfPlusDependents: hhgAllowance.TotalWeightSelfPlusDependents, + ProGearWeight: hhgAllowance.ProGearWeight, + ProGearWeightSpouse: hhgAllowance.ProGearWeightSpouse, + } + + } + } + // TODO: Handle case where more than one user is authorized to modify orders if session.IsMilApp() && order.ServiceMember.ID != session.ServiceMemberID { return Order{}, ErrFetchForbidden diff --git a/pkg/models/order_test.go b/pkg/models/order_test.go index 6ef09fe7598..b275f131753 100644 --- a/pkg/models/order_test.go +++ b/pkg/models/order_test.go @@ -1,6 +1,7 @@ package models_test import ( + "encoding/json" "time" "github.com/gofrs/uuid" @@ -115,6 +116,22 @@ func (suite *ModelSuite) TestTacFormat() { } func (suite *ModelSuite) TestFetchOrderForUser() { + setupHhgStudentAllowanceParameter := func() { + paramJSON := `{ + "TotalWeightSelf": 350, + "TotalWeightSelfPlusDependents": 350, + "ProGearWeight": 0, + "ProGearWeightSpouse": 0, + "UnaccompaniedBaggageAllowance": 100 + }` + rawMessage := json.RawMessage(paramJSON) + + parameter := models.ApplicationParameters{ + ParameterName: models.StringPointer("studentTravelHhgAllowance"), + ParameterJson: &rawMessage, + } + suite.MustCreate(¶meter) + } suite.Run("successful fetch by authorized user", func() { order := factory.BuildOrder(suite.DB(), nil, nil) @@ -139,6 +156,35 @@ func (suite *ModelSuite) TestFetchOrderForUser() { suite.Equal(order.UploadedOrdersID, goodOrder.UploadedOrdersID) }) + suite.Run("retrieves student entitlement properly for user", func() { + setupHhgStudentAllowanceParameter() + order := factory.BuildOrder(suite.DB(), []factory.Customization{ + { + Model: models.Order{ + OrdersType: internalmessages.OrdersTypeSTUDENTTRAVEL, + }, + }, + }, nil) + + // User is authorized to fetch order + session := &auth.Session{ + ApplicationName: auth.MilApp, + UserID: order.ServiceMember.UserID, + ServiceMemberID: order.ServiceMemberID, + } + goodOrder, err := m.FetchOrderForUser(suite.DB(), session, order.ID) + + suite.NoError(err) + suite.FatalNotNil(goodOrder.Entitlement) + suite.FatalNotNil(goodOrder.Entitlement.WeightAllotted) + suite.Equal(goodOrder.Entitlement.WeightAllotted.TotalWeightSelf, 350) + suite.Equal(goodOrder.Entitlement.WeightAllotted.TotalWeightSelfPlusDependents, 350) + suite.Equal(goodOrder.Entitlement.WeightAllotted.ProGearWeight, 0) + suite.Equal(goodOrder.Entitlement.WeightAllotted.ProGearWeightSpouse, 0) + suite.Equal(goodOrder.Entitlement.WeightAllotted.UnaccompaniedBaggageAllowance, 100) + + }) + suite.Run("check for closeout office", func() { closeoutOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) move := factory.BuildMove(suite.DB(), []factory.Customization{ diff --git a/pkg/models/pay_grade.go b/pkg/models/pay_grade.go new file mode 100644 index 00000000000..a427e0d7dcf --- /dev/null +++ b/pkg/models/pay_grade.go @@ -0,0 +1,29 @@ +package models + +import ( + "time" + + "github.com/gobuffalo/pop/v6" + "github.com/gobuffalo/validate/v3" + "github.com/gobuffalo/validate/v3/validators" + "github.com/gofrs/uuid" +) + +// PayGrade represents a customer's pay grade (Including civilian) +type PayGrade struct { + ID uuid.UUID `json:"id" db:"id"` + Grade string `json:"grade" db:"grade"` + GradeDescription *string `json:"grade_description" db:"grade_description"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + UpdatedAt time.Time `json:"updated_at" db:"updated_at"` +} + +// Validate gets run every time you call a "pop.Validate*" method +func (pg PayGrade) Validate(_ *pop.Connection) (*validate.Errors, error) { + return validate.Validate( + &validators.StringIsPresent{Name: "Grade", Field: pg.Grade}, + ), nil +} + +// PayGrades is a slice of PayGrade +type PayGrades []PayGrade diff --git a/pkg/models/pay_grade_test.go b/pkg/models/pay_grade_test.go new file mode 100644 index 00000000000..3a5cf233bc2 --- /dev/null +++ b/pkg/models/pay_grade_test.go @@ -0,0 +1,25 @@ +package models_test + +import ( + m "github.com/transcom/mymove/pkg/models" +) + +func (suite *ModelSuite) TestBasicPayGradeInstantiation() { + newPayGrade := &m.PayGrade{ + Grade: "NewGrade", + } + + verrs, err := newPayGrade.Validate(nil) + + suite.NoError(err) + suite.False(verrs.HasAny(), "Error validating model") +} + +func (suite *ModelSuite) TestEmptyPayGradeInstantiation() { + newPayGrade := m.PayGrade{} + + expErrors := map[string][]string{ + "grade": {"Grade can not be blank."}, + } + suite.verifyValidationErrors(&newPayGrade, expErrors) +} diff --git a/pkg/models/queue_test.go b/pkg/models/queue_test.go deleted file mode 100644 index 18d2e3b90c0..00000000000 --- a/pkg/models/queue_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package models_test - -import ( - "github.com/transcom/mymove/pkg/factory" - m "github.com/transcom/mymove/pkg/models" -) - -func (suite *ModelSuite) TestCreateMoveWithPPMShow() { - - factory.BuildMoveWithPPMShipment(suite.DB(), nil, nil) - - moves, moveErrs := m.GetMoveQueueItems(suite.DB(), "all") - suite.Nil(moveErrs) - suite.Len(moves, 1) -} - -func (suite *ModelSuite) TestCreateMoveWithPPMNoShow() { - moveTemplate := m.Move{ - Show: m.BoolPointer(false), - } - factory.BuildMoveWithPPMShipment(suite.DB(), []factory.Customization{ - { - Model: moveTemplate, - }, - }, nil) - - moves, moveErrs := m.GetMoveQueueItems(suite.DB(), "all") - suite.Nil(moveErrs) - suite.Empty(moves) - -} - -func (suite *ModelSuite) TestCreateNewMoveWithNoPPMShow() { - orders := factory.BuildOrder(suite.DB(), nil, nil) - factory.FetchOrBuildDefaultContractor(suite.DB(), nil, nil) - office := factory.BuildTransportationOffice(suite.DB(), nil, nil) - - moveOptions := m.MoveOptions{ - Show: m.BoolPointer(true), - CounselingOfficeID: &office.ID, - } - _, verrs, err := orders.CreateNewMove(suite.DB(), moveOptions) - suite.NoError(err) - suite.False(verrs.HasAny(), "failed to validate move") - - moves, moveErrs := m.GetMoveQueueItems(suite.DB(), "all") - suite.Nil(moveErrs) - suite.Empty(moves) -} - -func (suite *ModelSuite) TestQueueNotFound() { - moves, moveErrs := m.GetMoveQueueItems(suite.DB(), "queue_not_found") - suite.Equal(m.ErrFetchNotFound, moveErrs, "Expected not to find move queue items") - suite.Empty(moves) -} diff --git a/pkg/notifications/move_submitted.go b/pkg/notifications/move_submitted.go index d9cc1a26431..e553ce2e480 100644 --- a/pkg/notifications/move_submitted.go +++ b/pkg/notifications/move_submitted.go @@ -2,6 +2,7 @@ package notifications import ( "bytes" + "errors" "fmt" html "html/template" text "text/template" @@ -53,6 +54,15 @@ func (m MoveSubmitted) emails(appCtx appcontext.AppContext) ([]emailContent, err return emails, err } + // Nil check here. Previously weight allotments were hard coded and a lookup was placed here. + // Since allotments are now stored in the database, to avoid an import circle we enhance the + // "FetchOrderForUser" to return the allotment, preventing any need for addtional lookup or db querying. + if orders.Entitlement == nil || orders.Entitlement.WeightAllotted == nil { + errMsg := "WeightAllotted is nil during move email generation. Ensure orders fetch includes an entitlement join and the correct pay grade exists" + appCtx.Logger().Error(errMsg, zap.String("orderID", orders.ID.String())) + return nil, errors.New(errMsg) + } + destinationAddress := orders.NewDutyLocation.Name isSeparateeRetiree := orders.OrdersType == internalmessages.OrdersTypeRETIREMENT || orders.OrdersType == internalmessages.OrdersTypeSEPARATION if isSeparateeRetiree && len(move.MTOShipments) > 0 { @@ -90,15 +100,14 @@ func (m MoveSubmitted) emails(appCtx appcontext.AppContext) ([]emailContent, err originDutyLocationName = &originDutyLocation.Name } - totalEntitlement := models.GetWeightAllotment(*orders.Grade, orders.OrdersType) unaccompaniedBaggageAllowance, err := models.GetUBWeightAllowance(appCtx, originDutyLocation.Address.IsOconus, orders.NewDutyLocation.Address.IsOconus, orders.ServiceMember.Affiliation, orders.Grade, &orders.OrdersType, orders.Entitlement.DependentsAuthorized, orders.Entitlement.AccompaniedTour, orders.Entitlement.DependentsUnderTwelve, orders.Entitlement.DependentsTwelveAndOver) if err == nil { - totalEntitlement.UnaccompaniedBaggageAllowance = unaccompaniedBaggageAllowance + orders.Entitlement.WeightAllotted.UnaccompaniedBaggageAllowance = unaccompaniedBaggageAllowance } - weight := totalEntitlement.TotalWeightSelf + weight := orders.Entitlement.WeightAllotted.TotalWeightSelf if orders.HasDependents { - weight = totalEntitlement.TotalWeightSelfPlusDependents + weight = orders.Entitlement.WeightAllotted.TotalWeightSelfPlusDependents } if serviceMember.PersonalEmail == nil { diff --git a/pkg/services/entitlements.go b/pkg/services/entitlements.go new file mode 100644 index 00000000000..58b8ccc09fd --- /dev/null +++ b/pkg/services/entitlements.go @@ -0,0 +1,30 @@ +package services + +import ( + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/gen/internalmessages" + "github.com/transcom/mymove/pkg/models" +) + +// The weight allotment fetcher interface helps identify weight allotments (allowances) for a given grade +// +//go:generate mockery --name WeightAllotmentFetcher +type WeightAllotmentFetcher interface { + GetWeightAllotment(appCtx appcontext.AppContext, grade string, ordersType internalmessages.OrdersType) (models.WeightAllotment, error) + GetAllWeightAllotments(appCtx appcontext.AppContext) (map[internalmessages.OrderPayGrade]models.WeightAllotment, error) + GetWeightAllotmentByOrdersType(appCtx appcontext.AppContext, ordersType internalmessages.OrdersType) (models.WeightAllotment, error) +} + +// The weight restrictor interface helps apply weight restrictions to entitlements +// +//go:generate mockery --name WeightRestrictor +type WeightRestrictor interface { + ApplyWeightRestrictionToEntitlement(appCtx appcontext.AppContext, entitlement models.Entitlement, weightRestriction int, eTag string) (*models.Entitlement, error) + RemoveWeightRestrictionFromEntitlement(appCtx appcontext.AppContext, entitlement models.Entitlement, eTag string) (*models.Entitlement, error) +} + +// The weight allotment saver helps apply weight restrictions to entitlements +// +//go:generate mockery --name WeightRestrictor +type WeightAllotmentSaver interface { +} diff --git a/pkg/services/entitlements/entitlements_service_test.go b/pkg/services/entitlements/entitlements_service_test.go new file mode 100644 index 00000000000..241ebd258cd --- /dev/null +++ b/pkg/services/entitlements/entitlements_service_test.go @@ -0,0 +1,27 @@ +package entitlements + +import ( + "testing" + + "github.com/spf13/afero" + "github.com/stretchr/testify/suite" + + "github.com/transcom/mymove/pkg/testingsuite" +) + +// EntitlementsServiceSuite is a suite for testing entitlement service objects +type EntitlementsServiceSuite struct { + *testingsuite.PopTestSuite + fs *afero.Afero +} + +func TestEntitlementsServiceSuite(t *testing.T) { + var f = afero.NewMemMapFs() + file := &afero.Afero{Fs: f} + ts := &EntitlementsServiceSuite{ + PopTestSuite: testingsuite.NewPopTestSuite(testingsuite.CurrentPackage(), testingsuite.WithPerTestTransaction()), + fs: file, + } + suite.Run(t, ts) + ts.PopTestSuite.TearDown() +} diff --git a/pkg/services/entitlements/weight_allotment_fetcher.go b/pkg/services/entitlements/weight_allotment_fetcher.go new file mode 100644 index 00000000000..718a0e30169 --- /dev/null +++ b/pkg/services/entitlements/weight_allotment_fetcher.go @@ -0,0 +1,122 @@ +package entitlements + +import ( + "bytes" + "encoding/json" + "fmt" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/apperror" + "github.com/transcom/mymove/pkg/gen/internalmessages" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" +) + +type weightAllotmentFetcher struct { +} + +// NewWeightAllotmentFetcher returns a new weight allotment fetcher +func NewWeightAllotmentFetcher() services.WeightAllotmentFetcher { + return &weightAllotmentFetcher{} +} + +func (waf *weightAllotmentFetcher) GetWeightAllotment(appCtx appcontext.AppContext, grade string, ordersType internalmessages.OrdersType) (models.WeightAllotment, error) { + // Check order allotment first + if ordersType == internalmessages.OrdersTypeSTUDENTTRAVEL { // currently only applies to student travel order that limits overall authorized weight + entitlement, err := waf.GetWeightAllotmentByOrdersType(appCtx, ordersType) + if err != nil { + return models.WeightAllotment{}, err + } + return entitlement, nil + } + + // Continue if the orders type is not student travel + var hhgAllowance models.HHGAllowance + err := appCtx.DB(). + RawQuery(` + SELECT hhg_allowances.* + FROM hhg_allowances + INNER JOIN pay_grades ON hhg_allowances.pay_grade_id = pay_grades.id + WHERE pay_grades.grade = $1 + LIMIT 1 + `, grade). + First(&hhgAllowance) + if err != nil { + return models.WeightAllotment{}, apperror.NewQueryError("HHGAllowance", err, fmt.Sprintf("Error retrieving HHG allowance for grade: %s", grade)) + } + + // Convert HHGAllowance to WeightAllotment + weightAllotment := models.WeightAllotment{ + TotalWeightSelf: hhgAllowance.TotalWeightSelf, + TotalWeightSelfPlusDependents: hhgAllowance.TotalWeightSelfPlusDependents, + ProGearWeight: hhgAllowance.ProGearWeight, + ProGearWeightSpouse: hhgAllowance.ProGearWeightSpouse, + } + + return weightAllotment, nil +} + +var ordersTypeToAllotmentAppParamName = map[internalmessages.OrdersType]string{ + internalmessages.OrdersTypeSTUDENTTRAVEL: "studentTravelHhgAllowance", +} + +// Helper func to enforce strict unmarshal of application param values into a given interface +func strictUnmarshal(data []byte, v interface{}) error { + decoder := json.NewDecoder(bytes.NewReader(data)) + // Fail on unknown fields + decoder.DisallowUnknownFields() + return decoder.Decode(v) +} + +func (waf *weightAllotmentFetcher) GetWeightAllotmentByOrdersType(appCtx appcontext.AppContext, ordersType internalmessages.OrdersType) (models.WeightAllotment, error) { + if paramName, ok := ordersTypeToAllotmentAppParamName[ordersType]; ok { + // We currently store orders allotment overrides as an application parameter + // as it is a current one-off use case introduced by E-06189 + var jsonData json.RawMessage + err := appCtx.DB().RawQuery(` + SELECT parameter_json + FROM application_parameters + WHERE parameter_name = $1 + `, paramName).First(&jsonData) + + if err != nil { + return models.WeightAllotment{}, fmt.Errorf("failed to fetch weight allotment for orders type %s: %w", ordersType, err) + } + + // Convert the JSON data to the WeightAllotment struct + var weightAllotment models.WeightAllotment + err = strictUnmarshal(jsonData, &weightAllotment) + if err != nil { + return models.WeightAllotment{}, fmt.Errorf("failed to parse weight allotment JSON for orders type %s: %w", ordersType, err) + } + + return weightAllotment, nil + } + return models.WeightAllotment{}, fmt.Errorf("no entitlement found for orders type %s", ordersType) +} + +func (waf *weightAllotmentFetcher) GetAllWeightAllotments(appCtx appcontext.AppContext) (map[internalmessages.OrderPayGrade]models.WeightAllotment, error) { + var hhgAllowances models.HHGAllowances + err := appCtx.DB(). + Eager("PayGrade"). + All(&hhgAllowances) + if err != nil { + return nil, apperror.NewQueryError("HHGAllowances", err, "Error retrieving all HHG allowances") + } + + weightAllotments := make(map[internalmessages.OrderPayGrade]models.WeightAllotment) + + for _, hhgAllowance := range hhgAllowances { + // Convert HHGAllowance to WeightAllotment + weightAllotment := models.WeightAllotment{ + TotalWeightSelf: hhgAllowance.TotalWeightSelf, + TotalWeightSelfPlusDependents: hhgAllowance.TotalWeightSelfPlusDependents, + ProGearWeight: hhgAllowance.ProGearWeight, + ProGearWeightSpouse: hhgAllowance.ProGearWeightSpouse, + } + + grade := internalmessages.OrderPayGrade(hhgAllowance.PayGrade.Grade) + weightAllotments[grade] = weightAllotment + } + return weightAllotments, nil +} diff --git a/pkg/services/entitlements/weight_allotment_fetcher_test.go b/pkg/services/entitlements/weight_allotment_fetcher_test.go new file mode 100644 index 00000000000..43330be5c09 --- /dev/null +++ b/pkg/services/entitlements/weight_allotment_fetcher_test.go @@ -0,0 +1,128 @@ +package entitlements + +import ( + "encoding/json" + + "github.com/transcom/mymove/pkg/apperror" + "github.com/transcom/mymove/pkg/gen/internalmessages" + "github.com/transcom/mymove/pkg/models" +) + +func (suite *EntitlementsServiceSuite) TestGetWeightAllotment() { + suite.Run("If a weight allotment is fetched by grade, it should be returned", func() { + fetcher := NewWeightAllotmentFetcher() + + allotment, err := fetcher.GetWeightAllotment(suite.AppContextForTest(), "E_1", internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) + + suite.NoError(err) + suite.NotEmpty(allotment) + }) + + suite.Run("If pay grade does not exist, return an error", func() { + fetcher := NewWeightAllotmentFetcher() + + allotment, err := fetcher.GetWeightAllotment(suite.AppContextForTest(), "X-1", internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) + suite.Error(err) + suite.IsType(apperror.QueryError{}, err) + suite.Empty(allotment) + }) +} + +func (suite *EntitlementsServiceSuite) TestGetAllWeightAllotments() { + suite.Run("Successfully fetch all weight allotments", func() { + fetcher := NewWeightAllotmentFetcher() + + allotments, err := fetcher.GetAllWeightAllotments(suite.AppContextForTest()) + suite.NoError(err) + suite.Greater(len(allotments), 0) + }) +} + +func (suite *EntitlementsServiceSuite) TestGetWeightAllotmentByOrdersType() { + setupHhgStudentAllowanceParameter := func() { + paramJSON := `{ + "TotalWeightSelf": 350, + "TotalWeightSelfPlusDependents": 350, + "ProGearWeight": 0, + "ProGearWeightSpouse": 0, + "UnaccompaniedBaggageAllowance": 100 + }` + rawMessage := json.RawMessage(paramJSON) + + parameter := models.ApplicationParameters{ + ParameterName: models.StringPointer("studentTravelHhgAllowance"), + ParameterJson: &rawMessage, + } + suite.MustCreate(¶meter) + } + + suite.Run("Successfully fetch student travel allotment from application_parameters", func() { + setupHhgStudentAllowanceParameter() + fetcher := NewWeightAllotmentFetcher() + + allotment, err := fetcher.GetWeightAllotment( + suite.AppContextForTest(), + "E-1", + internalmessages.OrdersTypeSTUDENTTRAVEL, + ) + + suite.NoError(err) + suite.Equal(350, allotment.TotalWeightSelf) + suite.Equal(350, allotment.TotalWeightSelfPlusDependents) + suite.Equal(0, allotment.ProGearWeight) + suite.Equal(0, allotment.ProGearWeightSpouse) + suite.Equal(100, allotment.UnaccompaniedBaggageAllowance) + }) + + suite.Run("Returns an error if json does not match allotment from db", func() { + // Proper JSON but not proper target struct + faultyParamJSON := `{ + "TotalWeight": 350, + "TotalWeightPlusDependents": 350, + "ProGear": 0, + "ProGearSpouse": 0, + "Allowance": 100 + }` + rawMessage := json.RawMessage(faultyParamJSON) + + parameter := models.ApplicationParameters{ + ParameterName: models.StringPointer("studentTravelHhgAllowance"), + ParameterJson: &rawMessage, + } + suite.MustCreate(¶meter) + + fetcher := NewWeightAllotmentFetcher() + _, err := fetcher.GetWeightAllotmentByOrdersType( + suite.AppContextForTest(), + internalmessages.OrdersTypeSTUDENTTRAVEL, + ) + + suite.Error(err) + suite.Contains(err.Error(), "failed to parse weight allotment JSON for orders type") + + }) + + suite.Run("Returns an error if no application_parameters entry exists for student travel", func() { + // Don’t create the parameter this time for the student travel + fetcher := NewWeightAllotmentFetcher() + _, err := fetcher.GetWeightAllotment( + suite.AppContextForTest(), + "E-1", + internalmessages.OrdersTypeSTUDENTTRAVEL, + ) + + suite.Error(err) + suite.Contains(err.Error(), "failed to fetch weight allotment for orders type STUDENT_TRAVEL: sql: no rows in result set") + }) + + suite.Run("Returns an error if the orders type is not in the ordersTypeToAllotmentAppParamName map", func() { + fetcher := NewWeightAllotmentFetcher() + _, err := fetcher.GetWeightAllotmentByOrdersType( + suite.AppContextForTest(), + internalmessages.OrdersTypeSEPARATION, + ) + + suite.Error(err) + suite.Contains(err.Error(), "no entitlement found for orders type SEPARATION") + }) +} diff --git a/pkg/services/entitlements/weight_restrictor.go b/pkg/services/entitlements/weight_restrictor.go new file mode 100644 index 00000000000..a8255aa51cf --- /dev/null +++ b/pkg/services/entitlements/weight_restrictor.go @@ -0,0 +1,100 @@ +package entitlements + +import ( + "fmt" + + "github.com/pkg/errors" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/apperror" + "github.com/transcom/mymove/pkg/etag" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" +) + +type weightRestrictor struct { +} + +func NewWeightRestrictor() services.WeightRestrictor { + return &weightRestrictor{} +} + +func (wr *weightRestrictor) ApplyWeightRestrictionToEntitlement(appCtx appcontext.AppContext, entitlement models.Entitlement, weightRestriction int, eTag string) (*models.Entitlement, error) { + // First, fetch the latest version of the entitlement for etag check + var originalEntitlement models.Entitlement + err := appCtx.DB().Find(&originalEntitlement, entitlement.ID) + if err != nil { + return nil, apperror.NewQueryError("Entitlements", err, "error fetching entitlement") + } + + // verify ETag + if etag.GenerateEtag(originalEntitlement.UpdatedAt) != eTag { + return nil, apperror.NewPreconditionFailedError(originalEntitlement.ID, nil) + } + + maxHhgAllowance, err := wr.fetchMaxHhgAllowance(appCtx) + if err != nil { + return nil, err + } + + // Don't allow applying a weight restriction above the max allowance, that's silly + if weightRestriction > maxHhgAllowance { + return nil, apperror.NewInvalidInputError(entitlement.ID, + fmt.Errorf("weight restriction %d exceeds max HHG allowance %d", weightRestriction, maxHhgAllowance), + nil, "error applying weight restriction") + } + + // Update the restriction field + originalEntitlement.WeightRestriction = &weightRestriction + + verrs, err := appCtx.DB().ValidateAndUpdate(&originalEntitlement) + if err != nil { + return nil, apperror.NewQueryError("Entitlements", err, "error updating weight restriction for entitlement") + } + if verrs != nil && verrs.HasAny() { + return nil, apperror.NewInvalidInputError(originalEntitlement.ID, err, verrs, "invalid input while updating entitlement") + } + + return &originalEntitlement, nil +} + +func (wr *weightRestrictor) RemoveWeightRestrictionFromEntitlement(appCtx appcontext.AppContext, entitlement models.Entitlement, eTag string) (*models.Entitlement, error) { + // Fetch the latest version of the entitlement for etag check + var originalEntitlement models.Entitlement + err := appCtx.DB().Find(&originalEntitlement, entitlement.ID) + if err != nil { + if errors.Cause(err).Error() == models.RecordNotFoundErrorString { + return nil, apperror.NewNotFoundError(entitlement.ID, "entitlement not found") + } + return nil, apperror.NewQueryError("Entitlements", err, "error fetching entitlement") + } + + // verify ETag + if etag.GenerateEtag(originalEntitlement.UpdatedAt) != eTag { + return nil, apperror.NewPreconditionFailedError(originalEntitlement.ID, nil) + } + + // Update the restriction field + originalEntitlement.WeightRestriction = nil + + verrs, err := appCtx.DB().ValidateAndUpdate(&originalEntitlement) + if err != nil { + return nil, apperror.NewQueryError("Entitlements", err, "error removing weight restriction for entitlement") + } + if verrs != nil && verrs.HasAny() { + return nil, apperror.NewInvalidInputError(originalEntitlement.ID, err, verrs, "invalid input while updating entitlement") + } + + return &originalEntitlement, nil +} + +func (wr *weightRestrictor) fetchMaxHhgAllowance(appCtx appcontext.AppContext) (int, error) { + var maxHhgAllowance int + err := appCtx.DB(). + RawQuery(`SELECT parameter_value::int FROM application_parameters WHERE parameter_name = 'maxHhgAllowance' LIMIT 1`). + First(&maxHhgAllowance) + if err != nil { + return maxHhgAllowance, apperror.NewQueryError("ApplicationParameters", err, "error fetching max HHG allowance") + } + return maxHhgAllowance, nil +} diff --git a/pkg/services/entitlements/weight_restrictor_test.go b/pkg/services/entitlements/weight_restrictor_test.go new file mode 100644 index 00000000000..b9b2b534156 --- /dev/null +++ b/pkg/services/entitlements/weight_restrictor_test.go @@ -0,0 +1,97 @@ +package entitlements + +import ( + "github.com/gofrs/uuid" + + "github.com/transcom/mymove/pkg/apperror" + "github.com/transcom/mymove/pkg/etag" + "github.com/transcom/mymove/pkg/models" +) + +func (suite *EntitlementsServiceSuite) TestWeightRestrictor() { + setupHhgAllowanceParameter := func() { + parameter := models.ApplicationParameters{ + ParameterName: models.StringPointer("maxHhgAllowance"), + ParameterValue: models.StringPointer("18000"), + } + suite.MustCreate(¶meter) + } + + suite.Run("Successfully apply a weight restriction within max allowance", func() { + setupHhgAllowanceParameter() + // Create a blank entitlement db entry, nothing fancy we just want to update columns + entitlement := models.Entitlement{ + ID: uuid.Must(uuid.NewV4()), + } + suite.MustCreate(&entitlement) + + // Set a weight restriction within allowance + restrictor := NewWeightRestrictor() + updatedEntitlement, err := restrictor.ApplyWeightRestrictionToEntitlement(suite.AppContextForTest(), entitlement, 10000, etag.GenerateEtag(entitlement.UpdatedAt)) + suite.NoError(err) + suite.NotNil(updatedEntitlement) + suite.NotNil(updatedEntitlement.WeightRestriction) + suite.Equal(10000, *updatedEntitlement.WeightRestriction) + }) + + suite.Run("Attempt to apply restriction above max allowance, expect an error", func() { + setupHhgAllowanceParameter() + // Create a blank entitlement db entry, nothing fancy we just want to update columns + entitlement := models.Entitlement{ + ID: uuid.Must(uuid.NewV4()), + } + suite.MustCreate(&entitlement) + + // Set an impossible weight restriction + restrictor := NewWeightRestrictor() + updatedEntitlement, err := restrictor.ApplyWeightRestrictionToEntitlement(suite.AppContextForTest(), entitlement, 20000, etag.GenerateEtag(entitlement.UpdatedAt)) + suite.Error(err) + suite.Nil(updatedEntitlement) + suite.IsType(apperror.InvalidInputError{}, err) + }) + + suite.Run("No maxHhgAllowance parameter found returns error", func() { + entitlement := models.Entitlement{ + ID: uuid.Must(uuid.NewV4()), + } + suite.MustCreate(&entitlement) + + restrictor := NewWeightRestrictor() + updatedEntitlement, err := restrictor.ApplyWeightRestrictionToEntitlement(suite.AppContextForTest(), entitlement, 10000, etag.GenerateEtag(entitlement.UpdatedAt)) + suite.Error(err) + suite.Nil(updatedEntitlement) + suite.IsType(apperror.QueryError{}, err) + }) + + suite.Run("Successfully remove a weight restriction", func() { + setupHhgAllowanceParameter() + + // Create an entitlement with a restriction already applied + weightRestriction := 5000 + entitlement := models.Entitlement{ + ID: uuid.Must(uuid.NewV4()), + WeightRestriction: &weightRestriction, + } + suite.MustCreate(&entitlement) + + restrictor := NewWeightRestrictor() + updatedEntitlement, err := restrictor.RemoveWeightRestrictionFromEntitlement(suite.AppContextForTest(), entitlement, etag.GenerateEtag(entitlement.UpdatedAt)) + suite.NoError(err) + suite.NotNil(updatedEntitlement) + suite.Nil(updatedEntitlement.WeightRestriction) + }) + + suite.Run("Fails on removing a weight restriction for an entitlement that does not exist", func() { + setupHhgAllowanceParameter() + + entitlement := models.Entitlement{ + ID: uuid.Must(uuid.NewV4()), + } + + restrictor := NewWeightRestrictor() + updatedEntitlement, err := restrictor.RemoveWeightRestrictionFromEntitlement(suite.AppContextForTest(), entitlement, etag.GenerateEtag(entitlement.UpdatedAt)) + suite.Error(err) + suite.Nil(updatedEntitlement) + suite.IsType(apperror.NotFoundError{}, err) + }) +} diff --git a/pkg/services/move/move_weights.go b/pkg/services/move/move_weights.go index e1fbda2e4f1..0c88fddefba 100644 --- a/pkg/services/move/move_weights.go +++ b/pkg/services/move/move_weights.go @@ -23,12 +23,13 @@ const RiskOfExcessThreshold = .9 const AutoReweighRequestThreshold = .9 type moveWeights struct { - ReweighRequestor services.ShipmentReweighRequester + ReweighRequestor services.ShipmentReweighRequester + WeightAllotmentFetcher services.WeightAllotmentFetcher } // NewMoveWeights creates a new moveWeights service -func NewMoveWeights(reweighRequestor services.ShipmentReweighRequester) services.MoveWeights { - return &moveWeights{ReweighRequestor: reweighRequestor} +func NewMoveWeights(reweighRequestor services.ShipmentReweighRequester, weightAllotmentFetcher services.WeightAllotmentFetcher) services.MoveWeights { + return &moveWeights{ReweighRequestor: reweighRequestor, WeightAllotmentFetcher: weightAllotmentFetcher} } func validateAndSave(appCtx appcontext.AppContext, move *models.Move) (*validate.Errors, error) { @@ -98,7 +99,10 @@ func (w moveWeights) CheckExcessWeight(appCtx appcontext.AppContext, moveID uuid return nil, nil, errors.New("could not determine excess weight entitlement without dependents authorization value") } - totalWeightAllowance := models.GetWeightAllotment(*move.Orders.Grade, move.Orders.OrdersType) + totalWeightAllowance, err := w.WeightAllotmentFetcher.GetWeightAllotment(appCtx, string(*move.Orders.Grade), move.Orders.OrdersType) + if err != nil { + return nil, nil, err + } overallWeightAllowance := totalWeightAllowance.TotalWeightSelf if *move.Orders.Entitlement.DependentsAuthorized { @@ -261,7 +265,10 @@ func (w moveWeights) CheckAutoReweigh(appCtx appcontext.AppContext, moveID uuid. return nil, errors.New("could not determine excess weight entitlement without dependents authorization value") } - totalWeightAllowance := models.GetWeightAllotment(*move.Orders.Grade, move.Orders.OrdersType) + totalWeightAllowance, err := w.WeightAllotmentFetcher.GetWeightAllotment(appCtx, string(*move.Orders.Grade), move.Orders.OrdersType) + if err != nil { + return nil, err + } overallWeightAllowance := totalWeightAllowance.TotalWeightSelf if *move.Orders.Entitlement.DependentsAuthorized { diff --git a/pkg/services/move/move_weights_test.go b/pkg/services/move/move_weights_test.go index 6528830e2e4..60d716c1241 100644 --- a/pkg/services/move/move_weights_test.go +++ b/pkg/services/move/move_weights_test.go @@ -8,6 +8,7 @@ import ( "github.com/transcom/mymove/pkg/auth" "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services/entitlements" "github.com/transcom/mymove/pkg/services/mocks" mtoshipment "github.com/transcom/mymove/pkg/services/mto_shipment" "github.com/transcom/mymove/pkg/testdatagen" @@ -15,7 +16,9 @@ import ( ) func (suite *MoveServiceSuite) TestExcessWeight() { - moveWeights := NewMoveWeights(mtoshipment.NewShipmentReweighRequester()) + waf := entitlements.NewWeightAllotmentFetcher() + + moveWeights := NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) suite.Run("qualifies move for excess weight when an approved shipment estimated weight is updated within threshold", func() { // The default weight allotment for this move is 8000 and the threshold is 90% of that @@ -484,9 +487,10 @@ func (suite *MoveServiceSuite) TestExcessWeight() { } func (suite *MoveServiceSuite) TestAutoReweigh() { - moveWeights := NewMoveWeights(mtoshipment.NewShipmentReweighRequester()) + waf := entitlements.NewWeightAllotmentFetcher() + moveWeights := NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) - suite.Run("requests reweigh on shipment if the acutal weight is 90% of the weight allowance", func() { + suite.Run("requests reweigh on shipment if the actual weight is 90% of the weight allowance", func() { // The default weight allotment for this move is 8000 and the threshold is 90% of that approvedMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) now := time.Now() @@ -524,7 +528,7 @@ func (suite *MoveServiceSuite) TestAutoReweigh() { suite.Run("does not request reweigh on shipments when below 90% of weight allowance threshold", func() { mockedReweighRequestor := mocks.ShipmentReweighRequester{} - mockedWeightService := NewMoveWeights(&mockedReweighRequestor) + mockedWeightService := NewMoveWeights(&mockedReweighRequestor, waf) approvedMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) @@ -615,7 +619,7 @@ func (suite *MoveServiceSuite) TestAutoReweigh() { suite.Run("does not request reweigh when shipments aren't in approved statuses", func() { mockedReweighRequestor := mocks.ShipmentReweighRequester{} - mockedWeightService := NewMoveWeights(&mockedReweighRequestor) + mockedWeightService := NewMoveWeights(&mockedReweighRequestor, waf) approvedMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) now := time.Now() @@ -664,7 +668,7 @@ func (suite *MoveServiceSuite) TestAutoReweigh() { suite.Run("uses lower reweigh weight on shipments that already have reweighs", func() { mockedReweighRequestor := mocks.ShipmentReweighRequester{} - mockedWeightService := NewMoveWeights(&mockedReweighRequestor) + mockedWeightService := NewMoveWeights(&mockedReweighRequestor, waf) approvedMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) now := time.Now() diff --git a/pkg/services/move_task_order/move_task_order_fetcher.go b/pkg/services/move_task_order/move_task_order_fetcher.go index 1d30aef5ceb..b42d76729d2 100644 --- a/pkg/services/move_task_order/move_task_order_fetcher.go +++ b/pkg/services/move_task_order/move_task_order_fetcher.go @@ -20,11 +20,14 @@ import ( ) type moveTaskOrderFetcher struct { + waf services.WeightAllotmentFetcher } // NewMoveTaskOrderFetcher creates a new struct with the service dependencies -func NewMoveTaskOrderFetcher() services.MoveTaskOrderFetcher { - return &moveTaskOrderFetcher{} +func NewMoveTaskOrderFetcher(weightAllotmentFetcher services.WeightAllotmentFetcher) services.MoveTaskOrderFetcher { + return &moveTaskOrderFetcher{ + waf: weightAllotmentFetcher, + } } // ListAllMoveTaskOrders retrieves all Move Task Orders that may or may not be available to prime, and may or may not be enabled. @@ -196,6 +199,16 @@ func (f moveTaskOrderFetcher) FetchMoveTaskOrder(appCtx appcontext.AppContext, s } } + // Now that we have the move and order, construct the allotment (hhg allowance) + // Only fetch if grade is not nil + if mto.Orders.Grade != nil { + allotment, err := f.waf.GetWeightAllotment(appCtx, string(*mto.Orders.Grade), mto.Orders.OrdersType) + if err != nil { + return nil, err + } + mto.Orders.Entitlement.WeightAllotted = &allotment + } + // Due to a bug in Pop for EagerPreload the New Address of the DeliveryAddressUpdate and the PortLocation (City, Country, UsPostRegionCity.UsPostRegion.State") must be loaded manually. // The bug occurs in EagerPreload when there are two or more eager paths with 3+ levels // where the first 2 levels match. For example: diff --git a/pkg/services/move_task_order/move_task_order_fetcher_test.go b/pkg/services/move_task_order/move_task_order_fetcher_test.go index a80be4aa524..6000b23d49a 100644 --- a/pkg/services/move_task_order/move_task_order_fetcher_test.go +++ b/pkg/services/move_task_order/move_task_order_fetcher_test.go @@ -11,12 +11,14 @@ import ( "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/services/entitlements" m "github.com/transcom/mymove/pkg/services/move_task_order" "github.com/transcom/mymove/pkg/testdatagen" "github.com/transcom/mymove/pkg/uploader" ) func (suite *MoveTaskOrderServiceSuite) TestMoveTaskOrderFetcher() { + waf := entitlements.NewWeightAllotmentFetcher() setupTestData := func() (models.Move, models.MTOShipment) { @@ -61,7 +63,7 @@ func (suite *MoveTaskOrderServiceSuite) TestMoveTaskOrderFetcher() { return expectedMTO, primeShipment } - mtoFetcher := m.NewMoveTaskOrderFetcher() + mtoFetcher := m.NewMoveTaskOrderFetcher(waf) suite.Run("Success with fetching a MTO that has a shipment address update", func() { traits := []factory.Trait{factory.GetTraitShipmentAddressUpdateApproved} @@ -576,8 +578,9 @@ func (suite *MoveTaskOrderServiceSuite) TestGetMoveTaskOrderFetcher() { return expectedMTO } + waf := entitlements.NewWeightAllotmentFetcher() - mtoFetcher := m.NewMoveTaskOrderFetcher() + mtoFetcher := m.NewMoveTaskOrderFetcher(waf) suite.Run("success getting a move using GetMove for Prime user", func() { expectedMTO := setupTestData() @@ -705,6 +708,8 @@ func (suite *MoveTaskOrderServiceSuite) TestListAllMoveTaskOrdersFetcher() { // Set up a hidden move so we can check if it's in the output: now := time.Now() show := false + waf := entitlements.NewWeightAllotmentFetcher() + setupTestData := func() (models.Move, models.Move, models.MTOShipment) { hiddenMTO := factory.BuildAvailableToPrimeMove(suite.DB(), []factory.Customization{ { @@ -743,7 +748,7 @@ func (suite *MoveTaskOrderServiceSuite) TestListAllMoveTaskOrdersFetcher() { return hiddenMTO, mto, primeShipment } - mtoFetcher := m.NewMoveTaskOrderFetcher() + mtoFetcher := m.NewMoveTaskOrderFetcher(waf) suite.Run("all move task orders", func() { hiddenMTO, mto, _ := setupTestData() @@ -850,6 +855,7 @@ func (suite *MoveTaskOrderServiceSuite) TestListAllMoveTaskOrdersFetcher() { func (suite *MoveTaskOrderServiceSuite) TestListPrimeMoveTaskOrdersFetcher() { now := time.Now() + waf := entitlements.NewWeightAllotmentFetcher() // Set up a hidden move so we can check if it's in the output: hiddenMove := factory.BuildAvailableToPrimeMove(suite.DB(), []factory.Customization{ { @@ -879,7 +885,7 @@ func (suite *MoveTaskOrderServiceSuite) TestListPrimeMoveTaskOrdersFetcher() { suite.Require().NoError(suite.DB().RawQuery("UPDATE mto_shipments SET updated_at=$1 WHERE id=$2;", now.Add(-10*time.Second), shipmentForPrimeMove4.ID).Exec()) - fetcher := m.NewMoveTaskOrderFetcher() + fetcher := m.NewMoveTaskOrderFetcher(waf) page := int64(1) perPage := int64(20) // filling out search params to allow for pagination @@ -912,6 +918,8 @@ func (suite *MoveTaskOrderServiceSuite) TestListPrimeMoveTaskOrdersFetcher() { } func (suite *MoveTaskOrderServiceSuite) TestListPrimeMoveTaskOrdersAmendmentsFetcher() { + waf := entitlements.NewWeightAllotmentFetcher() + suite.Run("Test with and without filter of moves containing amendments", func() { now := time.Now() // Set up a hidden move so we can check if it's in the output: @@ -995,7 +1003,7 @@ func (suite *MoveTaskOrderServiceSuite) TestListPrimeMoveTaskOrdersAmendmentsFet suite.Require().NoError(suite.DB().RawQuery("UPDATE mto_shipments SET updated_at=$1 WHERE id=$2;", now.Add(-10*time.Second), shipmentForPrimeMove4.ID).Exec()) - fetcher := m.NewMoveTaskOrderFetcher() + fetcher := m.NewMoveTaskOrderFetcher(waf) page := int64(1) perPage := int64(20) // filling out search params to allow for pagination @@ -1071,7 +1079,7 @@ func (suite *MoveTaskOrderServiceSuite) TestListPrimeMoveTaskOrdersAmendmentsFet suite.Require().NoError(suite.DB().RawQuery("UPDATE moves SET updated_at=$1 WHERE id IN ($2, $3);", now.Add(-10*time.Second), primeMove1.ID, primeMove2.ID).Exec()) - fetcher := m.NewMoveTaskOrderFetcher() + fetcher := m.NewMoveTaskOrderFetcher(waf) page := int64(1) perPage := int64(20) // filling out search params to allow for pagination diff --git a/pkg/services/move_task_order/move_task_order_updater.go b/pkg/services/move_task_order/move_task_order_updater.go index 25fbec6856a..d0cd55aed37 100644 --- a/pkg/services/move_task_order/move_task_order_updater.go +++ b/pkg/services/move_task_order/move_task_order_updater.go @@ -15,6 +15,7 @@ import ( "github.com/transcom/mymove/pkg/etag" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/services/entitlements" "github.com/transcom/mymove/pkg/services/order" "github.com/transcom/mymove/pkg/services/query" ) @@ -31,7 +32,12 @@ type moveTaskOrderUpdater struct { // NewMoveTaskOrderUpdater creates a new struct with the service dependencies func NewMoveTaskOrderUpdater(builder UpdateMoveTaskOrderQueryBuilder, serviceItemCreator services.MTOServiceItemCreator, moveRouter services.MoveRouter, signedCertificationCreator services.SignedCertificationCreator, signedCertificationUpdater services.SignedCertificationUpdater, estimator services.PPMEstimator) services.MoveTaskOrderUpdater { - return &moveTaskOrderUpdater{moveTaskOrderFetcher{}, builder, serviceItemCreator, moveRouter, signedCertificationCreator, signedCertificationUpdater, estimator} + // Fetcher dependency + waf := entitlements.NewWeightAllotmentFetcher() + + return &moveTaskOrderUpdater{moveTaskOrderFetcher{ + waf: waf, + }, builder, serviceItemCreator, moveRouter, signedCertificationCreator, signedCertificationUpdater, estimator} } // UpdateStatusServiceCounselingCompleted updates the status on the move (move task order) to service counseling completed diff --git a/pkg/services/mto_shipment/mto_shipment_updater_test.go b/pkg/services/mto_shipment/mto_shipment_updater_test.go index bfced125206..c7075402d1f 100644 --- a/pkg/services/mto_shipment/mto_shipment_updater_test.go +++ b/pkg/services/mto_shipment/mto_shipment_updater_test.go @@ -19,6 +19,7 @@ import ( notificationMocks "github.com/transcom/mymove/pkg/notifications/mocks" "github.com/transcom/mymove/pkg/route/mocks" "github.com/transcom/mymove/pkg/services/address" + "github.com/transcom/mymove/pkg/services/entitlements" "github.com/transcom/mymove/pkg/services/fetch" "github.com/transcom/mymove/pkg/services/ghcrateengine" mockservices "github.com/transcom/mymove/pkg/services/mocks" @@ -54,7 +55,8 @@ func (suite *MTOShipmentServiceSuite) TestMTOShipmentUpdater() { false, ).Return(1000, nil) moveRouter := moveservices.NewMoveRouter() - moveWeights := moveservices.NewMoveWeights(NewShipmentReweighRequester()) + waf := entitlements.NewWeightAllotmentFetcher() + moveWeights := moveservices.NewMoveWeights(NewShipmentReweighRequester(), waf) mockShipmentRecalculator := mockservices.PaymentRequestShipmentRecalculator{} mockShipmentRecalculator.On("ShipmentRecalculatePaymentRequest", mock.AnythingOfType("*appcontext.appContext"), @@ -2775,6 +2777,8 @@ func (suite *MTOShipmentServiceSuite) TestUpdateMTOShipmentStatus() { func (suite *MTOShipmentServiceSuite) TestMTOShipmentsMTOAvailableToPrime() { now := time.Now() + waf := entitlements.NewWeightAllotmentFetcher() + hide := false var primeShipment models.MTOShipment var nonPrimeShipment models.MTOShipment @@ -2805,7 +2809,7 @@ func (suite *MTOShipmentServiceSuite) TestMTOShipmentsMTOAvailableToPrime() { fetcher := fetch.NewFetcher(builder) planner := &mocks.Planner{} moveRouter := moveservices.NewMoveRouter() - moveWeights := moveservices.NewMoveWeights(NewShipmentReweighRequester()) + moveWeights := moveservices.NewMoveWeights(NewShipmentReweighRequester(), waf) mockShipmentRecalculator := mockservices.PaymentRequestShipmentRecalculator{} mockShipmentRecalculator.On("ShipmentRecalculatePaymentRequest", mock.AnythingOfType("*appcontext.appContext"), @@ -2873,8 +2877,10 @@ func (suite *MTOShipmentServiceSuite) TestUpdateShipmentEstimatedWeightMoveExces builder := query.NewQueryBuilder() fetcher := fetch.NewFetcher(builder) planner := &mocks.Planner{} + waf := entitlements.NewWeightAllotmentFetcher() + moveRouter := moveservices.NewMoveRouter() - moveWeights := moveservices.NewMoveWeights(NewShipmentReweighRequester()) + moveWeights := moveservices.NewMoveWeights(NewShipmentReweighRequester(), waf) mockShipmentRecalculator := mockservices.PaymentRequestShipmentRecalculator{} mockShipmentRecalculator.On("ShipmentRecalculatePaymentRequest", mock.AnythingOfType("*appcontext.appContext"), @@ -3054,10 +3060,12 @@ func (suite *MTOShipmentServiceSuite) TestUpdateShipmentEstimatedWeightMoveExces func (suite *MTOShipmentServiceSuite) TestUpdateShipmentActualWeightAutoReweigh() { builder := query.NewQueryBuilder() + waf := entitlements.NewWeightAllotmentFetcher() + fetcher := fetch.NewFetcher(builder) planner := &mocks.Planner{} moveRouter := moveservices.NewMoveRouter() - moveWeights := moveservices.NewMoveWeights(NewShipmentReweighRequester()) + moveWeights := moveservices.NewMoveWeights(NewShipmentReweighRequester(), waf) mockShipmentRecalculator := mockservices.PaymentRequestShipmentRecalculator{} mockShipmentRecalculator.On("ShipmentRecalculatePaymentRequest", mock.AnythingOfType("*appcontext.appContext"), diff --git a/pkg/services/mto_shipment/shipment_approver_test.go b/pkg/services/mto_shipment/shipment_approver_test.go index e0a80dfdf87..265b9133cd1 100644 --- a/pkg/services/mto_shipment/shipment_approver_test.go +++ b/pkg/services/mto_shipment/shipment_approver_test.go @@ -17,6 +17,7 @@ import ( "github.com/transcom/mymove/pkg/route" "github.com/transcom/mymove/pkg/route/mocks" "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/services/entitlements" "github.com/transcom/mymove/pkg/services/ghcrateengine" shipmentmocks "github.com/transcom/mymove/pkg/services/mocks" moverouter "github.com/transcom/mymove/pkg/services/move" @@ -85,6 +86,7 @@ func (suite *MTOShipmentServiceSuite) createApproveShipmentSubtestData() (subtes router := NewShipmentRouter() builder := query.NewQueryBuilder() + waf := entitlements.NewWeightAllotmentFetcher() moveRouter := moverouter.NewMoveRouter() planner := &mocks.Planner{} planner.On("ZipTransitDistance", @@ -96,7 +98,7 @@ func (suite *MTOShipmentServiceSuite) createApproveShipmentSubtestData() (subtes ).Return(400, nil) siCreator := mtoserviceitem.NewMTOServiceItemCreator(planner, builder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) subtestData.planner = &mocks.Planner{} - subtestData.moveWeights = moverouter.NewMoveWeights(NewShipmentReweighRequester()) + subtestData.moveWeights = moverouter.NewMoveWeights(NewShipmentReweighRequester(), waf) subtestData.shipmentApprover = NewShipmentApprover(router, siCreator, subtestData.planner, subtestData.moveWeights) subtestData.mockedShipmentApprover = NewShipmentApprover(subtestData.mockedShipmentRouter, siCreator, subtestData.planner, subtestData.moveWeights) @@ -258,7 +260,8 @@ func (suite *MTOShipmentServiceSuite) TestApproveShipment() { }) shipmentRouter := NewShipmentRouter() - moveWeights := moverouter.NewMoveWeights(NewShipmentReweighRequester()) + waf := entitlements.NewWeightAllotmentFetcher() + moveWeights := moverouter.NewMoveWeights(NewShipmentReweighRequester(), waf) var serviceItemCreator services.MTOServiceItemCreator appCtx := suite.AppContextWithSessionForTest(&auth.Session{ ApplicationName: auth.OfficeApp, diff --git a/pkg/services/order/order_fetcher.go b/pkg/services/order/order_fetcher.go index 524271a345b..6b6c57840c4 100644 --- a/pkg/services/order/order_fetcher.go +++ b/pkg/services/order/order_fetcher.go @@ -25,6 +25,7 @@ import ( const RFC3339Micro = "2006-01-02T15:04:05.999999Z07:00" type orderFetcher struct { + waf services.WeightAllotmentFetcher } // QueryOption defines the type for the functional arguments used for private functions in OrderFetcher @@ -610,8 +611,8 @@ func (f orderFetcher) ListAllOrderLocations(appCtx appcontext.AppContext, office } // NewOrderFetcher creates a new struct with the service dependencies -func NewOrderFetcher() services.OrderFetcher { - return &orderFetcher{} +func NewOrderFetcher(weightAllotmentFetcher services.WeightAllotmentFetcher) services.OrderFetcher { + return &orderFetcher{waf: weightAllotmentFetcher} } // FetchOrder retrieves an Order for a given UUID @@ -635,6 +636,15 @@ func (f orderFetcher) FetchOrder(appCtx appcontext.AppContext, orderID uuid.UUID } } + // Construct weight allotted if grade is present + if order.Grade != nil { + allotment, err := f.waf.GetWeightAllotment(appCtx, string(*order.Grade), order.OrdersType) + if err != nil { + return nil, err + } + order.Entitlement.WeightAllotted = &allotment + } + // Due to a bug in pop (https://github.com/gobuffalo/pop/issues/578), we // cannot eager load the address as "OriginDutyLocation.Address" because // OriginDutyLocation is a pointer. diff --git a/pkg/services/order/order_fetcher_test.go b/pkg/services/order/order_fetcher_test.go index 0f9332c1d9e..15aa5d9863a 100644 --- a/pkg/services/order/order_fetcher_test.go +++ b/pkg/services/order/order_fetcher_test.go @@ -12,6 +12,7 @@ import ( "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/models/roles" "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/services/entitlements" "github.com/transcom/mymove/pkg/services/mocks" moveservice "github.com/transcom/mymove/pkg/services/move" officeuserservice "github.com/transcom/mymove/pkg/services/office_user" @@ -21,7 +22,8 @@ import ( func (suite *OrderServiceSuite) TestFetchOrder() { expectedMove := factory.BuildMove(suite.DB(), nil, nil) expectedOrder := expectedMove.Orders - orderFetcher := NewOrderFetcher() + waf := entitlements.NewWeightAllotmentFetcher() + orderFetcher := NewOrderFetcher(waf) order, err := orderFetcher.FetchOrder(suite.AppContextForTest(), expectedOrder.ID) suite.FatalNoError(err) @@ -51,7 +53,7 @@ func (suite *OrderServiceSuite) TestFetchOrderWithEmptyFields() { // noticed an exception due to trying to load empty OriginDutyLocations. // This was not caught by any tests, so we're adding one now. expectedOrder := factory.BuildOrder(suite.DB(), nil, nil) - + waf := entitlements.NewWeightAllotmentFetcher() expectedOrder.Entitlement = nil expectedOrder.EntitlementID = nil expectedOrder.Grade = nil @@ -66,7 +68,7 @@ func (suite *OrderServiceSuite) TestFetchOrderWithEmptyFields() { }, }, nil) - orderFetcher := NewOrderFetcher() + orderFetcher := NewOrderFetcher(waf) order, err := orderFetcher.FetchOrder(suite.AppContextForTest(), expectedOrder.ID) suite.FatalNoError(err) @@ -76,7 +78,7 @@ func (suite *OrderServiceSuite) TestFetchOrderWithEmptyFields() { } func (suite *OrderServiceSuite) TestListOrders() { - + waf := entitlements.NewWeightAllotmentFetcher() agfmPostalCode := "06001" setupTestData := func() (models.OfficeUser, models.Move, auth.Session) { @@ -97,7 +99,7 @@ func (suite *OrderServiceSuite) TestListOrders() { factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), agfmPostalCode, "AGFM") return officeUser, move, session } - orderFetcher := NewOrderFetcher() + orderFetcher := NewOrderFetcher(waf) suite.Run("returns moves", func() { // Under test: ListOrders @@ -729,6 +731,8 @@ func (suite *OrderServiceSuite) TestListOrders() { } func (suite *OrderServiceSuite) TestListDestinationRequestsOrders() { + waf := entitlements.NewWeightAllotmentFetcher() + setupTestData := func(officeUserGBLOC string) (models.OfficeUser, auth.Session) { officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ @@ -758,7 +762,7 @@ func (suite *OrderServiceSuite) TestListDestinationRequestsOrders() { return officeUser, session } - orderFetcher := NewOrderFetcher() + orderFetcher := NewOrderFetcher(waf) suite.Run("returns moves for KKFA GBLOC when destination address is in KKFA GBLOC", func() { officeUser, session := setupTestData("KKFA") @@ -1347,7 +1351,8 @@ func (suite *OrderServiceSuite) TestListOrderWithAssignedUserSingle() { suite.Equal(createdMove.SCAssignedUser.LastName, moves[0].SCAssignedUser.LastName) } func (suite *OrderServiceSuite) TestListOrdersUSMCGBLOC() { - orderFetcher := NewOrderFetcher() + waf := entitlements.NewWeightAllotmentFetcher() + orderFetcher := NewOrderFetcher(waf) suite.Run("returns USMC order for USMC office user", func() { marines := models.AffiliationMARINES @@ -1483,7 +1488,8 @@ func buildPPMShipmentCloseoutComplete(suite *OrderServiceSuite, move models.Move return ppm } func (suite *OrderServiceSuite) TestListOrdersPPMCloseoutForArmyAirforce() { - orderFetcher := NewOrderFetcher() + waf := entitlements.NewWeightAllotmentFetcher() + orderFetcher := NewOrderFetcher(waf) var session auth.Session @@ -1544,7 +1550,8 @@ func (suite *OrderServiceSuite) TestListOrdersPPMCloseoutForArmyAirforce() { } func (suite *OrderServiceSuite) TestListOrdersPPMCloseoutForNavyCoastGuardAndMarines() { - orderFetcher := NewOrderFetcher() + waf := entitlements.NewWeightAllotmentFetcher() + orderFetcher := NewOrderFetcher(waf) suite.Run("returns Navy order for NAVY office user when there's a ppm shipment in closeout", func() { // It doesn't matter what the Origin GBLOC is for the move. Only the navy @@ -1707,8 +1714,10 @@ func (suite *OrderServiceSuite) TestListOrdersPPMCloseoutForNavyCoastGuardAndMar } func (suite *OrderServiceSuite) TestListOrdersMarines() { + waf := entitlements.NewWeightAllotmentFetcher() suite.Run("does not return moves where the service member affiliation is Marines for non-USMC office user", func() { - orderFetcher := NewOrderFetcher() + + orderFetcher := NewOrderFetcher(waf) marines := models.AffiliationMARINES factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ { @@ -1736,7 +1745,7 @@ func (suite *OrderServiceSuite) TestListOrdersMarines() { func (suite *OrderServiceSuite) TestListOrdersWithEmptyFields() { expectedOrder := factory.BuildOrder(suite.DB(), nil, nil) - + waf := entitlements.NewWeightAllotmentFetcher() expectedOrder.Entitlement = nil expectedOrder.EntitlementID = nil expectedOrder.Grade = nil @@ -1786,7 +1795,7 @@ func (suite *OrderServiceSuite) TestListOrdersWithEmptyFields() { AccessToken: "fakeAccessToken", } - orderFetcher := NewOrderFetcher() + orderFetcher := NewOrderFetcher(waf) moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{PerPage: models.Int64Pointer(1), Page: models.Int64Pointer(1)}) suite.FatalNoError(err) @@ -1796,6 +1805,7 @@ func (suite *OrderServiceSuite) TestListOrdersWithEmptyFields() { func (suite *OrderServiceSuite) TestListOrdersWithPagination() { officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + waf := entitlements.NewWeightAllotmentFetcher() session := auth.Session{ ApplicationName: auth.OfficeApp, Roles: officeUser.User.Roles, @@ -1808,7 +1818,7 @@ func (suite *OrderServiceSuite) TestListOrdersWithPagination() { factory.BuildMoveWithShipment(suite.DB(), nil, nil) } - orderFetcher := NewOrderFetcher() + orderFetcher := NewOrderFetcher(waf) params := services.ListOrderParams{Page: models.Int64Pointer(1), PerPage: models.Int64Pointer(1)} moves, count, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) @@ -1827,7 +1837,7 @@ func (suite *OrderServiceSuite) TestListOrdersWithSortOrder() { affiliation := models.AffiliationNAVY edipi := "9999999999" var officeUser models.OfficeUser - + waf := entitlements.NewWeightAllotmentFetcher() // SET UP: Dates for sorting by Requested Move Date // - We want dates 2 and 3 to sandwich requestedMoveDate1 so we can test that the min() query is working requestedMoveDate1 := time.Date(testdatagen.GHCTestYear, 02, 20, 0, 0, 0, 0, time.UTC) @@ -1889,7 +1899,7 @@ func (suite *OrderServiceSuite) TestListOrdersWithSortOrder() { return expectedMove1, expectedMove2, session } - orderFetcher := NewOrderFetcher() + orderFetcher := NewOrderFetcher(waf) suite.Run("Sort by locator code", func() { expectedMove1, expectedMove2, session := setupTestData() @@ -2052,6 +2062,7 @@ func getPPMShipmentWithCloseoutOfficeNeedsCloseout(suite *OrderServiceSuite, clo func (suite *OrderServiceSuite) TestListOrdersNeedingServicesCounselingWithPPMCloseoutColumnsSort() { defaultShipmentPickupPostalCode := "90210" + waf := entitlements.NewWeightAllotmentFetcher() setupTestData := func() models.OfficeUser { // Make an office user → GBLOC X officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) @@ -2064,7 +2075,7 @@ func (suite *OrderServiceSuite) TestListOrdersNeedingServicesCounselingWithPPMCl return officeUser } - orderFetcher := NewOrderFetcher() + orderFetcher := NewOrderFetcher(waf) var session auth.Session @@ -2328,7 +2339,7 @@ func (suite *OrderServiceSuite) TestListOrdersNeedingServicesCounselingWithPPMCl } func (suite *OrderServiceSuite) TestListOrdersNeedingServicesCounselingWithGBLOCSortFilter() { - + waf := entitlements.NewWeightAllotmentFetcher() suite.Run("Filter by origin GBLOC", func() { // TESTCASE SCENARIO @@ -2398,7 +2409,7 @@ func (suite *OrderServiceSuite) TestListOrdersNeedingServicesCounselingWithGBLOC }, }, nil) // Setup and run the function under test requesting status NEEDS SERVICE COUNSELING - orderFetcher := NewOrderFetcher() + orderFetcher := NewOrderFetcher(waf) statuses := []string{"NEEDS SERVICE COUNSELING"} // Sort by origin GBLOC, filter by status params := services.ListOrderParams{Sort: models.StringPointer("originGBLOC"), Order: models.StringPointer("asc"), Status: statuses} @@ -2420,6 +2431,7 @@ func (suite *OrderServiceSuite) TestListOrdersForTOOWithNTSRelease() { }, }, }, nil) + waf := entitlements.NewWeightAllotmentFetcher() // Make a TOO user and the postal code to GBLOC link. tooOfficeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) session := auth.Session{ @@ -2430,7 +2442,7 @@ func (suite *OrderServiceSuite) TestListOrdersForTOOWithNTSRelease() { AccessToken: "fakeAccessToken", } - orderFetcher := NewOrderFetcher() + orderFetcher := NewOrderFetcher(waf) moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), tooOfficeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) suite.FatalNoError(err) @@ -2441,7 +2453,7 @@ func (suite *OrderServiceSuite) TestListOrdersForTOOWithNTSRelease() { func (suite *OrderServiceSuite) TestListOrdersForTOOWithPPM() { postalCode := "50309" partialPPMType := models.MovePPMTypePARTIAL - + waf := entitlements.NewWeightAllotmentFetcher() ppmShipment := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ { Model: models.Order{ @@ -2474,7 +2486,7 @@ func (suite *OrderServiceSuite) TestListOrdersForTOOWithPPM() { // GBLOC for the below doesn't really matter, it just means the query for the moves passes the inner join in ListOrders factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), ppmShipment.PickupAddress.PostalCode, tooOfficeUser.TransportationOffice.Gbloc) - orderFetcher := NewOrderFetcher() + orderFetcher := NewOrderFetcher(waf) moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), tooOfficeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) suite.FatalNoError(err) suite.Equal(1, moveCount) @@ -2484,7 +2496,7 @@ func (suite *OrderServiceSuite) TestListOrdersForTOOWithPPM() { func (suite *OrderServiceSuite) TestListOrdersWithViewAsGBLOCParam() { var hqOfficeUser models.OfficeUser var hqOfficeUserAGFM models.OfficeUser - + waf := entitlements.NewWeightAllotmentFetcher() requestedMoveDate1 := time.Date(testdatagen.GHCTestYear, 02, 20, 0, 0, 0, 0, time.UTC) requestedMoveDate2 := time.Date(testdatagen.GHCTestYear, 03, 03, 0, 0, 0, 0, time.UTC) @@ -2566,7 +2578,7 @@ func (suite *OrderServiceSuite) TestListOrdersWithViewAsGBLOCParam() { return expectedMove1, expectedMove2, expectedShipment3, hqSession, hqSessionAGFM } - orderFetcher := NewOrderFetcher() + orderFetcher := NewOrderFetcher(waf) suite.Run("Sort by locator code", func() { expectedMove1, expectedMove2, expectedShipment3, hqSession, hqSessionAGFM := setupTestData() @@ -2606,6 +2618,7 @@ func (suite *OrderServiceSuite) TestListOrdersWithViewAsGBLOCParam() { func (suite *OrderServiceSuite) TestListOrdersForTOOWithPPMWithDeletedShipment() { postalCode := "50309" deletedAt := time.Now() + waf := entitlements.NewWeightAllotmentFetcher() move := factory.BuildMove(suite.DB(), []factory.Customization{ { Model: models.Move{ @@ -2648,7 +2661,7 @@ func (suite *OrderServiceSuite) TestListOrdersForTOOWithPPMWithDeletedShipment() AccessToken: "fakeAccessToken", } - orderFetcher := NewOrderFetcher() + orderFetcher := NewOrderFetcher(waf) moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), tooOfficeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{Status: []string{string(models.MoveStatusSUBMITTED)}}) suite.FatalNoError(err) suite.Equal(0, moveCount) @@ -2658,6 +2671,7 @@ func (suite *OrderServiceSuite) TestListOrdersForTOOWithPPMWithDeletedShipment() func (suite *OrderServiceSuite) TestListOrdersForTOOWithPPMWithOneDeletedShipmentButOtherExists() { postalCode := "50309" deletedAt := time.Now() + waf := entitlements.NewWeightAllotmentFetcher() move := factory.BuildMove(suite.DB(), []factory.Customization{ { Model: models.Move{ @@ -2728,7 +2742,7 @@ func (suite *OrderServiceSuite) TestListOrdersForTOOWithPPMWithOneDeletedShipmen AccessToken: "fakeAccessToken", } - orderFetcher := NewOrderFetcher() + orderFetcher := NewOrderFetcher(waf) moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), tooOfficeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) suite.FatalNoError(err) suite.Equal(1, moveCount) @@ -2736,8 +2750,9 @@ func (suite *OrderServiceSuite) TestListOrdersForTOOWithPPMWithOneDeletedShipmen } func (suite *OrderServiceSuite) TestListAllOrderLocations() { + waf := entitlements.NewWeightAllotmentFetcher() suite.Run("returns a list of all order locations in the current users queue", func() { - orderFetcher := NewOrderFetcher() + orderFetcher := NewOrderFetcher(waf) officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeServicesCounselor}) session := auth.Session{ ApplicationName: auth.OfficeApp, @@ -2756,8 +2771,9 @@ func (suite *OrderServiceSuite) TestListAllOrderLocations() { } func (suite *OrderServiceSuite) TestListAllOrderLocationsWithViewAsGBLOCParam() { + waf := entitlements.NewWeightAllotmentFetcher() suite.Run("returns a list of all order locations in the current users queue", func() { - orderFetcher := NewOrderFetcher() + orderFetcher := NewOrderFetcher(waf) officeUserFetcher := officeuserservice.NewOfficeUserFetcherPop() movesContainOriginDutyLocation := func(moves models.Moves, keyword string) func() (success bool) { return func() (success bool) { @@ -2855,26 +2871,26 @@ func (suite *OrderServiceSuite) TestListAllOrderLocationsWithViewAsGBLOCParam() func (suite *OrderServiceSuite) TestOriginDutyLocationFilter() { var session auth.Session + waf := entitlements.NewWeightAllotmentFetcher() var expectedMove models.Move var officeUser models.OfficeUser - orderFetcher := NewOrderFetcher() - suite.PreloadData(func() { - setupTestData := func() (models.OfficeUser, models.Move, auth.Session) { - officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) - session := auth.Session{ - ApplicationName: auth.OfficeApp, - Roles: officeUser.User.Roles, - OfficeUserID: officeUser.ID, - IDToken: "fake_token", - AccessToken: "fakeAccessToken", - } - move := factory.BuildMoveWithShipment(suite.DB(), nil, nil) - return officeUser, move, session + orderFetcher := NewOrderFetcher(waf) + setupTestData := func() (models.OfficeUser, models.Move, auth.Session) { + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", } - officeUser, expectedMove, session = setupTestData() - }) - locationName := expectedMove.Orders.OriginDutyLocation.Name + move := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + return officeUser, move, session + } + suite.Run("Returns orders matching full originDutyLocation name filter", func() { + officeUser, expectedMove, session = setupTestData() + locationName := expectedMove.Orders.OriginDutyLocation.Name expectedMoves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{OriginDutyLocation: strings.Split(locationName, " ")}) suite.NoError(err) suite.Equal(1, len(expectedMoves)) @@ -2882,6 +2898,8 @@ func (suite *OrderServiceSuite) TestOriginDutyLocationFilter() { }) suite.Run("Returns orders matching partial originDutyLocation name filter", func() { + officeUser, expectedMove, session = setupTestData() + locationName := expectedMove.Orders.OriginDutyLocation.Name //Split the location name and retrieve a substring (first string) for the search param partialParamSearch := strings.Split(locationName, " ")[0] expectedMoves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{OriginDutyLocation: strings.Split(partialParamSearch, " ")}) @@ -2892,6 +2910,8 @@ func (suite *OrderServiceSuite) TestOriginDutyLocationFilter() { } func (suite *OrderServiceSuite) TestListOrdersFilteredByCustomerName() { + waf := entitlements.NewWeightAllotmentFetcher() + serviceMemberFirstName := "Margaret" serviceMemberLastName := "Starlight" edipi := "9999999998" @@ -2950,7 +2970,7 @@ func (suite *OrderServiceSuite) TestListOrdersFilteredByCustomerName() { } }) - orderFetcher := NewOrderFetcher() + orderFetcher := NewOrderFetcher(waf) suite.Run("list moves by customer name - full name (last, first)", func() { // Search "Spacemen, Margaret" diff --git a/pkg/services/order/order_updater.go b/pkg/services/order/order_updater.go index 02b8c848ca5..12821783d7a 100644 --- a/pkg/services/order/order_updater.go +++ b/pkg/services/order/order_updater.go @@ -17,6 +17,7 @@ import ( "github.com/transcom/mymove/pkg/gen/internalmessages" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/services/entitlements" "github.com/transcom/mymove/pkg/services/query" "github.com/transcom/mymove/pkg/storage" "github.com/transcom/mymove/pkg/uploader" @@ -48,7 +49,10 @@ func (f *orderUpdater) UpdateOrderAsTOO(appCtx appcontext.AppContext, orderID uu return &models.Order{}, uuid.Nil, apperror.NewInvalidInputError(orderID, nil, nil, "SAC cannot be more than 80 characters") } - orderToUpdate := orderFromTOOPayload(appCtx, *order, payload) + orderToUpdate, err := orderFromTOOPayload(appCtx, *order, payload) + if err != nil { + return nil, uuid.Nil, err + } return f.updateOrderAsTOO(appCtx, orderToUpdate, CheckRequiredFields()) } @@ -70,7 +74,10 @@ func (f *orderUpdater) UpdateOrderAsCounselor(appCtx appcontext.AppContext, orde return &models.Order{}, uuid.Nil, apperror.NewInvalidInputError(orderID, nil, nil, "SAC cannot be more than 80 characters") } - orderToUpdate := orderFromCounselingPayload(*order, payload) + orderToUpdate, err := orderFromCounselingPayload(appCtx, *order, payload) + if err != nil { + return &models.Order{}, uuid.Nil, nil + } return f.updateOrder(appCtx, orderToUpdate, CheckRequiredFields()) } @@ -87,7 +94,10 @@ func (f *orderUpdater) UpdateAllowanceAsTOO(appCtx appcontext.AppContext, orderI return &models.Order{}, uuid.Nil, apperror.NewPreconditionFailedError(orderID, query.StaleIdentifierError{StaleIdentifier: eTag}) } - orderToUpdate := allowanceFromTOOPayload(appCtx, *order, payload) + orderToUpdate, err := allowanceFromTOOPayload(appCtx, *order, payload) + if err != nil { + return &models.Order{}, uuid.Nil, err + } return f.updateOrder(appCtx, orderToUpdate) } @@ -104,7 +114,10 @@ func (f *orderUpdater) UpdateAllowanceAsCounselor(appCtx appcontext.AppContext, return &models.Order{}, uuid.Nil, apperror.NewPreconditionFailedError(orderID, query.StaleIdentifierError{StaleIdentifier: eTag}) } - orderToUpdate := allowanceFromCounselingPayload(appCtx, *order, payload) + orderToUpdate, err := allowanceFromCounselingPayload(appCtx, *order, payload) + if err != nil { + return &models.Order{}, uuid.Nil, err + } return f.updateOrder(appCtx, orderToUpdate) } @@ -177,8 +190,9 @@ func (f *orderUpdater) findOrderWithAmendedOrders(appCtx appcontext.AppContext, return &order, nil } -func orderFromTOOPayload(appCtx appcontext.AppContext, existingOrder models.Order, payload ghcmessages.UpdateOrderPayload) models.Order { +func orderFromTOOPayload(appCtx appcontext.AppContext, existingOrder models.Order, payload ghcmessages.UpdateOrderPayload) (models.Order, error) { order := existingOrder + waf := entitlements.NewWeightAllotmentFetcher() // update order origin duty location if payload.OriginDutyLocationID != nil { @@ -257,7 +271,10 @@ func orderFromTOOPayload(appCtx appcontext.AppContext, existingOrder models.Orde if payload.Grade != nil { order.Grade = (*internalmessages.OrderPayGrade)(payload.Grade) // Calculate new DBWeightAuthorized based on the new grade - weightAllotment := models.GetWeightAllotment(*order.Grade, order.OrdersType) + weightAllotment, err := waf.GetWeightAllotment(appCtx, string(*order.Grade), order.OrdersType) + if err != nil { + return models.Order{}, err + } weight := weightAllotment.TotalWeightSelf // Payload does not have this information, retrieve dependents from the existing order if existingOrder.HasDependents && *order.Entitlement.DependentsAuthorized { @@ -267,7 +284,7 @@ func orderFromTOOPayload(appCtx appcontext.AppContext, existingOrder models.Orde order.Entitlement.DBAuthorizedWeight = &weight } - return order + return order, nil } func (f *orderUpdater) amendedOrder(appCtx appcontext.AppContext, userID uuid.UUID, order models.Order, file io.ReadCloser, filename string, storer storage.FileStorer, uploadType models.UploadType) (models.UserUpload, string, *validate.Errors, error) { @@ -317,8 +334,9 @@ func (f *orderUpdater) amendedOrder(appCtx appcontext.AppContext, userID uuid.UU return *userUpload, url, nil, nil } -func orderFromCounselingPayload(existingOrder models.Order, payload ghcmessages.CounselingUpdateOrderPayload) models.Order { +func orderFromCounselingPayload(appCtx appcontext.AppContext, existingOrder models.Order, payload ghcmessages.CounselingUpdateOrderPayload) (models.Order, error) { order := existingOrder + waf := entitlements.NewWeightAllotmentFetcher() // update order origin duty location if payload.OriginDutyLocationID != nil { @@ -390,7 +408,10 @@ func orderFromCounselingPayload(existingOrder models.Order, payload ghcmessages. if payload.Grade != nil { order.Grade = (*internalmessages.OrderPayGrade)(payload.Grade) // Calculate new DBWeightAuthorized based on the new grade - weightAllotment := models.GetWeightAllotment(*order.Grade, order.OrdersType) + weightAllotment, err := waf.GetWeightAllotment(appCtx, string(*order.Grade), order.OrdersType) + if err != nil { + return models.Order{}, err + } weight := weightAllotment.TotalWeightSelf // Payload does not have this information, retrieve dependents from the existing order if existingOrder.HasDependents && *order.Entitlement.DependentsAuthorized { @@ -404,11 +425,12 @@ func orderFromCounselingPayload(existingOrder models.Order, payload ghcmessages. order.HasDependents = *payload.HasDependents } - return order + return order, nil } -func allowanceFromTOOPayload(appCtx appcontext.AppContext, existingOrder models.Order, payload ghcmessages.UpdateAllowancePayload) models.Order { +func allowanceFromTOOPayload(appCtx appcontext.AppContext, existingOrder models.Order, payload ghcmessages.UpdateAllowancePayload) (models.Order, error) { order := existingOrder + waf := entitlements.NewWeightAllotmentFetcher() if payload.ProGearWeight != nil { order.Entitlement.ProGearWeight = int(*payload.ProGearWeight) @@ -434,7 +456,10 @@ func allowanceFromTOOPayload(appCtx appcontext.AppContext, existingOrder models. } // Calculate new DBWeightAuthorized based on the new grade - weightAllotment := models.GetWeightAllotment(*order.Grade, order.OrdersType) + weightAllotment, err := waf.GetWeightAllotment(appCtx, string(*order.Grade), order.OrdersType) + if err != nil { + return models.Order{}, err + } weight := weightAllotment.TotalWeightSelf // Payload does not have this information, retrieve dependents from the existing order if existingOrder.HasDependents && *payload.DependentsAuthorized { @@ -500,15 +525,16 @@ func allowanceFromTOOPayload(appCtx appcontext.AppContext, existingOrder models. if order.Entitlement != nil { unaccompaniedBaggageAllowance, err := models.GetUBWeightAllowance(appCtx, order.OriginDutyLocation.Address.IsOconus, order.NewDutyLocation.Address.IsOconus, order.ServiceMember.Affiliation, order.Grade, &order.OrdersType, &hasDepedents, order.Entitlement.AccompaniedTour, order.Entitlement.DependentsUnderTwelve, order.Entitlement.DependentsTwelveAndOver) if err == nil { - weightAllotment.UnaccompaniedBaggageAllowance = unaccompaniedBaggageAllowance + order.Entitlement.UBAllowance = &unaccompaniedBaggageAllowance } } - return order + return order, nil } -func allowanceFromCounselingPayload(appCtx appcontext.AppContext, existingOrder models.Order, payload ghcmessages.CounselingUpdateAllowancePayload) models.Order { +func allowanceFromCounselingPayload(appCtx appcontext.AppContext, existingOrder models.Order, payload ghcmessages.CounselingUpdateAllowancePayload) (models.Order, error) { order := existingOrder + waf := entitlements.NewWeightAllotmentFetcher() if payload.ProGearWeight != nil { order.Entitlement.ProGearWeight = int(*payload.ProGearWeight) @@ -534,7 +560,10 @@ func allowanceFromCounselingPayload(appCtx appcontext.AppContext, existingOrder } // Calculate new DBWeightAuthorized based on the new grade - weightAllotment := models.GetWeightAllotment(*order.Grade, order.OrdersType) + weightAllotment, err := waf.GetWeightAllotment(appCtx, string(*order.Grade), order.OrdersType) + if err != nil { + return models.Order{}, err + } weight := weightAllotment.TotalWeightSelf // Payload does not have this information, retrieve dependents from the existing order if existingOrder.HasDependents && *payload.DependentsAuthorized { @@ -584,21 +613,23 @@ func allowanceFromCounselingPayload(appCtx appcontext.AppContext, existingOrder if order.NewDutyLocationID != uuid.Nil { var newDutyLocation models.DutyLocation newDutyLocation, err := models.FetchDutyLocation(appCtx.DB(), order.NewDutyLocationID) - if err == nil { - order.NewDutyLocationID = newDutyLocation.ID - order.NewDutyLocation = newDutyLocation + if err != nil { + return models.Order{}, err } + order.NewDutyLocationID = newDutyLocation.ID + order.NewDutyLocation = newDutyLocation } // Recalculate UB allowance of order entitlement if order.Entitlement != nil { unaccompaniedBaggageAllowance, err := models.GetUBWeightAllowance(appCtx, order.OriginDutyLocation.Address.IsOconus, order.NewDutyLocation.Address.IsOconus, order.ServiceMember.Affiliation, order.Grade, &order.OrdersType, payload.DependentsAuthorized, order.Entitlement.AccompaniedTour, order.Entitlement.DependentsUnderTwelve, order.Entitlement.DependentsTwelveAndOver) - if err == nil { - weightAllotment.UnaccompaniedBaggageAllowance = unaccompaniedBaggageAllowance + if err != nil { + return models.Order{}, err } + order.Entitlement.UBAllowance = &unaccompaniedBaggageAllowance } - return order + return order, nil } func (f *orderUpdater) saveDocumentForAmendedOrder(appCtx appcontext.AppContext, doc *models.Document) (*models.Document, error) { diff --git a/pkg/services/pptas_report/pptas_report_list_fetcher.go b/pkg/services/pptas_report/pptas_report_list_fetcher.go index b0ce2d24015..95e0f5bb9d0 100644 --- a/pkg/services/pptas_report/pptas_report_list_fetcher.go +++ b/pkg/services/pptas_report/pptas_report_list_fetcher.go @@ -19,14 +19,16 @@ type pptasReportListFetcher struct { moveFetcher services.MoveFetcher tacFetcher services.TransportationAccountingCodeFetcher loaFetcher services.LineOfAccountingFetcher + waf services.WeightAllotmentFetcher } -func NewPPTASReportListFetcher(estimator services.PPMEstimator, moveFetcher services.MoveFetcher, tacFetcher services.TransportationAccountingCodeFetcher, loaFetcher services.LineOfAccountingFetcher) services.PPTASReportListFetcher { +func NewPPTASReportListFetcher(estimator services.PPMEstimator, moveFetcher services.MoveFetcher, tacFetcher services.TransportationAccountingCodeFetcher, loaFetcher services.LineOfAccountingFetcher, weightAllotmentFetcher services.WeightAllotmentFetcher) services.PPTASReportListFetcher { return &pptasReportListFetcher{ estimator: estimator, moveFetcher: moveFetcher, tacFetcher: tacFetcher, loaFetcher: loaFetcher, + waf: weightAllotmentFetcher, } } @@ -91,7 +93,11 @@ func (f *pptasReportListFetcher) BuildPPTASReportsFromMoves(appCtx appcontext.Ap report.Address = orders.ServiceMember.ResidentialAddress if orders.Grade != nil && orders.Entitlement != nil { - orders.Entitlement.SetWeightAllotment(string(*orders.Grade), orders.OrdersType) + entitlement, err := f.waf.GetWeightAllotment(appCtx, string(*orders.Grade), orders.OrdersType) + if err != nil { + return nil, err + } + orders.Entitlement.WeightAllotted = &entitlement } weightAllotment := orders.Entitlement.WeightAllotment() diff --git a/pkg/services/pptas_report/pptas_report_list_fetcher_test.go b/pkg/services/pptas_report/pptas_report_list_fetcher_test.go index 49748c7bc0d..e5d441ea483 100644 --- a/pkg/services/pptas_report/pptas_report_list_fetcher_test.go +++ b/pkg/services/pptas_report/pptas_report_list_fetcher_test.go @@ -8,6 +8,7 @@ import ( "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/gen/internalmessages" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services/entitlements" mocks "github.com/transcom/mymove/pkg/services/mocks" ) @@ -16,8 +17,9 @@ func (suite *ReportServiceSuite) TestReportFetcher() { moveFetcher := mocks.MoveFetcher{} tacFetcher := mocks.TransportationAccountingCodeFetcher{} loaFetcher := mocks.LineOfAccountingFetcher{} + waf := entitlements.NewWeightAllotmentFetcher() - reportListFetcher := NewPPTASReportListFetcher(&ppmEstimator, &moveFetcher, &tacFetcher, &loaFetcher) + reportListFetcher := NewPPTASReportListFetcher(&ppmEstimator, &moveFetcher, &tacFetcher, &loaFetcher, waf) // defaultSearchParams := services.MoveTaskOrderFetcherParams{} appCtx := suite.AppContextForTest() diff --git a/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet.go b/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet.go index a0d4ab3a421..9c132a0e830 100644 --- a/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet.go +++ b/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet.go @@ -24,6 +24,7 @@ import ( "github.com/transcom/mymove/pkg/paperwork" "github.com/transcom/mymove/pkg/route" "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/services/entitlements" "github.com/transcom/mymove/pkg/unit" ) @@ -194,17 +195,22 @@ func (wa *SSWMaxWeightEntitlement) addLineItem(field string, value int) { // SSWGetEntitlement calculates the entitlement for the shipment summary worksheet based on the parameters of // a move (hasDependents, spouseHasProGear) -func SSWGetEntitlement(grade internalmessages.OrderPayGrade, hasDependents bool, spouseHasProGear bool, ordersType internalmessages.OrdersType) models.SSWMaxWeightEntitlement { +func SSWGetEntitlement(appCtx appcontext.AppContext, grade internalmessages.OrderPayGrade, hasDependents bool, spouseHasProGear bool, ordersType internalmessages.OrdersType) (models.SSWMaxWeightEntitlement, error) { sswEntitlements := SSWMaxWeightEntitlement{} - entitlements := models.GetWeightAllotment(grade, ordersType) + waf := entitlements.NewWeightAllotmentFetcher() + entitlements, err := waf.GetWeightAllotment(appCtx, string(grade), ordersType) + if err != nil { + return models.SSWMaxWeightEntitlement{}, nil + } + //entitlements := models.GetWeightAllotment(grade, ordersType) sswEntitlements.addLineItem("ProGear", entitlements.ProGearWeight) sswEntitlements.addLineItem("SpouseProGear", entitlements.ProGearWeightSpouse) if !hasDependents { sswEntitlements.addLineItem("Entitlement", entitlements.TotalWeightSelf) - return models.SSWMaxWeightEntitlement(sswEntitlements) + return models.SSWMaxWeightEntitlement(sswEntitlements), nil } sswEntitlements.addLineItem("Entitlement", entitlements.TotalWeightSelfPlusDependents) - return models.SSWMaxWeightEntitlement(sswEntitlements) + return models.SSWMaxWeightEntitlement(sswEntitlements), nil } // Calculates cost for the Remaining PPM Incentive (pre-tax) field on page 2 of SSW form. @@ -1074,7 +1080,10 @@ func (SSWPPMComputer *SSWPPMComputer) FetchDataShipmentSummaryWorksheetFormData( return nil, errors.New("order for requested shipment summary worksheet data does not have a pay grade attached") } - weightAllotment := SSWGetEntitlement(*ppmShipment.Shipment.MoveTaskOrder.Orders.Grade, ppmShipment.Shipment.MoveTaskOrder.Orders.HasDependents, ppmShipment.Shipment.MoveTaskOrder.Orders.SpouseHasProGear, ppmShipment.Shipment.MoveTaskOrder.Orders.OrdersType) + weightAllotment, err := SSWGetEntitlement(appCtx, *ppmShipment.Shipment.MoveTaskOrder.Orders.Grade, ppmShipment.Shipment.MoveTaskOrder.Orders.HasDependents, ppmShipment.Shipment.MoveTaskOrder.Orders.SpouseHasProGear, ppmShipment.Shipment.MoveTaskOrder.Orders.OrdersType) + if err != nil { + return nil, err + } maxSit, err := CalculateShipmentSITAllowance(appCtx, ppmShipment.Shipment) if err != nil { diff --git a/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet_test.go b/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet_test.go index 8f89327f6f2..e5ff72a4aa7 100644 --- a/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet_test.go +++ b/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet_test.go @@ -20,6 +20,7 @@ import ( "github.com/transcom/mymove/pkg/gen/internalmessages" "github.com/transcom/mymove/pkg/models" paperworkgenerator "github.com/transcom/mymove/pkg/paperwork" + "github.com/transcom/mymove/pkg/services/entitlements" "github.com/transcom/mymove/pkg/services/mocks" storageTest "github.com/transcom/mymove/pkg/storage/test" "github.com/transcom/mymove/pkg/unit" @@ -27,9 +28,9 @@ import ( ) func (suite *ShipmentSummaryWorksheetServiceSuite) TestFetchDataShipmentSummaryWorksheet() { - //advanceID, _ := uuid.NewV4() ordersType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION yuma := factory.FetchOrBuildCurrentDutyLocation(suite.DB()) + waf := entitlements.NewWeightAllotmentFetcher() fortGordon := factory.FetchOrBuildOrdersDutyLocation(suite.DB()) grade := models.ServiceMemberGradeE9 mockPPMCloseoutFetcher := &mocks.PPMCloseoutFetcher{} @@ -91,12 +92,14 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestFetchDataShipmentSummaryW suite.Equal(yuma.Address.ID, ssd.CurrentDutyLocation.Address.ID) suite.Equal(fortGordon.ID, ssd.NewDutyLocation.ID) suite.Equal(fortGordon.Address.ID, ssd.NewDutyLocation.Address.ID) - gradeWtgAllotment := models.GetWeightAllotment(grade, ordersType) + gradeWtgAllotment, err := waf.GetWeightAllotment(suite.AppContextForTest(), string(grade), ordersType) + suite.NoError(err) suite.Equal(unit.Pound(gradeWtgAllotment.TotalWeightSelf), ssd.WeightAllotment.Entitlement) suite.Equal(unit.Pound(gradeWtgAllotment.ProGearWeight), ssd.WeightAllotment.ProGear) suite.Equal(unit.Pound(500), ssd.WeightAllotment.SpouseProGear) suite.Require().NotNil(ssd.Order.Grade) - weightAllotment := models.GetWeightAllotment(*ssd.Order.Grade, ssd.Order.OrdersType) + weightAllotment, err := waf.GetWeightAllotment(suite.AppContextForTest(), string(*ssd.Order.Grade), ssd.Order.OrdersType) + suite.NoError(err) // E_9 rank, no dependents, with spouse pro-gear totalWeight := weightAllotment.TotalWeightSelf + weightAllotment.ProGearWeight + weightAllotment.ProGearWeightSpouse suite.Require().Nil(err) @@ -219,6 +222,7 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestFetchDataShipmentSummaryW yuma := factory.FetchOrBuildCurrentDutyLocation(suite.DB()) fortGordon := factory.FetchOrBuildOrdersDutyLocation(suite.DB()) grade := models.ServiceMemberGradeE9 + waf := entitlements.NewWeightAllotmentFetcher() mockPPMCloseoutFetcher := &mocks.PPMCloseoutFetcher{} SSWPPMComputer := NewSSWPPMComputer(mockPPMCloseoutFetcher) @@ -263,12 +267,14 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestFetchDataShipmentSummaryW suite.Equal(yuma.Address.ID, ssd.CurrentDutyLocation.Address.ID) suite.Equal(fortGordon.ID, ssd.NewDutyLocation.ID) suite.Equal(fortGordon.Address.ID, ssd.NewDutyLocation.Address.ID) - gradeWtgAllotment := models.GetWeightAllotment(grade, ordersType) + gradeWtgAllotment, err := waf.GetWeightAllotment(suite.AppContextForTest(), string(grade), ordersType) + suite.NoError(err) suite.Equal(unit.Pound(gradeWtgAllotment.TotalWeightSelf), ssd.WeightAllotment.Entitlement) suite.Equal(unit.Pound(gradeWtgAllotment.ProGearWeight), ssd.WeightAllotment.ProGear) suite.Equal(unit.Pound(500), ssd.WeightAllotment.SpouseProGear) suite.Require().NotNil(ssd.Order.Grade) - weightAllotment := models.GetWeightAllotment(*ssd.Order.Grade, ssd.Order.OrdersType) + weightAllotment, err := waf.GetWeightAllotment(suite.AppContextForTest(), string(*ssd.Order.Grade), ssd.Order.OrdersType) + suite.NoError(err) // E_9 rank, no dependents, with spouse pro-gear totalWeight := weightAllotment.TotalWeightSelf + weightAllotment.ProGearWeight + weightAllotment.ProGearWeightSpouse suite.Equal(unit.Pound(totalWeight), ssd.WeightAllotment.TotalWeight) @@ -885,10 +891,12 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatSSWGetEntitlement() spouseHasProGear := true hasDependants := true ordersType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION - allotment := models.GetWeightAllotment(models.ServiceMemberGradeE1, ordersType) + waf := entitlements.NewWeightAllotmentFetcher() + allotment, err := waf.GetWeightAllotment(suite.AppContextForTest(), string(models.ServiceMemberGradeE1), ordersType) + suite.NoError(err) expectedTotalWeight := allotment.TotalWeightSelfPlusDependents + allotment.ProGearWeight + allotment.ProGearWeightSpouse - sswEntitlement := SSWGetEntitlement(models.ServiceMemberGradeE1, hasDependants, spouseHasProGear, ordersType) - + sswEntitlement, err := SSWGetEntitlement(suite.AppContextForTest(), models.ServiceMemberGradeE1, hasDependants, spouseHasProGear, ordersType) + suite.NoError(err) suite.Equal(unit.Pound(expectedTotalWeight), sswEntitlement.TotalWeight) suite.Equal(unit.Pound(allotment.TotalWeightSelfPlusDependents), sswEntitlement.Entitlement) suite.Equal(unit.Pound(allotment.ProGearWeightSpouse), sswEntitlement.SpouseProGear) @@ -898,10 +906,13 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatSSWGetEntitlement() func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatSSWGetEntitlementNoDependants() { spouseHasProGear := false hasDependants := false + waf := entitlements.NewWeightAllotmentFetcher() ordersType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION - allotment := models.GetWeightAllotment(models.ServiceMemberGradeE1, ordersType) + allotment, err := waf.GetWeightAllotment(suite.AppContextForTest(), string(models.ServiceMemberGradeE1), ordersType) + suite.NoError(err) expectedTotalWeight := allotment.TotalWeightSelf + allotment.ProGearWeight + allotment.ProGearWeightSpouse - sswEntitlement := SSWGetEntitlement(models.ServiceMemberGradeE1, hasDependants, spouseHasProGear, ordersType) + sswEntitlement, err := SSWGetEntitlement(suite.AppContextForTest(), models.ServiceMemberGradeE1, hasDependants, spouseHasProGear, ordersType) + suite.NoError(err) suite.Equal(unit.Pound(expectedTotalWeight), sswEntitlement.TotalWeight) suite.Equal(unit.Pound(allotment.TotalWeightSelf), sswEntitlement.Entitlement) diff --git a/pkg/services/sit_extension/sit_extension_creator_test.go b/pkg/services/sit_extension/sit_extension_creator_test.go index 9a3a15afc98..04c53f04d87 100644 --- a/pkg/services/sit_extension/sit_extension_creator_test.go +++ b/pkg/services/sit_extension/sit_extension_creator_test.go @@ -7,6 +7,7 @@ import ( "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/services/entitlements" moverouter "github.com/transcom/mymove/pkg/services/move" movefetcher "github.com/transcom/mymove/pkg/services/move_task_order" ) @@ -16,7 +17,8 @@ func (suite *SitExtensionServiceSuite) TestSITExtensionCreator() { // Create move router for SitExtension Createor moveRouter := moverouter.NewMoveRouter() sitExtensionCreator := NewSitExtensionCreator(moveRouter) - movefetcher := movefetcher.NewMoveTaskOrderFetcher() + waf := entitlements.NewWeightAllotmentFetcher() + movefetcher := movefetcher.NewMoveTaskOrderFetcher(waf) suite.Run("Success - CreateSITExtension with no status passed in", func() { // Under test: CreateSITExtension diff --git a/pkg/testdatagen/make_entitlement.go b/pkg/testdatagen/make_entitlement.go index e71bf02cec1..442cf8f94b6 100644 --- a/pkg/testdatagen/make_entitlement.go +++ b/pkg/testdatagen/make_entitlement.go @@ -3,6 +3,7 @@ package testdatagen import ( "github.com/gobuffalo/pop/v6" + "github.com/transcom/mymove/pkg/gen/internalmessages" "github.com/transcom/mymove/pkg/models" ) @@ -14,7 +15,6 @@ func makeEntitlement(db *pop.Connection, assertions Assertions) models.Entitleme rmeWeight := 1000 ocie := true grade := assertions.Order.Grade - ordersType := assertions.Order.OrdersType proGearWeight := 2000 proGearWeightSpouse := 500 @@ -33,9 +33,46 @@ func makeEntitlement(db *pop.Connection, assertions Assertions) models.Entitleme RequiredMedicalEquipmentWeight: rmeWeight, OrganizationalClothingAndIndividualEquipment: ocie, } - entitlement.SetWeightAllotment(string(*grade), ordersType) - dBAuthorizedWeight := entitlement.AuthorizedWeight() - entitlement.DBAuthorizedWeight = dBAuthorizedWeight + + var hhgAllowance models.HHGAllowance + if assertions.Order.OrdersType == internalmessages.OrdersTypeSTUDENTTRAVEL { + // Set to student travel allotment + entitlement.WeightAllotted = &models.WeightAllotment{ + TotalWeightSelf: 350, + TotalWeightSelfPlusDependents: 350, + ProGearWeight: 0, + ProGearWeightSpouse: 0, + UnaccompaniedBaggageAllowance: 100, + } + } + err := db.RawQuery(` + SELECT hhg_allowances.* + FROM hhg_allowances + INNER JOIN pay_grades ON hhg_allowances.pay_grade_id = pay_grades.id + WHERE pay_grades.grade = $1 + LIMIT 1 + `, grade).First(&hhgAllowance) + if err != nil { + // Resort to defaults, for some reason it must've been truncated + weightData := getDefaultWeightData(string(*grade)) + allotment := models.WeightAllotment{ + TotalWeightSelf: weightData.TotalWeightSelf, + TotalWeightSelfPlusDependents: weightData.TotalWeightSelfPlusDependents, + ProGearWeight: weightData.ProGearWeight, + ProGearWeightSpouse: weightData.ProGearWeightSpouse, + } + entitlement.WeightAllotted = &allotment + dBAuthorizedWeight := entitlement.AuthorizedWeight() + entitlement.DBAuthorizedWeight = dBAuthorizedWeight + } else { + // The db data was found + entitlement.WeightAllotted = &models.WeightAllotment{ + TotalWeightSelf: hhgAllowance.TotalWeightSelf, + TotalWeightSelfPlusDependents: hhgAllowance.TotalWeightSelfPlusDependents, + ProGearWeight: hhgAllowance.ProGearWeight, + ProGearWeightSpouse: hhgAllowance.ProGearWeightSpouse, + } + } // Overwrite values with those from assertions mergeModels(&entitlement, assertions.Entitlement) @@ -45,6 +82,59 @@ func makeEntitlement(db *pop.Connection, assertions Assertions) models.Entitleme return entitlement } +// Helper function to retrieve default weight data by grade +func getDefaultWeightData(grade string) struct { + TotalWeightSelf int + TotalWeightSelfPlusDependents int + ProGearWeight int + ProGearWeightSpouse int +} { + if data, ok := knownAllowances[grade]; ok { + return data + } + return knownAllowances["EMPTY"] // Default to EMPTY if grade not found. This is just dummy default data +} + +// Default allowances CAO December 2024 +// Note that the testdatagen package has its own default allowance +var knownAllowances = map[string]struct { + TotalWeightSelf int + TotalWeightSelfPlusDependents int + ProGearWeight int + ProGearWeightSpouse int +}{ + "EMPTY": {0, 0, 0, 0}, + "ACADEMY_CADET": {350, 350, 0, 0}, + "MIDSHIPMAN": {350, 350, 0, 0}, + "AVIATION_CADET": {7000, 8000, 2000, 500}, + "E-1": {5000, 8000, 2000, 500}, + "E-2": {5000, 8000, 2000, 500}, + "E-3": {5000, 8000, 2000, 500}, + "E-4": {7000, 8000, 2000, 500}, + "E-5": {7000, 9000, 2000, 500}, + "E-6": {8000, 11000, 2000, 500}, + "E-7": {11000, 13000, 2000, 500}, + "E-8": {12000, 14000, 2000, 500}, + "E-9": {13000, 15000, 2000, 500}, + "E-9SPECIALSENIORENLISTED": {14000, 17000, 2000, 500}, + "O-1ACADEMYGRADUATE": {10000, 12000, 2000, 500}, + "O-2": {12500, 13500, 2000, 500}, + "O-3": {13000, 14500, 2000, 500}, + "O-4": {14000, 17000, 2000, 500}, + "O-5": {16000, 17500, 2000, 500}, + "O-6": {18000, 18000, 2000, 500}, + "O-7": {18000, 18000, 2000, 500}, + "O-8": {18000, 18000, 2000, 500}, + "O-9": {18000, 18000, 2000, 500}, + "O-10": {18000, 18000, 2000, 500}, + "W-1": {10000, 12000, 2000, 500}, + "W-2": {12500, 13500, 2000, 500}, + "W-3": {13000, 14500, 2000, 500}, + "W-4": {14000, 17000, 2000, 500}, + "W-5": {16000, 17500, 2000, 500}, + "CIVILIAN_EMPLOYEE": {18000, 18000, 2000, 500}, +} + func setDependentsAuthorized(assertionDependentsAuthorized *bool) *bool { dependentsAuthorized := models.BoolPointer(true) if assertionDependentsAuthorized != nil { diff --git a/playwright/tests/my/milmove/ppms/entireShipmentCloseout.spec.js b/playwright/tests/my/milmove/ppms/entireShipmentCloseout.spec.js index 2ab93a21207..6d593a1bfaa 100644 --- a/playwright/tests/my/milmove/ppms/entireShipmentCloseout.spec.js +++ b/playwright/tests/my/milmove/ppms/entireShipmentCloseout.spec.js @@ -11,8 +11,11 @@ const multiMoveEnabled = process.env.FEATURE_FLAG_MULTI_MOVE; test.describe('Entire PPM closeout flow', () => { test.skip(multiMoveEnabled === 'true', 'Skip if MultiMove workflow is enabled.'); + forEachViewport(async () => { test(`flows through happy path for existing shipment`, async ({ customerPpmPage }) => { + test.slow(); + test.setTimeout(300000); // This one has been a headache forever. Shoehorn fix to go way above default "slow" timeout const move = await customerPpmPage.testHarness.buildApprovedMoveWithPPM(); await customerPpmPage.signInForPPMWithMove(move); @@ -32,6 +35,8 @@ test.describe('Entire PPM closeout flow', () => { }); test(`happy path with edits and backs`, async ({ customerPpmPage }) => { + test.slow(); + test.setTimeout(300000); // This one has been a headache forever. Shoehorn fix to go way above default "slow" timeout const move = await customerPpmPage.testHarness.buildMoveWithPPMShipmentReadyForFinalCloseout(); await customerPpmPage.signInForPPMWithMove(move); @@ -54,6 +59,7 @@ test.describe('Entire PPM closeout flow', () => { }); test(`delete complete and incomplete line items`, async ({ customerPpmPage }) => { + test.slow(); const move = await customerPpmPage.testHarness.buildMoveWithPPMShipmentReadyForFinalCloseout(); await customerPpmPage.signInForPPMWithMove(move); @@ -94,6 +100,7 @@ test.describe('Entire PPM closeout flow', () => { }); test(`deleting weight tickets updates final incentive`, async ({ customerPpmPage }) => { + test.slow(); const move = await customerPpmPage.testHarness.buildMoveWithPPMShipmentReadyForFinalCloseout(); await customerPpmPage.signInForPPMWithMove(move); @@ -129,6 +136,8 @@ test.describe('(MultiMove) Entire PPM closeout flow (MultiMove Workflow)', () => forEachViewport(async () => { test(`flows through happy path for existing shipment`, async ({ customerPpmPage }) => { + test.slow(); + test.setTimeout(300000); // This one has been a headache forever. Shoehorn fix to go way above default "slow" timeout const move = await customerPpmPage.testHarness.buildApprovedMoveWithPPM(); await customerPpmPage.signInForPPMWithMove(move); @@ -149,6 +158,8 @@ test.describe('(MultiMove) Entire PPM closeout flow (MultiMove Workflow)', () => }); test(`happy path with edits and backs`, async ({ customerPpmPage }) => { + test.slow(); + test.setTimeout(300000); // This one has been a headache forever. Shoehorn fix to go way above default "slow" timeout const move = await customerPpmPage.testHarness.buildMoveWithPPMShipmentReadyForFinalCloseout(); await customerPpmPage.signInForPPMWithMove(move); @@ -175,6 +186,7 @@ test.describe('(MultiMove) Entire PPM closeout flow (MultiMove Workflow)', () => }); test(`delete complete and incomplete line items`, async ({ customerPpmPage }) => { + test.slow(); const move = await customerPpmPage.testHarness.buildMoveWithPPMShipmentReadyForFinalCloseout(); await customerPpmPage.signInForPPMWithMove(move); @@ -222,6 +234,7 @@ test.describe('(MultiMove) Entire PPM closeout flow (MultiMove Workflow)', () => }); test(`deleting weight tickets updates final incentive`, async ({ customerPpmPage }) => { + test.slow(); const move = await customerPpmPage.testHarness.buildMoveWithPPMShipmentReadyForFinalCloseout(); await customerPpmPage.signInForPPMWithMove(move); diff --git a/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js b/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js index 16db36fbc3c..064c7e1d63d 100644 --- a/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js +++ b/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js @@ -47,6 +47,7 @@ test.describe('Services counselor user', () => { }); test('is able to click on move and submit after using the move code filter', async ({ page }) => { + test.slow(); /** * Move Details page */ @@ -65,6 +66,7 @@ test.describe('Services counselor user', () => { }); test('is able to flag a move for financial review', async ({ page, scPage }) => { + test.slow(); // click to trigger financial review modal await page.getByText('Flag move for financial review').click(); @@ -98,6 +100,7 @@ test.describe('Services counselor user', () => { }); test('is able to edit a shipment', async ({ page, scPage }) => { + test.slow(); await page.locator('[data-testid="ShipmentContainer"] .usa-button').first().click(); await page.locator('#requestedPickupDate').clear(); await page.locator('#requestedPickupDate').fill('16 Mar 2022'); @@ -125,6 +128,7 @@ test.describe('Services counselor user', () => { await expect(page.locator('.usa-alert__text')).toContainText('Your changes were saved.'); }); test('is able to view Origin GBLOC', async ({ page }) => { + test.slow(); // Check for Origin GBLOC label await expect(page.getByTestId('originGBLOC')).toHaveText('Origin GBLOC'); await expect(page.getByTestId('infoBlock')).toContainText('KKFA'); @@ -138,6 +142,7 @@ test.describe('Services counselor user', () => { }); test('is able to view USMC as Origin GBLOC', async ({ page }) => { + test.slow(); // Check for Origin GBLOC label await expect(page.getByTestId('originGBLOC')).toHaveText('Origin GBLOC'); await expect(page.getByTestId('infoBlock')).toContainText('KKFA / USMC'); @@ -151,6 +156,7 @@ test.describe('Services counselor user', () => { }); test('is able to view orders and amended orders', async ({ page }) => { + test.slow(); await page.getByRole('link', { name: 'View and edit orders' }).click(); await page.getByTestId('openMenu').click(); await expect(page.getByTestId('DocViewerMenu').getByTestId('button')).toHaveCount(3); @@ -170,6 +176,7 @@ test.describe('Services counselor user', () => { }); test('is able to add and delete orders and amended orders', async ({ page, officePage }) => { + test.slow(); await page.getByRole('link', { name: 'View and edit orders' }).click(); // check initial quanity of files @@ -226,6 +233,7 @@ test.describe('Services counselor user', () => { }); test('is able to add and delete supporting documents', async ({ page, officePage }) => { + test.slow(); test.skip(supportingDocsEnabled === 'false', 'Skip if Supporting Documents is not enabled.'); await page.getByRole('link', { name: 'Supporting Documents' }).click(); await expect(page.getByText('No supporting documents have been uploaded.')).toBeVisible(); @@ -260,6 +268,7 @@ test.describe('Services counselor user', () => { }); test('is able to add a shipment', async ({ page, scPage }) => { + test.slow(); const deliveryDate = new Date().toLocaleDateString('en-US'); await expect(page.locator('[data-testid="ShipmentContainer"] .usa-button')).toHaveCount(2); @@ -292,6 +301,7 @@ test.describe('Services counselor user', () => { }); test('is able to see and use the left navigation', async ({ page }) => { + test.slow(); await expect(page.locator('a[href*="#shipments"]')).toContainText('Shipments'); await expect(page.locator('a[href*="#orders"]')).toContainText('Orders'); await expect(page.locator('a[href*="#allowances"]')).toContainText('Allowances'); @@ -307,6 +317,7 @@ test.describe('Services counselor user', () => { }); test('is able to edit a shipment', async ({ page, scPage }) => { + test.slow(); await page.locator('[data-testid="ShipmentContainer"] .usa-button').first().click(); await page.locator('#requestedPickupDate').clear(); await page.locator('#requestedPickupDate').fill('16 Mar 2022'); @@ -323,6 +334,8 @@ test.describe('Services counselor user', () => { await expect(page.getByText(LocationLookup, { exact: true })).toBeVisible(); await page.keyboard.press('Enter'); await page.locator('select[name="destinationType"]').selectOption({ label: 'Home of selection (HOS)' }); + await page.getByLabel('Requested pickup date').fill('16 Mar 2022'); + await page.locator('[data-testid="submitForm"]').click(); await scPage.waitForLoading(); @@ -330,6 +343,7 @@ test.describe('Services counselor user', () => { }); test('is able to update destination type if delivery address is unknown', async ({ page, scPage }) => { + test.slow(); await page.locator('[data-testid="ShipmentContainer"] .usa-button').first().click(); await page.locator('#requestedPickupDate').clear(); await page.locator('#requestedPickupDate').fill('16 Mar 2022'); @@ -353,6 +367,7 @@ test.describe('Services counselor user', () => { }); test('is able to see that the tag next to shipment is updated', async ({ page, scPage }) => { + test.slow(); // Verify that there's a tag on the left nav that flags missing information await expect(page.locator('[data-testid="shipment-missing-info-alert"]')).toContainText('1'); @@ -372,6 +387,7 @@ test.describe('Services counselor user', () => { }); test('can complete review of PPM shipment documents and view documents after', async ({ page, scPage }) => { + test.slow(); const move = await scPage.testHarness.buildApprovedMoveWithPPMAllDocTypesOffice(); await scPage.navigateToCloseoutMove(move.locator); @@ -427,6 +443,7 @@ test.describe('Services counselor user', () => { }); test('is able to edit/save actual move start date', async ({ page, scPage }) => { + test.slow(); // Navigate to the "Review documents" page await expect(page.getByRole('button', { name: /Review documents/i })).toBeVisible(); await page.getByRole('button', { name: 'Review documents' }).click(); @@ -443,6 +460,7 @@ test.describe('Services counselor user', () => { }); test('is able to edit/save pickup address', async ({ page, scPage }) => { + test.slow(); // Navigate to the "Review documents" page await expect(page.getByRole('button', { name: /Review documents/i })).toBeVisible(); await page.getByRole('button', { name: 'Review documents' }).click(); @@ -459,6 +477,7 @@ test.describe('Services counselor user', () => { }); test('is able to edit/save delivery address', async ({ page, scPage }) => { + test.slow(); // Navigate to the "Review documents" page await expect(page.getByRole('button', { name: /Review documents/i })).toBeVisible(); await page.getByRole('button', { name: 'Review documents' }).click(); @@ -475,6 +494,7 @@ test.describe('Services counselor user', () => { }); test('is able to edit/save advance received', async ({ page, scPage }) => { + test.slow(); // Navigate to the "Review documents" page await expect(page.getByRole('button', { name: /Review documents/i })).toBeVisible(); await page.getByRole('button', { name: 'Review documents' }).click(); @@ -510,6 +530,7 @@ test.describe('Services counselor user', () => { let fullPpmMoveLocator = ''; test('counselor can see partial PPM ready for closeout', async ({ page, scPage }) => { + test.slow(); const partialPpmMoveCloseout = await scPage.testHarness.buildPartialPPMMoveReadyForCloseout(); partialPpmCloseoutLocator = partialPpmMoveCloseout.locator; await scPage.searchForCloseoutMove(partialPpmCloseoutLocator); @@ -517,6 +538,7 @@ test.describe('Services counselor user', () => { }); test('counselor can see partial PPM ready for counseling', async ({ page, scPage }) => { + test.slow(); const partialPpmMoveCounseling = await scPage.testHarness.buildPartialPPMMoveReadyForCounseling(); partialPpmCounselingLocator = partialPpmMoveCounseling.locator; await scPage.searchForMove(partialPpmCounselingLocator); @@ -524,6 +546,7 @@ test.describe('Services counselor user', () => { }); test('counselor can see full PPM ready for closeout', async ({ page, scPage }) => { + test.slow(); const fullPpmMove = await scPage.testHarness.buildPPMMoveWithCloseout(); fullPpmMoveLocator = fullPpmMove.locator; await scPage.searchForCloseoutMove(fullPpmMoveLocator); @@ -534,6 +557,7 @@ test.describe('Services counselor user', () => { test.describe('Actual expense reimbursement tests', () => { test.describe('is able to view/edit actual expense reimbursement for non-civilian moves', () => { test('view/edit actual expense reimbursement - edit shipments page', async ({ page, scPage }) => { + test.slow(); const move = await scPage.testHarness.buildSubmittedMoveWithPPMShipmentForSC(); await scPage.navigateToMove(move.locator); @@ -564,6 +588,7 @@ test.describe('Services counselor user', () => { }); test('view/edit actual expense reimbursement - PPM closeout review documents', async ({ page, scPage }) => { + test.slow(); const move = await scPage.testHarness.buildApprovedMoveWithPPMProgearWeightTicketOffice(); await scPage.navigateToMoveUsingMoveSearch(move.locator); @@ -593,6 +618,7 @@ test.describe('Services counselor user', () => { test.describe('is unable to edit actual expense reimbursement for civilian moves', () => { test('cannot edit actual expense reimbursement - edit shipments page', async ({ page, scPage }) => { + test.slow(); const move = await scPage.testHarness.buildSubmittedMoveWithPPMShipmentForSC(); await scPage.navigateToMove(move.locator); @@ -613,6 +639,7 @@ test.describe('Services counselor user', () => { }); test('cannot edit actual expense reimbursement - PPM closeout review documents', async ({ page, scPage }) => { + test.slow(); const move = await scPage.testHarness.buildApprovedMoveWithPPMProgearWeightTicketOfficeCivilian(); await scPage.navigateToMoveUsingMoveSearch(move.locator); @@ -637,6 +664,7 @@ test.describe('Services counselor user', () => { }); test('is unable to view/edit orders after MTO has been created(sent to prime)', async ({ page }) => { + test.slow(); await expect(page.getByTestId('view-edit-orders')).toBeHidden(); await expect(page.getByTestId('edit-allowances')).toBeHidden(); }); diff --git a/playwright/tests/office/servicescounseling/servicesCounselingMovingExpenses.spec.js b/playwright/tests/office/servicescounseling/servicesCounselingMovingExpenses.spec.js index 4933c3616f2..ea3452e5827 100644 --- a/playwright/tests/office/servicescounseling/servicesCounselingMovingExpenses.spec.js +++ b/playwright/tests/office/servicescounseling/servicesCounselingMovingExpenses.spec.js @@ -1,6 +1,7 @@ import { test, expect } from './servicesCounselingTestFixture'; test('A service counselor can approve/reject moving expenses', async ({ page, scPage }) => { + test.slow(); // Create a move with TestHarness, and then navigate to the move details page for it const move = await scPage.testHarness.buildApprovedMoveWithPPMMovingExpenseOffice(); await scPage.navigateToCloseoutMove(move.locator); @@ -94,6 +95,8 @@ test('A service counselor can approve/reject moving expenses', async ({ page, sc }); test('Review documents page displays correct value for Total days in SIT', async ({ page, scPage }) => { + test.slow(); + test.setTimeout(300000); // This one has been a headache forever. Shoehorn fix to go way above default "slow" timeout // Create a move with TestHarness, and then navigate to the move details page for it const move = await scPage.testHarness.buildApprovedMoveWithPPMMovingExpenseOffice(); await scPage.navigateToCloseoutMove(move.locator); diff --git a/playwright/tests/office/servicescounseling/servicesCounselingQueueFilters.spec.js b/playwright/tests/office/servicescounseling/servicesCounselingQueueFilters.spec.js index e4ef042c31d..4bd1e13d97c 100644 --- a/playwright/tests/office/servicescounseling/servicesCounselingQueueFilters.spec.js +++ b/playwright/tests/office/servicescounseling/servicesCounselingQueueFilters.spec.js @@ -23,6 +23,7 @@ test.describe('Services counselor user', () => { }); test('is able to filter partial vs full moves based on ppm type', async ({ page }) => { + test.slow(); // closeout tab // Created a single Partial PPM move, so when we search for @@ -40,6 +41,7 @@ test.describe('Services counselor user', () => { }); test('is able to filter moves based on PPM status', async ({ page }) => { + test.slow(); // Check for Needs closeout filter await page.locator('th[data-testid="locator"] > div > input').clear(); await page.locator('th[data-testid="locator"] > div > input').fill(moveWithNeedsCloseoutLocator); @@ -49,6 +51,7 @@ test.describe('Services counselor user', () => { }); test('is able to filter moves based on destination duty location', async ({ page }) => { + test.slow(); // add filter for move code (PPM closeout that has Fort Gordon as // its destination duty location) @@ -80,6 +83,7 @@ test.describe('Services counselor user', () => { }); test('is able to filter moves based on PPM Closeout initiated', async ({ page }) => { + test.slow(); const closeoutDate = new Date().toLocaleDateString('en-US'); // first test with bogus date and no moves are found @@ -108,6 +112,7 @@ test.describe('Services counselor user', () => { }); test('is able to filter moves based on PPM Closeout location', async ({ page }) => { + test.slow(); await page.locator('th[data-testid="locator"] > div > input').fill(moveLocator); await page.locator('th[data-testid="locator"] > div > input').blur(); // add another filter for the closeout office column checking diff --git a/playwright/tests/office/txo/tooFlows.spec.js b/playwright/tests/office/txo/tooFlows.spec.js index 4fae441f2d3..412076deb28 100644 --- a/playwright/tests/office/txo/tooFlows.spec.js +++ b/playwright/tests/office/txo/tooFlows.spec.js @@ -643,6 +643,7 @@ test.describe('TOO user', () => { }); test('approves a delivery address change request for an HHG shipment', async ({ officePage, page }) => { + test.setTimeout(300000); // This one has been a headache forever. Shoehorn fix to go way above default "slow" timeout const shipmentAddressUpdate = await officePage.testHarness.bulidHHGMoveWithAddressChangeRequest(); await officePage.signInAsNewTOOUser(); tooFlowPage = new TooFlowPage(officePage, shipmentAddressUpdate.Shipment.MoveTaskOrder); diff --git a/scripts/db-truncate b/scripts/db-truncate index 13835108703..c92cd5aae82 100755 --- a/scripts/db-truncate +++ b/scripts/db-truncate @@ -15,7 +15,7 @@ BEGIN 'ports','port_locations', 're_fsc_multipliers', 'ghc_diesel_fuel_prices', 're_zip3s','zip3_distances', 're_contracts', 're_domestic_service_areas', 're_intl_prices', 're_intl_other_prices', 're_domestic_linehaul_prices', - 're_domestic_service_area_prices', 're_domestic_other_prices')) LOOP + 're_domestic_service_area_prices', 're_domestic_other_prices', 'pay_grades', 'hhg_allowances')) LOOP EXECUTE 'TRUNCATE TABLE ' || quote_ident(r.tablename) || ' CASCADE'; END LOOP; END \$\$; diff --git a/src/components/Office/AddOrdersForm/AddOrdersForm.test.jsx b/src/components/Office/AddOrdersForm/AddOrdersForm.test.jsx index c3cc2f27d8b..7fe7c5bc413 100644 --- a/src/components/Office/AddOrdersForm/AddOrdersForm.test.jsx +++ b/src/components/Office/AddOrdersForm/AddOrdersForm.test.jsx @@ -453,3 +453,23 @@ describe('AddOrdersForm - With Counseling Office', () => { expect(nextBtn.getAttribute('disabled')).toBeFalsy(); }); }); +describe('AddOrdersForm - Edge Cases and Additional Scenarios', () => { + it('disables orders type when safety move is selected', async () => { + render( + + + , + ); + + expect(screen.getByLabelText('Orders type')).toBeDisabled(); + }); + + it('disables orders type when bluebark move is selected', async () => { + render( + + + , + ); + expect(screen.getByLabelText('Orders type')).toBeDisabled(); + }); +}); diff --git a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx index 345fa0d491d..e432f02b6e3 100644 --- a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx +++ b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx @@ -678,6 +678,8 @@ describe('CreateCustomerForm', () => { const saveBtn = await screen.findByRole('button', { name: 'Save' }); expect(saveBtn).toBeInTheDocument(); + // check the safety move box + await userEvent.type(getByTestId('is-safety-move-no'), bluebarkPayload.is_safety_move); await userEvent.type(getByTestId('is-bluebark-yes'), bluebarkPayload.is_bluebark); await userEvent.selectOptions(getByLabelText('Branch of service'), ['ARMY']); diff --git a/src/scenes/Office/api.js b/src/scenes/Office/api.js deleted file mode 100644 index e6ab7e50622..00000000000 --- a/src/scenes/Office/api.js +++ /dev/null @@ -1,11 +0,0 @@ -import { getClient, checkResponse } from 'shared/Swagger/api'; - -// MOVE QUEUE -export async function RetrieveMovesForOffice(queueType) { - const client = await getClient(); - const response = await client.apis.queues.showQueue({ - queueType, - }); - checkResponse(response, 'failed to retrieve moves due to server error'); - return response.body; -} diff --git a/swagger-def/internal.yaml b/swagger-def/internal.yaml index 0a9486e0c83..4ba05eef9c5 100644 --- a/swagger-def/internal.yaml +++ b/swagger-def/internal.yaml @@ -3826,6 +3826,8 @@ paths: description: List of weights allotted entitlement schema: $ref: '#/definitions/IndexEntitlements' + '500': + description: internal server error /calendar/available_move_dates: get: summary: Returns available dates for the move calendar diff --git a/swagger/internal.yaml b/swagger/internal.yaml index 99da9030213..77dd643491f 100644 --- a/swagger/internal.yaml +++ b/swagger/internal.yaml @@ -5745,6 +5745,8 @@ paths: description: List of weights allotted entitlement schema: $ref: '#/definitions/IndexEntitlements' + '500': + description: internal server error /calendar/available_move_dates: get: summary: Returns available dates for the move calendar diff --git a/yarn.lock b/yarn.lock index dd46ddf509b..13023153627 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2947,6 +2947,11 @@ resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.1.3.tgz#6801033be7ff87a6b7cadaf5b337c9f366a3c4b0" integrity sha512-WiBSI6JBIhC6LRIsB2Kwh8DsGTlbBU+mLRxJmAe3LjHTdkDpwIbEOZgoXBbZilk/vlfjK8i6nKRAvIRn1XaIMw== +"@scarf/scarf@=1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@scarf/scarf/-/scarf-1.4.0.tgz#3bbb984085dbd6d982494538b523be1ce6562972" + integrity sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ== + "@sinclair/typebox@^0.23.3": version "0.23.5" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.23.5.tgz#93f7b9f4e3285a7a9ade7557d9a8d36809cbc47d" @@ -16650,10 +16655,12 @@ swagger-client@^3.18.5: traverse "~0.6.6" url "~0.11.0" -swagger-ui-dist@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-5.2.0.tgz#175e112b3aea756fdbbbb035d4cffef26ac579d1" - integrity sha512-rLvJBgualxNZcwKOmTFzy4zF1nHy+3S0pUDDR/ageDRZgi8aITSe7pVYiAy03xGQZtqEifjwEtHQE+eF14gveg== +swagger-ui-dist@^5.18.2: + version "5.18.2" + resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-5.18.2.tgz#62013074374d272c04ed3030704b88db5aa8c0b7" + integrity sha512-J+y4mCw/zXh1FOj5wGJvnAajq6XgHOyywsa9yITmwxIlJbMqITq3gYRZHaeqLVH/eV/HOPphE6NjF+nbSNC5Zw== + dependencies: + "@scarf/scarf" "=1.4.0" swc-loader@^0.2.3: version "0.2.3"