diff --git a/.circleci/config.yml b/.circleci/config.yml index 3372fe2c710..29d7338740f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -40,10 +40,10 @@ references: # In addition, it's common practice to disable acceptance tests and # ignore tests for dp3 deploys. See the branch settings below. - dp3-branch: &dp3-branch placeholder_branch_name + dp3-branch: &dp3-branch integrationTesting # MUST BE ONE OF: loadtest, demo, exp. # These are used to pull in env vars so the spelling matters! - dp3-env: &dp3-env placeholder_env + dp3-env: &dp3-env loadtest # set integration-ignore-branch to the branch if you want to IGNORE # integration tests, or `placeholder_branch_name` if you do want to @@ -53,7 +53,7 @@ references: # set integration-mtls-ignore-branch to the branch if you want to # IGNORE mtls integration tests, or `placeholder_branch_name` if you # do want to run them - integration-mtls-ignore-branch: &integration-mtls-ignore-branch placeholder_branch_name + integration-mtls-ignore-branch: &integration-mtls-ignore-branch integrationTesting # set client-ignore-branch to the branch if you want to IGNORE # client tests, or `placeholder_branch_name` if you do want to run @@ -226,8 +226,6 @@ executors: # maybe don't need xlarge, but getting many timeouts with large # ahobson - 2023-02-07 resource_class: xlarge - # use ~/project as that already exists in cimg/node and is owned - # by circleci working_directory: ~/project docker: - image: *playwright @@ -805,6 +803,27 @@ commands: - restore_cache: keys: - v4-cache-yarn-v4-{{ checksum "yarn.lock" }} + - run: + # Needed for PDF e2e + name: Verify react-file-viewer client dependency exists + command: | + if [ ! -d "/home/circleci/project/public/static/react-file-viewer" ]; then + echo "Error: /home/circleci/project/public/static/react-file-viewer not found, the server will not be able to serve frontend dependencies" + exit 1 + fi + - run: + # Needed for PDF e2e + name: Verify react-file-viewer pdfjs chunk exists + command: | + ls -l /home/circleci/project/public/static/react-file-viewer + if [ ! -f "/home/circleci/project/public/static/react-file-viewer/pdfjs-dist-webpack.chunk.js" ]; then + echo "Error: /home/circleci/project/public/static/react-file-viewer/pdfjs-dist-webpack.chunk.js not found, the server will not be able to serve frontend dependencies" + exit 1 + fi + - run: + name: Verify small.pdf is in workspace + command: | + ls -l ./pkg/testdatagen/testdata/bandwidth_test_docs/ - run: name: setup hosts command: | @@ -827,6 +846,7 @@ commands: export LOGIN_GOV_SECRET_KEY=$(echo $E2E_LOGIN_GOV_SECRET_KEY | base64 --decode) export HERE_MAPS_APP_ID=$E2E_HERE_MAPS_APP_ID export HERE_MAPS_APP_CODE=$E2E_HERE_MAPS_APP_CODE + export LOCAL_STORAGE_WEB_ROOT=storage # pull in review app settings here so we don't have to # reproduce them sed 's,^,export ,' config/env/review.app.env > server_env @@ -877,14 +897,16 @@ commands: export FEATURE_FLAG_MANAGE_SUPPORTING_DOCS=false export FEATURE_FLAG_THIRD_ADDRESS_AVAILABLE=false export FEATURE_FLAG_QUEUE_MANAGEMENT=false - export FEATURE_FLAG_UNACCOMPANIED_BAGGAGE=false export FEATURE_FLAG_ENABLE_ALASKA=false + export FEATURE_FLAG_UNACCOMPANIED_BAGGAGE=false + export FEATURE_FLAG_ENABLE_HAWAII=false # disable for speed, playwright tests can fail otherwise export DB_DEBUG=false make db_dev_create bin/milmove migrate + # playwright tests DO NOT NEED SEED DATA bin/milmove serve 2>&1 | fmt - run: @@ -1179,6 +1201,20 @@ jobs: - save_cache_for_go - announce_failure + # Prep the public folder for frontend dependency serving + # This is needed for things like pdfjs-dist + prep_server_hosted_client_deps: + executor: mymove_compiler + steps: + - checkout + - attach_workspace: + at: . + - run: scripts/fetch-react-file-viewer-from-yarn + - persist_to_workspace: + root: ~/transcom/mymove + paths: + - public + # `pre_deps_yarn` is used to cache yarn sources pre_deps_yarn: executor: mymove_compiler @@ -1191,6 +1227,9 @@ jobs: name: Install Frozen YARN dependencies command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn - run: scripts/check-generated-code yarn.lock + - run: + name: Rebuild dependencies without binaries + command: ./scripts/rebuild-dependencies-without-binaries # `v4-cache-yarn-v4-{{ checksum "yarn.lock" }}` is used to cache yarn sources - save_cache: key: v4-cache-yarn-v4-{{ checksum "yarn.lock" }} @@ -1301,6 +1340,9 @@ jobs: - run: name: Install Frozen YARN dependencies command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn + - run: + name: Rebuild dependencies without binaries + command: ./scripts/rebuild-dependencies-without-binaries # this is so we can avoid go mod downloading and resulting in an error on a false positive - run: scripts/pre-commit-go-mod || exit 0 - run: @@ -1587,6 +1629,9 @@ jobs: - run: name: Install Frozen YARN dependencies command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn + - run: + name: Rebuild dependencies without binaries + command: ./scripts/rebuild-dependencies-without-binaries - run: name: client test coverage command: JEST_JUNIT_OUTPUT_DIR=jest-junit-reports make client_test_coverage @@ -1737,6 +1782,8 @@ jobs: # save scripts for deploy - scripts - swagger + # Client dependency for PDF enabling + - public/static/react-file-viewer - pkg/testdatagen/testdata - announce_failure @@ -1764,7 +1811,9 @@ jobs: - run: name: Install Frozen YARN dependencies command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn - + - run: + name: Rebuild dependencies without binaries + command: ./scripts/rebuild-dependencies-without-binaries # babel and terser both have a cache # # see https://webpack.js.org/loaders/babel-loader/ and look for @@ -1922,6 +1971,9 @@ jobs: - run: name: Install Frozen YARN dependencies command: yarn install --frozen-lockfile + - run: + name: Rebuild dependencies without binaries + command: ./scripts/rebuild-dependencies-without-binaries - restore_cache: keys: - v1-node-modules-cache @@ -2279,6 +2331,8 @@ workflows: - pre_deps_golang + - prep_server_hosted_client_deps + - pre_deps_yarn - base_noop @@ -2355,6 +2409,7 @@ workflows: - integration_tests_devseed: requires: - pre_deps_golang + - prep_server_hosted_client_deps # See comments at the top of this file for configuring/using # this branch config filters: @@ -2433,6 +2488,7 @@ workflows: requires: - anti_virus - pre_deps_golang + - prep_server_hosted_client_deps - build_app: requires: @@ -2719,4 +2775,4 @@ experimental: notify: branches: only: - - main + - main \ No newline at end of file diff --git a/.envrc b/.envrc index 4d24f935289..6460c298784 100644 --- a/.envrc +++ b/.envrc @@ -157,7 +157,10 @@ export FEATURE_FLAG_MOBILE_HOME=true export FEATURE_FLAG_UNACCOMPANIED_BAGGAGE=false # Feature flag to allow AK to be entered as a state -export FEATURE_FLAG_ENABLE_ALASKA=false +export FEATURE_FLAG_ENABLE_ALASKA=true + +# Feature flag to allow HI to be entered as a state +export FEATURE_FLAG_ENABLE_HAWAII=false # Feature flag to enable/disable customers needing to authenticate with CAC on registration # When turned to true, this will require each customer user to have the value of true in cac_validated in the service_members table @@ -214,9 +217,9 @@ export DEVLOCAL_AUTH=true export DOD_CA_PACKAGE="${MYMOVE_DIR}/config/tls/milmove-cert-bundle.p7b" # MyMove client certificate -# All of our DoD-signed certs are currently signed by DOD SW CA-75 +# All of our DoD-signed certs are currently signed by DOD SW CA-66 # This cannot be changed unless our certs are all resigned -MOVE_MIL_DOD_CA_CERT=$(cat "${MYMOVE_DIR}"/config/tls/dod-sw-ca-75.pem) +MOVE_MIL_DOD_CA_CERT=$(cat "${MYMOVE_DIR}"/config/tls/dod-sw-ca-66.pem) require MOVE_MIL_DOD_TLS_CERT "See 'DISABLE_AWS_VAULT_WRAPPER=1 AWS_REGION=us-gov-west-1 aws-vault exec transcom-gov-dev -- chamber read app-devlocal move_mil_dod_tls_cert'" require MOVE_MIL_DOD_TLS_KEY "See 'DISABLE_AWS_VAULT_WRAPPER=1 AWS_REGION=us-gov-west-1 aws-vault exec transcom-gov-dev -- chamber read app-devlocal move_mil_dod_tls_key'" export MOVE_MIL_DOD_CA_CERT @@ -438,4 +441,4 @@ then fi # Check that all required environment variables are set -check_required_variables \ No newline at end of file +check_required_variables diff --git a/.eslintignore b/.eslintignore index dff5f036538..f43216f6c93 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,4 @@ # Negative filter for storybook config folder as dot folders are ignored by eslint by default # See https://github.com/eslint/eslint/issues/8429#issuecomment-355967308 !/.storybook +public/static/react-file-viewer/* \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c0be88a7dfa..3554334587e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -6,4 +6,39 @@ # owners will be requested for a review. # Add language specific code owners if it becomes relevant -* @transcom/codeowners +# database team +/migrations/ @transcom/cacidatabaseteam +/pkg/assets/sql_scripts/ @transcom/cacidatabaseteam + +# backend team +/pkg/ @transcom/cacibackendteam +/swagger-def/ @transcom/cacibackendteam +*.go @transcom/cacibackendteam + + +# frontend team +/src/ @transcom/cacifrontendteam +/playwright/tests/ @transcom/cacifrontendteam + + +## not required for team specific review as prime UI is not deployed to production app +src/components/PrimeUI/ +src/pages/PrimeUI/ + + +# Require tech lead review for changes to the following files +.golangci.yml @transcom/codeowners +.pre-commit-config.yaml @transcom/codeowners +.eslintrc.js @transcom/codeowners +/eslint-plugin-ato/ @transcom/codeowners +/pkg/ato-linter/ @transcom/codeowners +/scripts/ @transcom/codeowners +/.circleci/config.yml @transcom/codeowners +/config/ @transcom/codeowners +Dockerfile* @transcom/codeowners +Brewfile* @transcom/codeowners +/Makefile @transcom/codeowners +package.json @transcom/codeowners +go.mod @transcom/codeowners +/github/ @transcom/codeowners +docker-compose* @transcom/codeowners \ No newline at end of file diff --git a/.gitignore b/.gitignore index febfd4d0dcc..5ccadf7861b 100644 --- a/.gitignore +++ b/.gitignore @@ -55,6 +55,8 @@ schema.sql *.stamp +public/static/react-file-viewer/* +!public/static/react-file-viewer/.gitkeep public/swagger-ui/* !public/swagger-ui/api.html !public/swagger-ui/internal.html diff --git a/.prettierignore b/.prettierignore index 7aa12310386..30643fa56b3 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,2 +1,3 @@ public/swagger-ui/* !public/swagger-ui/index.html +public/static/react-file-viewer/* \ No newline at end of file diff --git a/Brewfile.local b/Brewfile.local index 84f997ce601..6050c7e1f02 100644 --- a/Brewfile.local +++ b/Brewfile.local @@ -12,6 +12,13 @@ brew 'pre-commit' brew 'shellcheck' brew 'watchman' brew 'yarn' +brew 'pkg-config' +brew 'cairo' +brew 'pango' +brew 'libpng' +brew 'jpeg' +brew 'giflib' +brew 'librsvg' cask 'aws-vault' cask 'opensc' diff --git a/Dockerfile b/Dockerfile index 9d8fa4fc867..626887eda65 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,9 +17,11 @@ COPY bin/milmove /bin/milmove COPY config/tls/milmove-cert-bundle.p7b /config/tls/milmove-cert-bundle.p7b COPY config/tls/dod-sw-ca-75.pem /config/tls/dod-sw-ca-75.pem +COPY config/tls/dod-sw-ca-66.pem /config/tls/dod-sw-ca-66.pem COPY swagger/* /swagger/ COPY build /build +COPY public/static/react-file-viewer /public/static/react-file-viewer ENTRYPOINT ["/bin/milmove"] diff --git a/Dockerfile.dp3 b/Dockerfile.dp3 index 6beeaf496d2..dd58313bb01 100644 --- a/Dockerfile.dp3 +++ b/Dockerfile.dp3 @@ -18,8 +18,14 @@ COPY config/tls/api.loadtest.dp3.us.crt /config/tls/api.loadtest.dp3.us.crt COPY config/tls/api.exp.dp3.us.chain.der.p7b /config/tls/api.exp.dp3.us.chain.der.p7b COPY config/tls/api.exp.dp3.us.crt /config/tls/api.exp.dp3.us.crt +#copy dod certs +COPY config/tls/milmove-cert-bundle.p7b /config/tls/milmove-cert-bundle.p7b +COPY config/tls/dod-sw-ca-75.pem /config/tls/dod-sw-ca-75.pem +COPY config/tls/dod-sw-ca-66.pem /config/tls/dod-sw-ca-66.pem + COPY swagger/* /swagger/ COPY build /build +COPY public/static/react-file-viewer /public/static/react-file-viewer ENTRYPOINT ["/bin/milmove"] diff --git a/Dockerfile.e2e b/Dockerfile.e2e index c88d243eb48..ad5c7417d22 100644 --- a/Dockerfile.e2e +++ b/Dockerfile.e2e @@ -21,6 +21,7 @@ COPY config /config COPY swagger /swagger COPY pkg/testdatagen/testdata /pkg/testdatagen/testdata COPY scripts /scripts +COPY public/static/react-file-viewer /public/static/react-file-viewer # Install tools needed in container # hadolint ignore=DL3018 diff --git a/Dockerfile.local b/Dockerfile.local index d64c11c34b4..728a6a6f4c0 100644 --- a/Dockerfile.local +++ b/Dockerfile.local @@ -27,7 +27,8 @@ COPY --from=builder --chown=root:root /home/circleci/project/bin/rds-ca-2019-roo COPY --from=builder --chown=root:root /home/circleci/project/bin/milmove /bin/milmove COPY config/tls/milmove-cert-bundle.p7b /config/tls/milmove-cert-bundle.p7b -COPY config/tls/dod-sw-ca-66.pem /config/tls/dod-sw-ca-75.pem +COPY config/tls/dod-sw-ca-75.pem /config/tls/dod-sw-ca-75.pem +COPY config/tls/dod-sw-ca-66.pem /config/tls/dod-sw-ca-66.pem # While it's ok to have these certs copied locally, they should never be copied into Dockerfile. COPY config/tls/devlocal-ca.key /config/tls/devlocal-ca.key @@ -35,6 +36,7 @@ COPY config/tls/devlocal-ca.pem /config/tls/devlocal-ca.pem COPY swagger/* /swagger/ COPY build /build +COPY public/static/react-file-viewer /public/static/react-file-viewer ENTRYPOINT ["/bin/milmove"] diff --git a/Dockerfile.reviewapp b/Dockerfile.reviewapp index 7de62db25f2..2949f635093 100644 --- a/Dockerfile.reviewapp +++ b/Dockerfile.reviewapp @@ -33,6 +33,7 @@ ARG GIT_COMMIT COPY cmd /build/cmd COPY swagger /build/swagger COPY pkg /build/pkg +COPY public/static/react-file-viewer /public/static/react-file-viewer # fake src dir to silence make RUN mkdir /build/src @@ -99,6 +100,7 @@ COPY public /build/public COPY src /build/src RUN set -x \ && ./scripts/copy-swagger-ui \ + && ./scripts/copy-react-file-viewer \ && yarn build ######### diff --git a/Makefile b/Makefile index 9f86ef2ce78..5fe2ff52c73 100644 --- a/Makefile +++ b/Makefile @@ -159,11 +159,16 @@ check_app: ## Make sure you're running the correct APP client_deps_update: .check_node_version.stamp ## Update client dependencies yarn upgrade +.PHONY: server_hosted_client_deps +server_hosted_client_deps: scripts/fetch-react-file-viewer-from-yarn ## Serve static dependencies for client. This can be used by devs, but is pretty much exclusively used by CI/CD for compiler reasons + .PHONY: client_deps client_deps: .check_hosts.stamp .client_deps.stamp ## Install client dependencies .client_deps.stamp: yarn.lock .check_node_version.stamp yarn install scripts/copy-swagger-ui + scripts/copy-react-file-viewer + scripts/rebuild-dependencies-without-binaries touch .client_deps.stamp .client_build.stamp: .client_deps.stamp $(shell find src -type f) @@ -344,7 +349,8 @@ endif ./scripts/openapi bundle -o swagger/ ## Bundles the API definition files into a complete specification touch .swagger_build.stamp -server_generate: .server_generate.stamp +.PHONY: server_generate +server_generate: .server_generate.stamp ## generate the server code from swagger files .server_generate.stamp: .check_go_version.stamp .check_gopath.stamp .swagger_build.stamp bin/swagger $(wildcard swagger/*.yaml) ## Generate golang server code from Swagger files scripts/gen-server @@ -410,6 +416,9 @@ build_tools: bin/gin \ bin/simulate-process-tpps \ bin/tls-checker ## Build all tools +.PHONY: prep_webpack_chunks +prep_webpack_chunks: scripts/copy-react-file-viewer + .PHONY: build build: server_build build_tools client_build ## Build the server, tools, and client diff --git a/config-overrides.js b/config-overrides.js index f7974bdd7e2..abbee24e96a 100644 --- a/config-overrides.js +++ b/config-overrides.js @@ -15,6 +15,8 @@ module.exports = { config.resolve.fallback = { // This is the Node polyfill for process/browser http: require.resolve('stream-http'), + https: false, + fs: false, }; config.plugins.push( new webpack.ProvidePlugin({ diff --git a/config/env/demo.app-client-tls.env b/config/env/demo.app-client-tls.env index dd7afd4014d..a546d0cc6b0 100644 --- a/config/env/demo.app-client-tls.env +++ b/config/env/demo.app-client-tls.env @@ -48,4 +48,5 @@ FEATURE_FLAG_MANAGE_SUPPORTING_DOCS=false FEATURE_FLAG_THIRD_ADDRESS_AVAILABLE=false FEATURE_FLAG_QUEUE_MANAGEMENT=false FEATURE_FLAG_DODID_UNIQUE=false -FEATURE_FLAG_ENABLE_ALASKA=false \ No newline at end of file +FEATURE_FLAG_ENABLE_ALASKA=false +FEATURE_FLAG_ENABLE_HAWAII=false \ No newline at end of file diff --git a/config/env/demo.app.env b/config/env/demo.app.env index 2ce5b17e43d..ed92b849052 100644 --- a/config/env/demo.app.env +++ b/config/env/demo.app.env @@ -53,4 +53,5 @@ FEATURE_FLAG_MANAGE_SUPPORTING_DOCS=false FEATURE_FLAG_THIRD_ADDRESS_AVAILABLE=false FEATURE_FLAG_QUEUE_MANAGEMENT=false FEATURE_FLAG_DODID_UNIQUE=false -FEATURE_FLAG_ENABLE_ALASKA=false \ No newline at end of file +FEATURE_FLAG_ENABLE_ALASKA=false +FEATURE_FLAG_ENABLE_HAWAII=false \ No newline at end of file diff --git a/config/env/exp.app-client-tls.env b/config/env/exp.app-client-tls.env index e66c1cbefb0..e4ba80d8b88 100644 --- a/config/env/exp.app-client-tls.env +++ b/config/env/exp.app-client-tls.env @@ -48,4 +48,5 @@ FEATURE_FLAG_MANAGE_SUPPORTING_DOCS=false FEATURE_FLAG_THIRD_ADDRESS_AVAILABLE=false FEATURE_FLAG_QUEUE_MANAGEMENT=false FEATURE_FLAG_DODID_UNIQUE=false -FEATURE_FLAG_ENABLE_ALASKA=false \ No newline at end of file +FEATURE_FLAG_ENABLE_ALASKA=false +FEATURE_FLAG_ENABLE_HAWAII=false \ No newline at end of file diff --git a/config/env/exp.app.env b/config/env/exp.app.env index cf5f71dfc13..bc953c7f9d0 100644 --- a/config/env/exp.app.env +++ b/config/env/exp.app.env @@ -53,4 +53,5 @@ FEATURE_FLAG_MANAGE_SUPPORTING_DOCS=false FEATURE_FLAG_THIRD_ADDRESS_AVAILABLE=false FEATURE_FLAG_QUEUE_MANAGEMENT=false FEATURE_FLAG_DODID_UNIQUE=false -FEATURE_FLAG_ENABLE_ALASKA=false \ No newline at end of file +FEATURE_FLAG_ENABLE_ALASKA=false +FEATURE_FLAG_ENABLE_HAWAII=false \ No newline at end of file diff --git a/config/env/loadtest.app-client-tls.env b/config/env/loadtest.app-client-tls.env index 7ab7e57f7df..02e9fca87ce 100644 --- a/config/env/loadtest.app-client-tls.env +++ b/config/env/loadtest.app-client-tls.env @@ -46,4 +46,5 @@ FEATURE_FLAG_MANAGE_SUPPORTING_DOCS=false FEATURE_FLAG_THIRD_ADDRESS_AVAILABLE=false FEATURE_FLAG_QUEUE_MANAGEMENT=false FEATURE_FLAG_DODID_UNIQUE=false -FEATURE_FLAG_ENABLE_ALASKA=false \ No newline at end of file +FEATURE_FLAG_ENABLE_ALASKA=false +FEATURE_FLAG_ENABLE_HAWAII=false \ No newline at end of file diff --git a/config/env/loadtest.app.env b/config/env/loadtest.app.env index b112696073b..521864b95ce 100644 --- a/config/env/loadtest.app.env +++ b/config/env/loadtest.app.env @@ -8,7 +8,7 @@ DB_USER=crud DEBUG_PPROF=false DEVLOCAL_AUTH=true DOD_CA_PACKAGE=/config/tls/api.loadtest.dp3.us.chain.der.p7b -DTOD_USE_MOCK=true +DTOD_USE_MOCK=false EMAIL_BACKEND=ses FEATURE_FLAG_SERVER_URL=http://flipt.svc-loadtest.local:8080 HEALTH_SERVER_ENABLED=true @@ -51,4 +51,5 @@ FEATURE_FLAG_MANAGE_SUPPORTING_DOCS=false FEATURE_FLAG_THIRD_ADDRESS_AVAILABLE=false FEATURE_FLAG_QUEUE_MANAGEMENT=false FEATURE_FLAG_DODID_UNIQUE=false -FEATURE_FLAG_ENABLE_ALASKA=false \ No newline at end of file +FEATURE_FLAG_ENABLE_ALASKA=false +FEATURE_FLAG_ENABLE_HAWAII=false \ No newline at end of file diff --git a/config/env/prd.app-client-tls.env b/config/env/prd.app-client-tls.env index 0e41fb1331a..17f6f8ce169 100644 --- a/config/env/prd.app-client-tls.env +++ b/config/env/prd.app-client-tls.env @@ -45,4 +45,5 @@ FEATURE_FLAG_MANAGE_SUPPORTING_DOCS=false FEATURE_FLAG_THIRD_ADDRESS_AVAILABLE=false FEATURE_FLAG_QUEUE_MANAGEMENT=false FEATURE_FLAG_DODID_UNIQUE=false -FEATURE_FLAG_ENABLE_ALASKA=false \ No newline at end of file +FEATURE_FLAG_ENABLE_ALASKA=false +FEATURE_FLAG_ENABLE_HAWAII=false \ No newline at end of file diff --git a/config/env/prd.app.env b/config/env/prd.app.env index 6be73414f95..60696b84814 100644 --- a/config/env/prd.app.env +++ b/config/env/prd.app.env @@ -52,4 +52,5 @@ FEATURE_FLAG_MANAGE_SUPPORTING_DOCS=false FEATURE_FLAG_THIRD_ADDRESS_AVAILABLE=false FEATURE_FLAG_QUEUE_MANAGEMENT=false FEATURE_FLAG_DODID_UNIQUE=false -FEATURE_FLAG_ENABLE_ALASKA=false \ No newline at end of file +FEATURE_FLAG_ENABLE_ALASKA=false +FEATURE_FLAG_ENABLE_HAWAII=false \ No newline at end of file diff --git a/config/env/review.app.env b/config/env/review.app.env index 0e87bb2b912..bbcb2ede7d5 100644 --- a/config/env/review.app.env +++ b/config/env/review.app.env @@ -14,7 +14,7 @@ EIA_KEY=db2522a43820268a41a802a16ae9fd26 ENVIRONMENT=review AWS_CF_DOMAIN=assets.devlocal.move.mil IWS_RBS_ENABLED=1 -LOCAL_STORAGE_ROOT=/tmp +LOCAL_STORAGE_ROOT=tmp LOCAL_STORAGE_WEB_ROOT=storage LOGIN_GOV_CALLBACK_PORT=443 MUTUAL_TLS_ENABLED=0 diff --git a/config/env/stg.app-client-tls.env b/config/env/stg.app-client-tls.env index b907e4f8a56..e71d4cdd3ca 100644 --- a/config/env/stg.app-client-tls.env +++ b/config/env/stg.app-client-tls.env @@ -47,4 +47,5 @@ FEATURE_FLAG_MANAGE_SUPPORTING_DOCS=false FEATURE_FLAG_THIRD_ADDRESS_AVAILABLE=false FEATURE_FLAG_QUEUE_MANAGEMENT=false FEATURE_FLAG_DODID_UNIQUE=false -FEATURE_FLAG_ENABLE_ALASKA=false \ No newline at end of file +FEATURE_FLAG_ENABLE_ALASKA=false +FEATURE_FLAG_ENABLE_HAWAII=false \ No newline at end of file diff --git a/config/env/stg.app.env b/config/env/stg.app.env index c050d32b62f..7d38e89c4dc 100644 --- a/config/env/stg.app.env +++ b/config/env/stg.app.env @@ -53,4 +53,5 @@ FEATURE_FLAG_MANAGE_SUPPORTING_DOCS=false FEATURE_FLAG_THIRD_ADDRESS_AVAILABLE=false FEATURE_FLAG_QUEUE_MANAGEMENT=false FEATURE_FLAG_DODID_UNIQUE=false -FEATURE_FLAG_ENABLE_ALASKA=false \ No newline at end of file +FEATURE_FLAG_ENABLE_ALASKA=false +FEATURE_FLAG_ENABLE_HAWAII=false \ No newline at end of file diff --git a/config/tls/api.loadtest.dp3.us.chain.der.p7b b/config/tls/api.loadtest.dp3.us.chain.der.p7b index 8e90d3ed5da..a575ff9f7b5 100644 Binary files a/config/tls/api.loadtest.dp3.us.chain.der.p7b and b/config/tls/api.loadtest.dp3.us.chain.der.p7b differ diff --git a/migrations/app/migrations_manifest.txt b/migrations/app/migrations_manifest.txt index 5d41496ab5b..325df87cb39 100644 --- a/migrations/app/migrations_manifest.txt +++ b/migrations/app/migrations_manifest.txt @@ -1020,16 +1020,31 @@ 20241007184230_insertReOconusRateAreas.up.sql 20241007184259_insertReIntlTransTime.up.sql 20241007224427_update_addresses_us_post_region_cities_id.up.sql +20241008133341_consolidate_allowable_weight_in_ppm_shipments.up.sql 20241008212243_populate_market_code_on_shipments_table.up.sql 20241009210749_create_view_v_locations.up.sql 20241011201326_changes_for_actual_expense_reimbursement_field.up.sql 20241017183144_add_AK_HI_duty_locations.up.sql +20241018091722_oconus-entitlements-enhancement.up.sql 20241023130404_create_re_service_items.up.sql +20241023184337_create_ports_table.up.sql +20241023184350_create_port_locations_table.up.sql +20241023184437_insert_ports.up.sql 20241024114748_create_gbloc_aors.up.sql 20241029125015_add_orders_type_enum.up.sql 20241029144404_hdt-614-adjust-accomack-county.up.sql +20241031163018_create_ub_allowances_table.up.sql 20241107180705_add_alternative_AK_HI_duty_location_names.up.sql 20241109002854_add_gsr_table_to_move_history.up.sql 20241111203514_add_external_crate_and_remove_icrtsa.up.sql +20241111221400_add_new_order_types.up.sql 20241111223224_change_international_sit_services_to_accessorials.up.sql +20241115214553_create_re_fsc_multipliers_table.up.sql +20241119151019_stored_procs_for_ordering_service_items.up.sql +20241119163933_set_inactive_NSRA15_oconus_rate_areas.up.sql +20241120221040_change_port_location_fk_to_correct_table.up.sql +20241122155416_total_dependents_calculation.up.sql +20241126222026_add_sort_column_to_re_service_items.up.sql +20241127133504_add_indexes_speed_up_counseling_offices.up.sql +20241202163059_create_test_sequence_dev_env.up.sql 20241203024453_add_ppm_max_incentive_column.up.sql diff --git a/migrations/app/schema/20241007162933_addPaymentRequestEdiFiles.up.sql b/migrations/app/schema/20241007162933_addPaymentRequestEdiFiles.up.sql index ae0d2d1ae60..e7bc2cbb582 100644 --- a/migrations/app/schema/20241007162933_addPaymentRequestEdiFiles.up.sql +++ b/migrations/app/schema/20241007162933_addPaymentRequestEdiFiles.up.sql @@ -1,8 +1,13 @@ -CREATE TABLE payment_request_edi_files ( - id UUID PRIMARY KEY, - payment_request_number TEXT NOT NULL, - edi_string TEXT NOT NULL, - file_name TEXT NOT NULL, - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP -); +CREATE TABLE + IF NOT EXISTS payment_request_edi_files ( + id UUID PRIMARY KEY, + payment_request_number TEXT NOT NULL, + edi_string TEXT NOT NULL, + file_name TEXT NOT NULL, + created_at TIMESTAMP + WITH + TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP + WITH + TIME ZONE DEFAULT CURRENT_TIMESTAMP + ); \ No newline at end of file diff --git a/migrations/app/schema/20241008133341_consolidate_allowable_weight_in_ppm_shipments.up.sql b/migrations/app/schema/20241008133341_consolidate_allowable_weight_in_ppm_shipments.up.sql new file mode 100644 index 00000000000..6dca0a656ca --- /dev/null +++ b/migrations/app/schema/20241008133341_consolidate_allowable_weight_in_ppm_shipments.up.sql @@ -0,0 +1,29 @@ +SET statement_timeout = 300000; +SET lock_timeout = 300000; +SET idle_in_transaction_session_timeout = 300000; + +ALTER TABLE ppm_shipments +ADD COLUMN IF NOT EXISTS allowable_weight integer; + +COMMENT on COLUMN ppm_shipments.allowable_weight IS 'Combined allowable weight for all trips.'; + +UPDATE ppm_shipments +SET allowable_weight = summed_weights.summed_allowable_weight +FROM ( + SELECT ppm_shipment_id, SUM(coalesce(full_weight, 0) - coalesce(empty_weight, 0)) AS summed_allowable_weight FROM public.weight_tickets + GROUP BY ppm_shipment_id +) AS summed_weights +WHERE ppm_shipments.id = summed_weights.ppm_shipment_id +AND ppm_shipments.status = 'NEEDS_CLOSEOUT'; + +UPDATE ppm_shipments +SET allowable_weight = summed_weights.summed_allowable_weight +FROM ( + SELECT ppm_shipment_id, SUM(weight_tickets.allowable_weight) AS summed_allowable_weight FROM public.weight_tickets + GROUP BY ppm_shipment_id +) AS summed_weights +WHERE ppm_shipments.id = summed_weights.ppm_shipment_id +AND ppm_shipments.status = 'CLOSEOUT_COMPLETE'; + +ALTER TABLE weight_tickets + DROP COLUMN IF EXISTS allowable_weight; diff --git a/migrations/app/schema/20241018091722_oconus-entitlements-enhancement.up.sql b/migrations/app/schema/20241018091722_oconus-entitlements-enhancement.up.sql new file mode 100644 index 00000000000..190e63ef285 --- /dev/null +++ b/migrations/app/schema/20241018091722_oconus-entitlements-enhancement.up.sql @@ -0,0 +1,11 @@ + +ALTER TABLE entitlements + ADD COLUMN IF NOT EXISTS accompanied_tour BOOLEAN NULL, + ADD COLUMN IF NOT EXISTS dependents_under_twelve INTEGER NULL, + ADD COLUMN IF NOT EXISTS dependents_twelve_and_over INTEGER NULL, + ADD COLUMN IF NOT EXISTS ub_allowance INTEGER NULL; + +COMMENT ON COLUMN entitlements.accompanied_tour IS 'Indicates if the move entitlement allows dependents to travel to the new Permanent Duty Station (PDS). This is only present on OCONUS moves.'; +COMMENT ON COLUMN entitlements.dependents_under_twelve IS 'Indicates the number of dependents under the age of twelve for a move. This is only present on OCONUS moves.'; +COMMENT ON COLUMN entitlements.dependents_twelve_and_over IS 'Indicates the number of dependents of the age twelve or older for a move. This is only present on OCONUS moves.'; +COMMENT ON COLUMN entitlements.ub_allowance IS 'The amount of weight in pounds that the move is entitled for shipment types of Unaccompanied Baggage.'; diff --git a/migrations/app/schema/20241023184337_create_ports_table.up.sql b/migrations/app/schema/20241023184337_create_ports_table.up.sql new file mode 100644 index 00000000000..cdd4e4d9dff --- /dev/null +++ b/migrations/app/schema/20241023184337_create_ports_table.up.sql @@ -0,0 +1,21 @@ +CREATE TABLE IF NOT EXISTS ports ( + id uuid NOT NULL, + port_code varchar(4) NOT NULL, + port_type varchar(1) NOT NULL, + port_name varchar(100) NOT NULL, + created_at timestamp NOT NULL DEFAULT NOW(), + updated_at timestamp NOT NULL DEFAULT NOW(), + CONSTRAINT port_pkey PRIMARY KEY(id), + CONSTRAINT unique_port_code UNIQUE (port_code), + CONSTRAINT chk_port_type CHECK (port_type IN ('A', 'S', 'B')) +); +COMMENT ON TABLE ports IS 'Stores ports identification data'; +COMMENT ON COLUMN ports.port_code IS 'The 4 digit port code'; +COMMENT ON COLUMN ports.port_type IS 'The 1 char port type A, S, or B'; +COMMENT ON COLUMN ports.port_name IS 'The name of the port'; +ALTER TABLE mto_service_items ADD COLUMN IF NOT EXISTS poe_location_id uuid; +ALTER TABLE mto_service_items ADD CONSTRAINT fk_poe_location_id FOREIGN KEY (poe_location_id) REFERENCES ports (id); +ALTER TABLE mto_service_items ADD COLUMN IF NOT EXISTS pod_location_id uuid; +ALTER TABLE mto_service_items ADD CONSTRAINT fk_pod_location_id FOREIGN KEY (pod_location_id) REFERENCES ports (id); +COMMENT ON COLUMN mto_service_items.poe_location_id IS 'Stores the POE location id for port of embarkation'; +COMMENT ON COLUMN mto_service_items.pod_location_id IS 'Stores the POD location id for port of debarkation'; \ No newline at end of file diff --git a/migrations/app/schema/20241023184350_create_port_locations_table.up.sql b/migrations/app/schema/20241023184350_create_port_locations_table.up.sql new file mode 100644 index 00000000000..459bb1ccfe4 --- /dev/null +++ b/migrations/app/schema/20241023184350_create_port_locations_table.up.sql @@ -0,0 +1,22 @@ +CREATE TABLE IF NOT EXISTS port_locations ( + id uuid NOT NULL, + port_id uuid NOT NULL + CONSTRAINT fk_port_id_port REFERENCES ports (id), + cities_id uuid NOT NULL + CONSTRAINT fk_cities_id_re_cities REFERENCES re_cities (id), + us_post_region_cities_id uuid NOT NULL + CONSTRAINT fk_us_post_region_cities_id_us_post_region_cities REFERENCES us_post_region_cities (id), + country_id uuid NOT NULL + CONSTRAINT fk_country_id_re_countries REFERENCES re_countries (id), + is_active bool DEFAULT TRUE, + created_at timestamp NOT NULL DEFAULT NOW(), + updated_at timestamp NOT NULL DEFAULT NOW(), + CONSTRAINT port_locations_pkey PRIMARY KEY(id) +); + +COMMENT ON TABLE port_locations IS 'Stores the port location information'; +COMMENT ON COLUMN port_locations.port_id IS 'The ID for the port code references port'; +COMMENT ON COLUMN port_locations.cities_id IS 'The ID of the city'; +COMMENT ON COLUMN port_locations.us_post_region_cities_id IS 'The ID of the us postal regional city'; +COMMENT ON COLUMN port_locations.country_id IS 'The ID for the country'; +COMMENT ON COLUMN port_locations.is_active IS 'Bool for the active flag'; diff --git a/migrations/app/schema/20241023184437_insert_ports.up.sql b/migrations/app/schema/20241023184437_insert_ports.up.sql new file mode 100644 index 00000000000..73dd72bcfa6 --- /dev/null +++ b/migrations/app/schema/20241023184437_insert_ports.up.sql @@ -0,0 +1,469 @@ +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('debafac3-8d1a-4668-9b20-5613cbe74b07','0408','SALEM, MA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('ba01ad8e-aa1d-43ee-8582-1e0661f3bb1d','1816','PORT CANAVERAL, FL','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('bb4c817c-4a7d-4579-af8b-d73ab3eaad09','1801','TAMPA, FL','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('3b938416-5f21-4e60-a6c3-01f0327721e1','2812','RICHMOND,CA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('ac22ed1e-a574-4e2a-9890-21cf8e5974a0','2815','CROCKETT,CA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('c1d25479-2af1-4f1c-b403-0164e26d0545','2820','MARTINEZ,CA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('f0fca568-2e24-420e-96d4-c8aaab87c3c5','2821','REDWOOD CITY,CA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('35079ae2-90c3-416d-ad31-62dbc9cd9782','2827','SELBY,CA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('f4a0df68-7c87-4361-9079-0d073c042718','2828','SAN JUAQUIN RIVER,CA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('6fbf239e-74ea-4d65-a343-8737fcec7f48','2829','SAN PABLO BAY,CA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('a6de781b-63d2-4a9f-9b9b-8cfb9ac04d0c','2830','CARQUINEZ STRAIT,CA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('78070656-9ad9-4a89-bb66-a5d16b73686e','2833','RENO, NV','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('0a32cd17-243b-401b-8b14-3bf2b8766a07','2834','SAN JOSE INTL AIRPORT','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('892dd23b-0e2d-4c34-9533-716c741b1d1c','2835','SACRAMENTO INTL AIRPORT','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('1a57fad1-df52-4f24-880a-57977e4674a1','2895','FEDERAL EXPRESS OAKLAND,CA','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('4c1c339d-f833-4309-93f8-ccf4a803ceff','2901','ASTORIA, OR','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('3a0e4de7-502b-44cf-ba74-5d9adce35f09','2903','COOS BAY, OR','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('ae0667e0-b497-4dd3-bfb4-a20f457104d1','2904','PORTLAND, OR','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('80845880-8073-4076-9bed-e5bbcfd1734d','5201','MIAMI, FL','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('12f955f2-8c72-4e20-a130-d061d9502f3a','2905','LONGVIEW, WA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('fa42e79a-5430-4908-b457-9203111b4b46','2907','BOISE, IDAHO','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('5b61e267-b4dd-46af-839d-f523eb838f1b','2908','VANCOUVER, WA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('a22b04d2-986d-4855-b2c0-5124b1aef29d','2909','KALAMA, WA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('c2119a8c-2b5a-458d-8d03-c48b19f68c8d','2910','PORTLAND INTL AIRPORT, OR','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('e21ce23a-c261-4b54-a711-6dee965bcfe9','3001','SEATTLE, WA','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('27ce681f-6dd6-45fd-b3ab-9a5b459b814b','3002','TACOMA, WA','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('206bae92-4c8d-4694-939b-42ce21ce85a3','3003','ABERDEEN, WA','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('1d310248-5aa5-4c0c-a3ea-d70aa92e9d96','3004','BLAINE, WA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('68eb5cee-3138-4691-b3af-b5823bdfb5b5','3005','BELLINGHAM, WA','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('4fff1f3e-4304-4913-9a3e-83eb1ae6fad8','3006','EVERETT, WA','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('156cbd9e-1a25-4249-b1c4-73f020041cd9','3007','PORT ANGELES, WA','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('114dcccb-4bf6-42eb-87aa-5b123c70266e','3008','PORT TOWNSEND, WA','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('9e803bdb-7604-41c6-8003-7dd7741794c9','3009','SUMAS, WA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('f0967bec-8825-4077-80be-9167c7323255','3010','ANACORTES, WA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('1a46c8b9-a417-46a8-b29d-a4c1a220aef5','3011','NIGHTHAWK, WA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('54b7f668-0983-4736-8359-e6370eaf6416','3012','DANVILLE, WA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('85f2d0f4-84bc-4e5f-8b03-ca3df2452a38','3013','FERRY, WA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('f477b5fc-e964-4f4d-8e90-390298038038','3014','FRIDAY HARBOR, WA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('a141e369-3f1e-49d9-86ff-fabb8524c79e','3016','LAURIER, WA','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('01c6a8a5-7604-400a-81f7-26002b869de1','3017','POINT ROBERTS, WA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('0c0192f6-aa3c-4b2b-b916-87a7323fcfe6','3018','KENMORE AIR HARBOR, WA','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('50c4360c-aa82-4b87-a792-8439141b23ed','3019','OROVILLE, WA','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('7da9bc40-ee7e-42fc-9ec5-30f68a1ab014','3020','FRONTIER, WA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('4705a2cb-8dec-4cb1-aa28-1c8df968a8a5','3022','SPOKANE, WA','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('4730f6aa-4f70-4ea8-ac98-97d1669a8cb9','3023','LYNDEN, WA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('2f2e469c-8f46-407b-b49f-f51593baa3f2','3025','METALINE FALLS','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('444090e9-9ea6-4deb-9f36-52e4883db612','3026','OLYMPIA, WA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('91807e18-f903-4701-b067-5f52cf8af8ba','3029','SEATTLE-TACOMA INTL ARPT','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('39735404-b04c-4d53-8b68-dd50352266e1','3071','UPS, SEATTLE, WA','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('693fa3c0-252f-4329-a1b9-4cbaf0d67b93','3072','AVION BROKERS @ SEATAC','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('d5c75d07-2efa-4c71-b2b0-a9d2b064a312','3073','DHL, SEATTLE, WA','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('d13aab4e-036c-400d-9b30-2282989d2e0c','3074','AIRBORNE EXPRESS @SEATAC','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('9500f5db-1ff7-4c62-8b1f-fdadf1a85ffe','3082','GRANT COUNTY AIRPORT, WA','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('ac5d7896-87ed-4fc5-9aad-ac74020d07c6','3095','UPS, SEATTLE, WA','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('3bcf1fda-ee1e-4bab-9ecd-7fd3d3930319','3101','JUNEAU, AK','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('ca4b0cae-9e85-45d7-8cb1-8907a3983133','3102','KETCHIKAN, AK','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('8945a53e-01b7-40b6-8fe8-29bd06939a20','3103','SKAGWAY, AK','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('ea7dbd4f-c044-42bc-9d30-5a39f526779e','3104','ALCAN, AK','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('f89f2928-7ce0-4a09-bad6-33e5a0727a73','3202','HIL0, HI','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('6cd19688-d295-4df4-9f0f-a1af02691bb9','3105','WRANGELL, AK','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('4e792d19-fe7c-4f4a-ae6d-4370cf39547c','3106','DALTON CACHE, AK','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('bd61ced4-667d-4986-9685-9439e6d2d2d5','3111','FAIRBANKS, AK','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('66f60678-b6ef-44ae-902d-50b25d7f933d','3115','SITKA, AK','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('3dd41a74-8e67-4892-a144-7a323b0db0a6','3126','ANCHORAGE, AK','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('c3504cb5-f38f-41c1-b537-0dd830e451d4','3195','FEDERAL EXPRESS ANCHORAGE, AK','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('e7ba6985-36b7-41a3-af42-282eeffd8f05','3196','UPS, ANCHORAGE, AK','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('9b8b41b8-a4bb-4e73-bfe5-57a3fc9d8b22','3201','HONOLULU, HI','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('933d46e2-bcad-453c-8f9a-9bfde380b49d','3203','KAHULUI, HI','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('80535c80-e13c-47ce-8133-f35d33730492','3204','NAWILIWILI-PORT ALLEN, HI','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('8c555ee4-f4d0-464e-bd9e-9475146ccbdf','3205','HONOLULU INTL AIRPRT, HI','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('c7b20f10-4214-460a-b26e-fdabc6dc5013','3302','EASTPORT, ID','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('d9600304-013b-422c-becc-5820786f564f','1809','ORLANDO-SANFORD AIRPORT,FL','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('a1651e54-ce25-4a32-9e0c-8e81f2882a6a','3303','SALT LAKE CITY, UT','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('1ce8d981-9435-4067-93b9-0e0807c50b42','3304','GREAT FALLS, MT','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('0f922afd-90a1-40dc-b5ea-6df04ba25fee','3305','BUTTE, MT','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('37def34b-dac0-41a5-ba9b-a682f716893a','3306','TURNER, MT','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('a5cf3c7a-ba85-4a8d-881d-077c11cc2931','3307','DENVER, CO','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('e94cac89-3583-4bb9-ae85-d2fb7a1bcef8','3308','PORTHILL, ID','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('caa55592-241f-45e2-9794-c7588a99bfe7','3309','SCOBY, MT','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('fb28bf30-0b30-4cf5-ba1c-29a338ec5ad0','3310','SWEETGRASS, MT','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('47a1e0d6-8c65-46af-9b39-612744887dc9','3316','PIEGAN, MT','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('804c279b-c2dc-4f3f-b10c-196c622ff3f3','3318','ROOSVILLE, MT','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('eebe3df8-8505-4156-839c-308ec98c62ef','3319','MORGAN, MT','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('d0ea3346-de66-4d78-9068-2acccb723e80','3321','WHITLASH, MT','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('3d296d41-4ca0-4b7f-95c6-bf9d5914ad3e','3322','DEL BONITA, MT','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('5673024d-57c5-468b-b975-2b2f36c14ac0','3323','WILDHORSE, MT','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('e841cd6d-be88-4891-8488-d08701987e26','3324','KALISPELL AIRPORT, MT','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('cdc73674-6b3b-4274-b088-8535850a8849','3325','WILLOW CREEK, HAVRE, MT','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('fa5a1354-82ae-4082-a307-e53d4a6e56a2','3384','ARAPAHOE COUNTY PUBLICAIRPORT, CO','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('0383789a-9aa1-45cb-bdef-187ac52f4c4c','3385','EAGLE COUNTY REGIONALAIRPORT','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('8e303029-bf17-4d7a-8450-e4e50deba651','3386','BOZEMAN YELLOWSTONEUSER FEE AIRPORT','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('23730db9-f409-40c7-99c7-d5f319433d71','3401','PEMBINA, ND','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('1fed7398-7bae-479f-b19d-d5e028a667c3','3403','PORTAL, ND','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('b10e3b3a-54db-4a63-9676-d289130d2cf1','3404','NECHE, ND','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('626725e0-5ae3-444b-9f0e-6ce2f6bfe244','3405','ST JOHN, ND','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('d6b4abc7-7224-4c0d-9307-aa79c62493e0','3406','NORTHGATE, ND','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('89739a64-0890-4206-8acd-1d81e0ed2a73','3407','WALHALLA, ND','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('d9f56c96-44bb-49e3-94a7-b03fbb73eacf','3408','HANNAH, ND','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('fba4d3e1-40e5-4c09-b096-370592217b94','3409','SARLES, ND','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('0040d4f7-95bc-413a-b671-d4aff893640e','3410','AMBROSE, ND','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('ac29149c-6faf-4c13-a076-6111200f60d2','3411','FARGO, ND','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('fa9b0db5-c348-430a-af4f-f6d9f486adf2','3413','ANTLER, ND','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('56579460-ba5a-42ee-85a1-50f988452d94','3414','SHERWOOD, ND','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('b70c49b3-c1fc-404a-b35d-756eacab970a','3415','HANSBORO, ND','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('b7dc6606-782c-458c-a4f5-320aff5903ba','3416','MAIDA, ND','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('6afbde12-d63f-4704-bc59-13643962bd93','3417','FORTUNA, ND','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('ba5dfbef-3590-4c7a-bd99-c7adb631d3ff','3419','WESTHOPE, ND','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('cec31ce2-de1b-4350-8048-27d268907a58','3420','NOONAN, ND','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('8834e9ab-79e6-4f7e-b0f0-28dc018267c3','3421','CARBURY, ND','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('15d918b1-0f2e-4278-8c64-03f5993e3998','3422','DUNSEITH, ND','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('c9ea72f9-4c3a-44de-89a0-fc5ca509b08a','3423','WARROAD, MN','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('c376e180-d90b-4e1d-a38b-d9459919d17f','3424','BAUDETTE, MN','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('76ffccbb-f3f5-4ccd-a2b0-78fd9a10366b','3425','PINECREEK, MN','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('8e8d2a04-10c3-4eeb-941b-f9b41e3fa67b','3426','ROSEAU, MN','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('1da22579-cbe3-4feb-a868-b58ede1ae734','3427','GRAND FORKS, ND','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('680e6d55-21ea-4811-b75f-6bc32408553d','3430','LANCASTER, MN','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('4ffb9320-10b5-4fcb-8895-93985f5ee529','3433','WILLISTON AIRPORT, ND','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('b3d9fa59-4507-4671-b7ea-f3319c52c8c1','3434','MINOT AIRPORT, ND','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('69f9d2dd-67e1-44b6-9619-4be3246e8179','3501','MINNEAPOLIS-ST. PAUL, MN','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('39f48160-441b-46e6-b76d-cda04539d075','3502','SOUIX FALLS, SD','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('1d34e1ea-306c-49e7-9072-fe446a56ac9f','3510','DULUTH, MN – SUPERIOR, WI','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('c270c683-07dd-4cd4-9bb8-c43e5effb7bf','3511','ASHLAND, WI','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('d47d2415-23d6-49ed-8950-1b025a26fd3a','3513','DES MOINES, IA','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('0aba113b-8689-442f-9849-ae4f0a3e904e','3581','USER FEE AIRPORT, MN','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('a3910b88-7c6c-4138-a244-ad087b3c7d4b','3613','GRAND PORTAGE, MN','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('404d862c-8cfc-45a8-8ebe-1da300a5c0b0','3701','MILWAUKEE, WI','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('98538e63-669c-4a2b-b34a-a49ce05780b2','3702','MARINETTE, WI','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('e8b0e6cb-604a-4587-bdd3-cb5e0f3216f6','3703','GREEN BAY, WI','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('5179e216-b09a-4ad9-bd71-e367bb67409b','3706','MANITOWOC, WI','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('cbede9da-5842-4b1f-bebc-900f8ec26cc4','3708','RACINE, WI','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('38ff3683-3a8d-4bff-bd73-311184531579','3801','DETROIT, MI','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('382c55c1-4559-49c3-a627-89d4a292a01e','3802','PORT HURON, MI','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('e9dc191f-d82c-450a-8dee-7d82d6c9897c','3803','SAULT STE. MARIE, MI','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('e88fd925-2cd0-4919-8430-8f26ac79e3d4','3804','SAGINAW/BAY CITY, MI','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('b5bf2e49-c79c-4245-a951-738eec44f457','3805','BATTLE CREEK, MI','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('dbbed831-e8ee-410d-93ed-a97bd911ad3f','3806','GRAND RAPIDS, MI','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('8e3f44b2-9a34-445a-9504-cc6419cef5ce','3807','DETROIT METROPOLITANAIRPORT','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('e892f1db-9617-4d03-b995-aed8c1de42e2','3808','ESCANABA, MI','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('96ed8089-3662-4cf1-bbf0-1c9c7467a261','3809','MARQUETTE, MI','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('80443b2c-bf67-4cd6-bd95-912b35de797d','5203','PORT EVERGLADES, FL','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('c8ab7d2e-faea-4019-9510-6dcca180c00b','1001','NEW YORK, NY','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('dba59c24-d739-4740-991d-e9b8b95f3521','1002','ALBANY, NY','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('15a5215d-4e1e-4f51-af79-b317cd237c23','3301','RAYMOND, MT','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('f07ab52e-0750-4a40-afee-712d9675b7b2','4197','DHL CINCINNATI, OH','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('4ebbe1a4-f793-41ab-85a1-5e44605144ca','4198','FEDERAL EXPRESS INDIANAPOLIS, IN','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('f92ef109-9b7e-4ef1-bf3b-13ba64e781e3','4501','KANSAS CITY, MO','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('2a4d2424-33ee-423e-aef6-c8c7b8bb007b','4502','ST JOSEPH, MO','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('177b96b7-eb65-4a33-9f4b-9786fa34d0d8','4503','ST LOUIS, MO','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('1227a92c-3cbc-41fb-b338-eb0e989c1851','4504','WICHITA, KS','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('36caeeda-05bf-4cf3-91a9-08c66dc8733b','4505','SPRINGFIELD, MO','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('199f2baf-f980-47cc-acc2-f92c6889921e','4581','MIDAMERICAN AIRPORTMASCOUTAH, IL','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('ca4f2b1a-f995-483c-ae03-c9438d62b896','4601','NEWARK, NJ','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('8b2e3b18-c435-49f4-81bb-538711eb29ef','4602','PERTH AMBOY, NJ','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('43d6e736-b3f8-4ac6-a069-cd4e2e71f3e7','1818','PANAMA CITY, FL','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('478a9e2a-6d08-4f5a-b569-7e5ef8d082d0','1819','PENSACOLA, FL','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('d1b2293b-4c0a-4e99-aa9b-98519b68dea6','1821','PORT MANATEE, FL','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('8d638d75-00e4-4a06-8b79-cc5cf96f7ece','1822','FORT MYERS AIRPORT','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('26eeb421-f33d-4b27-897a-e9ff58f0162c','1880','NAPLES MUNICIPAL USER FEEAIRPORT','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('a2ea45a3-5d15-4b8d-af52-c02be75a0904','1883','SARASOTA BRADENTON AIRPT','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('459f6991-9bfd-4056-b2b1-ef6db7e43674','1884','DAYTONA BEACH INT''L AIRP','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('040f6a3c-a762-4db5-9e2c-d272253737bf','1885','MELBOURNE REGIONALAIRPORT, FL','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('de816627-e75f-404f-9e6d-c250840ff133','1887','LEESBURG REGIONAL AIRPORT,FL','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('c9e7ee98-11bf-4776-9c4b-3d6f8b6798e9','3317','OPHEIM, MT','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('fbb6806f-b76a-4871-bdf3-a7ed28c880cd','1888','ORLANDO EXECUTIVEAIRPORT, FL','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('9542b332-d7d6-4009-abac-c2e91590f63c','1889','St. AUGUSTINE AIPRORT, FL','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('3d7f53dd-042d-48e1-90bd-eb387c7c6e0a','1901','MOBILE, AL','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('1feb17cf-1725-4146-9224-c31c23bf2301','1902','GULFPORT, MS','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('ccb7ee6e-c5d8-4325-9609-300561ba9b28','1903','PASCAGOULA, MS','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('a45e827a-42db-4c7f-b6c6-598b5d62ae1c','1904','BIRMINGHAM, AL','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('61bf9c9a-fdfe-476a-b898-35d96ca457ee','1910','HUNTSVILLE, AL','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('5776a2cf-4f82-46d2-ac2c-b7661437e6d1','4670','UPS, NEWARK, NJ','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('e1479f98-2c1d-477b-9840-c86ec404ba79','4671','FEDERAL EXPRESS, NEWARK,NJ','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('bb5a1ead-cacc-40b0-8605-eac724f06469','4681','MORRISTOWN AIRPORT, NJ','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('493c317f-0718-40d3-a2f4-f3bc99645f52','4701','JOHN F KENNEDY AIRPORT, NY','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('61969615-f985-4121-9489-fdc32fbdac7d','4771','NYACC','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('e4eab5aa-69e4-4c11-8503-1c27b6605366','4772','DHL, JAMAICA, NY','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('fd812269-f29a-4e83-970c-4e6d976126c1','4773','MICOM, NY','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('dbe46591-24f3-4dcc-bc64-da6990248449','4774','AIR FRANCE (MACH PLUS), NY','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('bdc34a85-d6cf-4073-858e-65a997aeb59c','4778','TNT SKYPAK, NY','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('e43ab7d0-6162-42e0-96a4-d7795841871a','4901','AGUADILLA, PR','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('e036663f-42b5-4ca0-b259-c7bda8caf16c','4904','FAJARDO, PR','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('e702a932-9e7d-46a1-baa9-82be5017e966','4907','MAYAGUEZ, PR','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('d2ce7a2c-cedf-4648-9f0c-278da1854f2d','4908','PONCE, PR','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('e9bf96ee-a812-4c3e-af08-801cfc091315','4909','SAN JUAN, PR','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('18021eac-2ff1-4490-b4b5-ae41d12e8bb8','4913','INTL AIRPORT, PR','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('43bde4bb-0270-4938-b45b-3ef08a7e224f','5101','CHARLOTTE AMALIE, VI','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('697f5da5-67b8-4a03-afbe-66cc12187a99','5102','CRUZ BAY, VI','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('972a4340-e0d6-489c-94bd-573365b45b14','5402','ALEXANDRIA, VA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('d8fc6382-5be5-4d42-b9ee-644f4b34bb36','5501','DALLAS/FT. WORTH, TX','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('f47d268a-ecbc-45af-8412-3703380d8bbf','5502','AMARILLO, TX','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('7bef6bcd-e0ff-49cd-9821-f71fc78d6b54','5503','LUBBOCK, TX','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('33823c32-1a4f-49db-ac18-b87900be7a1a','5504','OKLAHOMA CITY, OK','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('ba99bd26-8c65-4f80-abff-263860924f0c','5505','TULSA, OK','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('c63b8aad-91c8-4569-ad2a-f011bb2b7ff3','5506','AUSTIN, TX','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('d55f88e9-7d74-4d02-8a15-39f253a732f8','5507','SAN ANTONIO, TX','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('57c4f8ab-ab95-4acf-99fb-2475e3ab3ea4','5582','MIDLAND INTER AIRPORT, TX','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('cf0bb3b4-ec69-432c-aa7c-fa954a48c960','5583','FORTH WORTH ALLIANCEAIRPORT, TX','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('3a94a547-8e33-4c88-b5b4-47961e9ae7d4','5584','ADDISON AIRPORT, TX','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('8bc1ef6c-67fe-4b72-a071-dd5325f12379','8000','U.S. MAIL EXPORTS','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('3497e68a-76cc-4fb2-88ad-09c3fb9532a6','2001','MORGAN CITY, LA','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('ca098e09-f732-41dc-9c51-83031b45b4a0','2002','NEW ORLEANS, LA','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('e0c69818-2f55-4040-afaf-aa215d05a6a3','2003','LITTLE ROCK, AR','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('0fa85d77-0a3a-416a-83ce-f6ca954c19fb','2004','BATON ROUGE, LA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('20b8f1da-fa73-4ce1-9cd4-c14aa8af7dc6','2006','MEMPHIS, TN','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('5f03cca8-656a-46d4-b6a0-6dd48a73a0d2','2007','NASHVILLE, TN','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('4ef6a3eb-2ec0-4f74-af78-e6eb17adba93','2008','CHATTANOOGA, TN','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('aae4438d-1d85-47b6-8aaa-a088aafa7925','2010','GRAMERCY, LA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('0998aaf4-8ace-423c-949b-ed87fc8ee320','2011','GREENVILLE, MS','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('355c0ea6-5807-412a-bef2-22d85d75e818','2015','VICKSBURG, MS','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('7f97c302-87fc-48b4-86d2-71d309427de4','2016','KNOXVILLE, TN','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('c4bb487f-cfef-4e8c-a72b-580554f5b91d','2017','LAKE CHARLES, LA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('009a9c9c-6e2d-4a79-bdc8-557b21a7db4e','2018','SHREVEPORT/BOSSIER CITY','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('2dd7100a-1ffa-43a7-abf6-9e1a759ebe18','2027','TRI-CITY AIRPORT, TN','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('4c81c3a4-a54a-41eb-970c-e2d6785ac8b6','2095','FEDERAL EXPRESS MEMPHIS,TN','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('e6c6489b-56b1-4fe2-a17b-3376d210626f','2101','PORT ARTHUR, TX','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('59ba66fe-5a8f-456b-8ee0-b53f79e0ba0c','2102','SABINE, TX','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('35a1862e-76ab-43a8-b473-1b27b69168d9','2103','ORANGE, TX','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('13a1d5ad-0435-45e2-9fce-e7224e208da0','2104','BEAUMONT, TX','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('f642ac8d-22d1-4ff7-8dfb-2034dae38bd7','2301','BROWNSVILLE, TX','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('ac9d1a37-b865-4e38-a0cd-6bcce1b73531','2302','DEL RIO, TX','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('67e6edfd-2dd5-4edd-bbfe-c35e0fa8818b','2303','EAGLE PASS, TX','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('17dc4732-ebb7-40db-abfc-ea26f89d0b01','2304','LAREDO, TX','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('3a227de1-dfc4-4f8c-85f1-27890ecec82f','2305','HIDALGO,TX','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('3782a36a-1e56-4489-8126-7dacc3584622','2307','RIO GRANDE CITY, TX','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('b2544a9b-1cbc-4657-8aa7-7611ed76b176','2309','PROGRESO, TX','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('eeaf303b-42ca-44d4-bd10-f0bd9ded64d1','2310','ROMA, TX','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('30bc5f59-4440-4702-b689-88dc9ed45ac3','2383','VALLEY INTL AIRPORTHARLINGEN, TX','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('d10eaf03-0e25-479a-88ab-7fc15932e58e','2407','ALBUQUERQUE, NM','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('cb9f4ecb-53b4-4f61-8bd6-ef13b6e24e58','2408','SANTA TERESA, NM','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('0f48f70c-9164-427b-95a9-7c6b6801f852','2481','SANTA TERESA AIRPORT, NM','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('9d2514e8-7c7b-4473-9f33-745340645301','2501','SAN DIEGO, CA','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('9cbc37fe-d23f-4c9e-904e-2b0fa2be5868','2502','ANDRADE, CA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('9b531cfb-115e-4f2b-a45e-c3eee6e228b9','2503','CALEXICO, CA','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('eea01110-5e86-4b55-8eac-4b4aa087fbad','3206','KAILUA-KONA, HI','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('dc34caca-c104-48cd-abe1-1e891936f7d2','5204','WEST PALM BEACH, FL','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('fa62598c-eba5-45f2-b7bc-3848f759f490','5205','FORT PIERCE, FL','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('46c997b5-e831-4f19-ade5-f4d2b8c5c9e8','5206','MIAMI INTL AIRPORT, FL','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('3550e164-9fed-4683-a71f-0b2a9d6b7f00','5210','FT. LAUDERDALE-HOLLYWOODINTL','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('eb5f67fb-d94d-4c03-bb41-85b9c6356c81','5295','UPS, MIAMI INTL AIRPORT, FL','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('46d10b7c-fb5a-4959-aab1-9a1a1351dfb5','0402','SPRINGFIELD, MA','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('9558e2a0-baf9-4383-8980-286be42ecf35','0403','WORCESTER, MA','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('39128bd2-894f-484c-93d1-16530c902fe7','0404','GLOUCESTER, MA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('beba74f0-e6e1-49e4-afa9-4b522a43b486','0405','NEW BEDFORD, MA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('902a7379-587a-46d5-bfdb-2d7e6611e325','0406','PLYMOUTH, MA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('06b6ad8d-7916-41a9-857b-afc967aa0ed3','0407','FALL RIVER, MA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('b29762b3-02c6-4184-88ef-340658468c82','0410','BRIDGEPORT, CT','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('d354f39d-2743-4fc6-8790-a1b00a827040','0411','HARTFORD, CT','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('0ef5cc9b-da85-48c7-916e-ef3dc6b42c08','0412','NEW HAVEN, CT','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('2e494961-6889-49fd-9a16-977fdb75e927','0413','NEW LONDON, CT','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('9d611f73-7554-44f6-b43b-03e658d7d599','0416','LAWRENCE, MA','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('67038763-2a0a-4ae3-86eb-d1098b93c74f','0417','LOGAN AIRPORT, MA','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('cf1b1110-1f78-4f67-bc56-d486954284cd','0481','L.G. HANSCOM FIELD BEDFIELD, MA','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('2c0916ca-8de3-47c4-b1ea-7a798593de21','0501','NEWPORT, RI','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('631eb519-7987-46c0-a3b6-2e871f4ccc27','0502','PROVIDENCE, RI','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('c6af1dda-0142-4000-a15f-1c4b4a9ba118','0503','MELLVILLE, RI','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('e81f584b-70c6-4ef3-8841-708fd2ac6df9','0701','OGDENSBURG, NY','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('3d6a3aa6-699c-438d-a299-5c544d606b78','0704','MASSENA, NY','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('ff85e2d9-c370-4466-b1f6-ea37009d6661','0706','CAPE VINCENT, NY','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('066334d6-bc24-4277-90c9-27489c8deb9f','0708','ALEXANDRIA BAY, NY','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('6b636d9f-6fb5-4a12-82d7-6da1cb098fd3','0712','CHAMPLAIN-ROUSES POINT, NY','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('b3906781-28c6-47eb-ae7d-b622247c44a6','0714','CLAYTON, NY','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('ef2270d1-985f-4b8d-8a08-705c4a8f9c15','0715','TROUT RIVER, NY','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('6f35f55c-9961-4900-b097-03c7045d7ba2','0901','BUFFALO-NIAGARA FALLS NY','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('e78b82e6-4715-457e-bfde-4dfa038d798e','0903','ROCHESTER, NY','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('282ca5ea-9d0b-4c7d-ab9c-0203ee8218f9','0904','OSWEGO, NY','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('ac62716f-fd44-4f51-8d04-1d57dc549b8a','0905','SODUS POINT, NY','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('a9f9f6bb-a166-452d-b34f-6ef031b1eb26','5296','DHL WORLDWIDE EXPRESS MIAMI, FL','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('404337f6-6895-4430-a8fa-6f72a1507b7c','5297','FEDERAL EXPRESS, MIAMI, FL','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('d193ff05-c599-474c-b373-1a4dad97783e','5298','IBC COURIER HUB, MIAMI, FL','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('d7a11a7b-5763-4b9e-b569-ee3952e894c2','5301','HOUSTON, TX','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('2872555d-7164-4c1a-b5af-f737d9f0516f','5306','TEXAS CITY, TX','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('2292722b-09e9-44e2-80ba-3f49f09d455a','5309','HOUSTON INTERCONTL, TX','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('0737ad44-7cbd-49c4-92e8-e560a0f61143','5310','GALVESTON, TX','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('b60dea6c-e276-475b-8e2a-1ef7ec31a2bd','5311','FREEPORT, TX','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('2a9cb54e-671e-4faf-b4a9-03e1639739e0','5312','CORPUS CHRISTI, TX','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('bee08fcf-812c-4d25-9d4d-cad13f5c1f8f','5313','PORT LAVACA, TX','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('618c43ba-01e7-4b7f-bfac-474702df009b','5381','SUGAR LAND REGIONAL AIRPORT, TX','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('73efadd1-4a83-429e-a625-e96b69e9df11','5401','WASHINGTON, DC','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('12861260-e3f2-49ed-85e0-7e7b4c7b27c2','0102','BANGOR, ME','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('38641157-ac28-413e-8455-b60314d17f18','0103','EASTPORT, ME','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('a6c1b6e2-5227-420c-b9c7-d89f37560b58','0104','JACKMAN, ME','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('77768c81-ac61-4eb7-b42e-aea7ee3873e3','0105','VANCEBORO, ME','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('f23a9428-cb6c-4e00-bb5d-b542814df952','0106','HOULTON, ME','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('f93701f8-3209-4cc6-9762-4b8f6ca1687b','0107','FORT FAIRFIELD, ME','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('e4ba8867-bf70-43c7-b143-0f719ed0121f','2902','NEWPORT, OR','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('73242b0e-5939-42d4-ae10-ba0bb8fdbe61','1805','FERNANDINA BEACH, FL','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('242aabb5-7fdd-4ac4-9297-7e314096db3f','1807','BOCA GRANDE, FL','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('d0af7236-9d2c-42b4-9d27-4091fb55abf7','1808','ORLANDO, FL','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('2bed2833-d653-4ad7-abac-36a6a91de50a','1814','ST. PETERSBURG, FL','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('65d15673-0d9c-4090-a9ae-2ae67e83cf6b','3604','INTERNATIONAL FALLS, MN','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('69c2d39d-36ea-4567-bd25-ccb84d64b0fc','3512','OMAHA, NE','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('4fe9a6fc-3f31-436c-85e2-d04a357bf664','0206','BEECHER FALLS, VT','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('4164581c-843b-4b55-9e00-92c3167854a5','0207','BURLINGTON, VT','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('8915e7b2-0ea1-4455-82d9-063cc9043b43','0209','DERBY LINE, VT','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('f4f596cc-9e1e-4277-ad79-9ab68ebfd1be','0212','HIGHGATE SPRINGS/ALBURG','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('a6b32eb3-98ef-4490-b0d8-5f5ada6d2465','0401','BOSTON, MA','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('7fc06536-e3f6-45f5-861b-dc0926d4f418','3015','BOUNDARY, WA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('ec40795f-5c5b-4e4d-866a-6c8b35a5ac25','3814','ALGONAC, MI','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('4ee802c3-dbd3-4763-a797-70dd84e2cefb','3815','MUSKEGON, MI','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('7c207a67-baab-4853-ade2-5e2cd4c3bd42','3816','GRAND HAVEN, MI','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('1ff2d060-9e4f-4ee8-a480-a2d57ec2d4f1','3818','ROGERS CITY, MI','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('a6e6c667-f103-4b06-9ae4-537acd93f0e4','3819','DETOUR, MI','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('37bb9790-d158-4d60-93fb-b92714b31d12','3842','PRESQUE ISLE, MI','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('f781bc24-81b6-441a-9b5e-77f24699c249','3843','ALPENA, MI','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('b69bd52a-1534-43dc-a186-7cbe1098a8ba','3881','OAKLAND/PONTIAC AIRPORT,MI','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('569931f7-53b2-4964-aade-5d8772a81958','3882','WILLOW RUN AIRPORT, MI','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('f1cfb95d-e773-4465-b08c-b7174adc959d','3883','CAPITAL REGION INTLAIRPORT, MI','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('d81a544f-4a9e-4a8e-b1c3-1238218375c0','3901','CHICAGO, IL','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('0e82cedb-e183-4afe-a740-1ba3dae15ef7','3902','PEORIA, IL','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('4ed3f5cd-0e5a-4985-a267-0497353fa166','3905','GARY, IN','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('50593cad-614f-4b73-9c23-23d2c0d76808','1602','GEORGETOWN, SC','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('b945bf9b-d69d-4708-ae20-47ba951dc373','1603','GREENVILLE-SPARTANBRG SC','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('c707e192-a221-41d9-8f58-ccdef9b482cc','1604','COLUMBIA, SC','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('fc76489a-d625-46e3-9eb8-35e2fa0f350d','1681','MYRTLE BEACH INT’L AIRPORT,SC','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('30b2f841-b7ed-4e40-a3ff-b9161185fb60','1701','BRUNSWICK, GA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('c8394cb2-28b9-4958-9228-45bb39482450','1703','SAVANNAH, GA','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('9f761471-fea2-4478-8cba-87ac978cf765','1704','ATLANTA, GA','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('fdbf3e13-d17a-4aed-9b56-2478994dc141','1803','JACKSONVILLE, FL','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('3d8ed551-acfe-440e-bd73-9eb860878324','2504','SAN YSIDRO, CA','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('cda7c027-1994-423b-bce4-a751de2f4764','2505','TECATE, CA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('e871ce70-9502-4659-8e35-73b4bd827421','2506','OTAY MESA, CA','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('34c62aad-3a15-4f0b-96a9-d87bc2b624ad','2507','CALEXICO-EAST, CA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('9b8c65f1-2f94-415a-b30a-507e556d7e86','2601','DOUGLAS, AZ','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('da6c6a02-c89d-405c-b451-7af69a34a895','2602','LUKEVILLE, AZ','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('02a402a6-1ca3-4c9f-a1d7-558be11bf2cb','2603','NACO, AZ','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('2e4c8fb5-bd18-4f69-a789-4e0281b66f01','2604','NOGALES, AZ','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('1b4dced2-b532-43ea-a8df-45418d960734','2605','PHOENIX, AZ','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('08307674-52cd-45c4-b4a7-af594f880a2f','2606','SASABE, AZ','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('9ae58721-2594-4b2f-b8a1-bb6ccca14053','2608','SAN LUIS, AZ','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('13b28305-bda0-4659-bf3b-3ea6b0184a0e','2609','TUCSON, AZ','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('1c07a716-ba71-433f-aefd-adaae30cfc92','2704','LOS ANGELES, CA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('252e640f-5c49-465e-ab90-d67929760a3e','2709','LONG BEACH, CA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('2e438932-b77c-438b-a25c-ff842d8fc590','2712','VENTURA, CA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('b301eaaa-6282-4371-97c0-eed058f5bddb','2713','PORT HUENEME, CA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('dcb295f6-7fb4-439a-94f5-b5bc26e78810','2715','CAPITAN, CA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('33aa9b2f-da98-4a3a-a38e-f3207c5b83c2','2719','MORRO BAY, CA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('d023f6cd-f2a4-437b-bc08-b3d79bb4e3d6','2720','LOS ANGELES INT ARPT, CA','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('80347a1b-fda2-4549-94a8-15d053053c64','2721','ONTARIO INTL AIRPORT, CA','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('9cc395bc-2f25-4dcf-a548-4e32969ab8f6','2722','LAS VEGAS, NV','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('cdd7e294-8c21-47ac-9249-225b195607a0','2401','PORT OF YSLETA, TX','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('58f8397a-5298-4362-aa11-c36e1130bef2','2402','EL PASO, TX','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('65d9f492-1b5b-4877-85b4-857ef0b0bc52','2403','PRESIDIO, TX','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('b79587fc-5d82-4c13-857e-b971de359bdd','2404','FABENS, TX','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('0878612d-d00e-4b8d-a4b8-d67f8ba28c1b','2406','COLUMBUS, NM','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('998af938-6801-4150-a302-2631f66d1ab9','0211','NORTON, VT','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('d7be0325-98f5-4b7e-9872-4192a4d1ea9d','1101','PHILADELPHIA, PA','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('12983a1f-663e-40d7-ba57-b404b9baaa73','1102','CHESTER, PA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('4f313453-0443-406d-b60d-0387a0df818e','1103','WILMINGTON, DE','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('b7080c0a-4dae-453d-be81-0c3aa6857d8d','1104','PITTSBURGH, PA','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('9eebdb57-e302-4c62-929e-e1a9adcf6a06','1106','WILKES-BARRE/SCRANTON PA','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('f42e99ed-89a9-45e7-992c-8fb95187abc8','1108','PHILA. INTL. AIRPORT, PA','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('0940573e-c15a-427c-b9ef-bb022fd2fc19','1109','HARRISBURG, PA','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('9d718c35-7277-4ad2-bc24-fd8b9a51261d','1113','GLOUCESTER CITY, NJ','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('a837d5b8-7339-4e77-9a3d-0972f0296237','1119','LEHIGHVALLEY INTL AIRPORT ALLENTOWN, PA','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('350241a6-0d48-4815-be45-7e01d5ca204e','3908','DAVENPORT-ROCK ISLAND, IL','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('d348e062-a6dc-4c12-a99d-b47a9da990f1','0906','SYRACUSE, NY','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('01c4ed98-eb5d-40b1-bd1a-74fa940dc8a1','1182','ATLANTIC CITY REGIONALAIRPORT, NJ','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('bfe383b3-ac04-4ba5-9b88-4a388d1348f2','1183','TRENTON/MERCER COUNTYAIRPORT, NJ','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('a1a09b96-d80c-46a7-ac9d-720b8b032cb5','1195','UPS, PHILADELPHIA, PA','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('1df3b29a-637b-4559-9e41-94bcee7e524f','1301','ANNAPOLIS, MD','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('ac0e637c-00ad-4b40-a114-777ae0e9f04d','1302','CAMBRIDGE, MD','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('959d39a3-504a-4701-b62c-2dff98bdf7eb','1303','BALTIMORE, MD','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('721ff6df-3b01-4086-a66d-bdac54254288','1304','CRISFIELD, MD','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('666fe186-655a-4011-8365-09c08032c5cf','1305','BWI AIRPORT','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('a48ab8b9-a97f-47d3-be21-2d0797e827fa','1401','NORFOLK-NEWPORT NEWS, VA','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('7f303781-5169-4252-8e27-d0245289a5ad','1404','RICHMOND-PETERSBURG, VA','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('645de3d1-48a2-4ce2-b022-8e38517fe126','1409','CHARLESTON, WV','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('8371f736-b588-44fd-9f0a-9e39a3b2902a','1410','FRONT ROYAL, VA','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('3c8967fb-a829-490e-a0da-0d48f3a6e721','1412','NEW RIVER VALLEY AIRPORT,VA','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('275e94a3-da6d-48e1-a913-7b240d30ae3a','1501','WILMINGTON, NC','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('88f2bdd2-a2ba-4799-acd7-882a7ca40c28','1502','WINSTON-SALEM, NC','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('a44d020e-393e-4e0e-ab76-5a1e624ad97a','1503','DURHAM, NC','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('fe70ca9c-7b97-4913-82da-2048af73a896','1511','BEAUFORT-MOREHEAD CTY,NC','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('75c0fde0-bc0a-4cc3-ba03-8eab2fd9190f','1512','CHARLOTTE, NC','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('976d9ff0-f94f-4e30-ac72-fe718ace736a','1601','CHARLESTON, SC','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('efbe12ae-5423-47d9-842b-75164cf18d30','0981','BINGHAMTON REGIONALAIRPORT, NY','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('f209600e-8173-4a25-bcba-8518bbc518b3','0983','ITHACA TOMPKINS INTL AIR ITHACA, NY','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('e49c58d4-c1ac-412d-b81a-a6833d0df98d','2775','TNT EXPRESS, LAX, CA','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('a53c4cb9-84ff-4c01-8ee2-005d9371078e','2776','IBC PACIFIC','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('74cf52c5-3cd7-4c94-9a33-735fbc22c022','2786','MEADOWS FIELD AIRPORT BAKERSFIELD, CA','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('f857864f-8914-4372-b8b1-e6ed19df47db','2791','DHL-HUB LOS ANGELES, CA','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('1985932e-31d6-4d65-8950-445eb9b9fa50','2795','UPS, ONTARIO, CA','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('fc04a024-c9f4-4595-a330-cb81c48f1903','2801','SAN FRANCISCO INTL AIRPT','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('d7577350-5c21-4090-820a-2784d64e6aec','2802','EUREKA, CA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('fd8d3f75-c12a-4c82-8f42-d24da411d94d','2803','FRESNO, CA','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('4deab209-d26b-49cc-a1c1-dc3a771c29fe','2805','MONTEREY, CA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('9d9f2a06-4624-4816-bdcd-5fe9226dbad1','2809','SAN FRANCISCO, CA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('6f021f02-49dc-458d-96ef-e6e783970772','2810','STOCKTON, CA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('c5964751-104a-4f86-b843-bac16668f287','2811','OAKLAND, CA','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('b205c048-842d-41c5-ade5-bf5670e3e8b1','3909','GREATER ROCKFORD AIRPORT,IL','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('7190d43a-0ea8-4655-8be7-f1d540ab412a','3910','CHICAGO MIDWAY INTL AIRPORT, IL','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('ec3ce0f8-9b3a-4e5a-823b-c0cf5d9dd58d','3971','TNT EXPRESS CONSIGNMENTCHICAGO, IL','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('d68a0bae-247f-42ea-b781-2ce3a9102d83','3983','CHICAGO EXECUTIVE AIRPORT','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('76f7bf05-06b9-4ac6-b9ad-82943731e84c','3987','UFA UI WILLARD AIRPORT SAVOY, IL','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('631c4035-c7bb-408a-a7f3-8437090a5768','4101','CLEVELAND, OH','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('95cb96fe-aacb-4c63-bbe0-a5429b6cd2ff','4102','CINCINNATI, OH','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('65922df0-1196-418b-bac5-3745f7ce7493','4103','COLUMBUS, OH','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('4ac10e23-333b-484e-a82a-95ce3aa57bb5','4104','DAYTON, OH','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('6d314359-666d-4310-a4dc-ede3cec5de6e','4105','TOLEDO, OH','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('b8bfdf45-db5a-4af1-82cd-8799fd485e7c','4106','ERIE, PA','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('2a96c6c2-7ed2-44a8-ab93-fdf92c634836','4110','INDIANAPOLIS, IN','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('c0196fa5-0ab4-4a7f-9e78-85e65e53e2df','4115','LOUISVILLE, KY','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('a4480c91-47a4-41e3-b988-8733cfc1a53b','4116','OWENSBORO, KY','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('60b160a8-d759-4777-aba2-5b7416738eac','0108','VAN BUREN, ME','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('22712492-02e9-4d3c-8819-07eecd62b3df','0109','MADAWASKA, ME','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('f0e24fdd-5944-47a3-b290-742691524c4d','0110','FORT KENT, ME','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('fe1077ea-b352-4116-83aa-a326cd334587','0111','BATH, ME','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('a35e861e-a85b-4179-b0fa-5ae6246a5733','0112','BAR HARBOR, ME','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('01fb1492-f6c7-40d5-9813-9801c9ccd29f','0115','CALAIS, ME','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('4ac739b6-2cc9-461a-a53a-216fa68d8b04','0118','LIMESTONE, ME','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('bdc5f29e-58a0-40f8-9190-fd09b8cdee46','0121','ROCKLAND, ME','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('6b4ded39-718e-4329-874e-261bcfd86cb1','0122','JONESPORT, ME','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('5b523aa4-0e53-4eba-af4a-12674bbbbdac','0127','BRIDGEWATER, ME','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('299319fd-7d7f-46ac-9d4d-cdf737639d48','0131','PORTSMOUTH, NH','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('d1bad8e7-2b38-4b69-b094-b3411ae19fac','0152','SEARSPORT, ME','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('e675ae08-5fbf-4830-bf08-7a3ee63a66a4','0182','MANCHESTER USER FEEAIRPORT, NH','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('a14534f6-196e-4a86-ab45-73a335e78c97','0201','ST. ALBANS, VT','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('ff08d620-4fc3-42c4-a19d-6a09dbc823e8','0203','RICHFORD, VT','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('2dc4563f-e5b6-4ddd-a859-f8cbdff2823d','4121','LORAIN, OH','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('f9702262-fc9b-41bf-af91-20c04975ee09','4122','ASHTABULA/CONNEAUT, OH','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); +INSERT INTO ports (id,port_code,port_name,port_type,created_at,updated_at) VALUES + ('7a2d1433-6603-49ec-a786-cdce1950d367','4183','FORT WAYNE AIRPORT, IN','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('6c018209-be7f-4a95-bbba-13e37d589b5a','4184','BLUE GRASS AIRPORT, KY','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('e270bccb-9e77-4d06-8d2e-30a16acb7989','4196','UPS, LOUISVILLE, KY','A','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('f4459d60-4139-4d29-bc26-defebe281f64','5104','CHRISTIANSTED, VI','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('36bfad37-665f-41e4-afcb-482a04dbd623','5105','FREDERIKSTED, VI','S','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'), + ('b4b4dc41-b2c8-4227-80f1-b8b5ab4fd18e','5202','KEY WEST, FL','B','2024-10-17 13:50:08.046274-05','2024-10-17 13:50:08.046274-05'); \ No newline at end of file diff --git a/migrations/app/schema/20241031163018_create_ub_allowances_table.up.sql b/migrations/app/schema/20241031163018_create_ub_allowances_table.up.sql new file mode 100644 index 00000000000..5bc6707860c --- /dev/null +++ b/migrations/app/schema/20241031163018_create_ub_allowances_table.up.sql @@ -0,0 +1,500 @@ +-- Create table to static UB allowances, used for calculating entitlements.ub_allowance for orders +CREATE TABLE IF NOT EXISTS ub_allowances ( + id uuid NOT NULL, + branch TEXT CHECK (branch in ('AIR_FORCE', 'ARMY', 'COAST_GUARD', 'MARINES', 'NAVY')), + grade TEXT CHECK (grade in ('E_1', 'E_2', 'E_3', 'E_4', 'E_5', 'E_6', 'E_7', 'E_8', 'E_9', 'O_1_ACADEMY_GRADUATE', 'O_2', 'O_3', 'O_4', 'O_5', 'O_6', 'O_7', 'O_8', 'O_9', 'O_10', 'W_1', 'W_2', 'W_3', 'W_4', 'W_5')), + orders_type TEXT CHECK (orders_type IN ('PERMANENT_CHANGE_OF_STATION', 'TEMPORARY_DUTY')), + dependents_authorized BOOLEAN, + accompanied_tour BOOLEAN, + ub_weight_allowance INTEGER CHECK (ub_weight_allowance >= 0 AND ub_weight_allowance <= 2000) +); + +COMMENT ON TABLE ub_allowances IS 'Base UB allowance values used to lookup and calculate entitlements.ub_allowance for orders'; +COMMENT ON COLUMN ub_allowances.branch IS 'Branch of service for service member'; +COMMENT ON COLUMN ub_allowances.grade IS 'Grade of service member'; +COMMENT ON COLUMN ub_allowances.orders_type IS 'Either PERMANENT_CHANGE_OF_STATION or TEMPORARY_DUTY order types'; +COMMENT ON COLUMN ub_allowances.dependents_authorized IS 'Depedents authorized on order'; +COMMENT ON COLUMN ub_allowances.accompanied_tour IS 'Is an accompanied tour'; +COMMENT ON COLUMN ub_allowances.ub_weight_allowance IS 'The base UB weight allowance for given values for branch, grade, orders_type, dependents_authorized, accompanied_tour.'; + + +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('8d934f2f-831a-44d9-a93a-20f1291f9d62', 'AIR_FORCE', 'E_1', 'PERMANENT_CHANGE_OF_STATION', true, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('0ff1f1ed-9f5a-4bbb-b992-5b5a1e956d48', 'AIR_FORCE', 'E_1', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('bc7ad884-aebc-4254-bb4a-92e1b1996899', 'AIR_FORCE', 'E_1', 'PERMANENT_CHANGE_OF_STATION', false, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('edc02ae1-e145-49b8-b0d3-98cba9b0421f', 'AIR_FORCE', 'E_1', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('de22b8de-d6c7-4df6-9ce4-50f1492613f3', 'AIR_FORCE', 'E_2', 'PERMANENT_CHANGE_OF_STATION', true, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('e9b9662d-e940-4a2a-b82c-7b3311cf07c3', 'AIR_FORCE', 'E_2', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('cba4dfe2-67bb-4f42-8388-600d7ea9f5e4', 'AIR_FORCE', 'E_2', 'PERMANENT_CHANGE_OF_STATION', false, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('785bdac4-0cdc-48c7-a062-6a3ca856b68a', 'AIR_FORCE', 'E_2', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('473c8f53-e971-45bd-91c4-6d9640c9618d', 'AIR_FORCE', 'E_3', 'PERMANENT_CHANGE_OF_STATION', true, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('7cb4847d-8a20-4b4b-9489-213eb7d76037', 'AIR_FORCE', 'E_3', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('0d8d1cf4-735c-4624-81da-599122d5cf2c', 'AIR_FORCE', 'E_3', 'PERMANENT_CHANGE_OF_STATION', false, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('aaa4c008-7b44-4dd1-a0cc-770b390a146c', 'AIR_FORCE', 'E_3', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('f08a932e-5f09-4f12-9b7d-85d3714783d1', 'AIR_FORCE', 'E_4', 'PERMANENT_CHANGE_OF_STATION', true, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('bcb2c34c-90dc-44d0-9885-3c2d44c82de7', 'AIR_FORCE', 'E_4', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('14d5bbf0-4d22-4c23-8713-994d328440d9', 'AIR_FORCE', 'E_4', 'PERMANENT_CHANGE_OF_STATION', false, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('ef91b384-41ba-4928-ab10-f0e0ea383b95', 'AIR_FORCE', 'E_4', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('95e1ab60-705a-4367-997c-2c2142d88e20', 'AIR_FORCE', 'E_5', 'PERMANENT_CHANGE_OF_STATION', true, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('3a93e9d3-bb92-47ed-9946-3aea937c09b5', 'AIR_FORCE', 'E_5', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('cf296351-41f3-46ec-8b45-fec6348799e6', 'AIR_FORCE', 'E_5', 'PERMANENT_CHANGE_OF_STATION', false, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('a379f431-628b-44dc-a854-232d44844791', 'AIR_FORCE', 'E_5', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('90b4f265-c228-4353-8586-77367293a027', 'AIR_FORCE', 'E_6', 'PERMANENT_CHANGE_OF_STATION', true, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('e14c210d-1579-4e92-86ab-d1c64a60195d', 'AIR_FORCE', 'E_6', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('db9d831b-4185-4e5e-9b61-6234e98fe262', 'AIR_FORCE', 'E_6', 'PERMANENT_CHANGE_OF_STATION', false, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('95b38e97-e63f-439a-9ce9-9d6cb0f5ccc7', 'AIR_FORCE', 'E_6', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('1e8030f1-55fa-4d73-becf-ecaa93e39618', 'AIR_FORCE', 'E_7', 'PERMANENT_CHANGE_OF_STATION', true, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('fbdabdbf-b939-4445-924d-30edbe031796', 'AIR_FORCE', 'E_7', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('a80f77c0-1667-4ee0-a2c7-a5036a0b8356', 'AIR_FORCE', 'E_7', 'PERMANENT_CHANGE_OF_STATION', false, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('7cc980ac-5348-4055-b37d-17b8daf27069', 'AIR_FORCE', 'E_7', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('f6207878-84a0-4820-b636-93e216494c99', 'AIR_FORCE', 'E_8', 'PERMANENT_CHANGE_OF_STATION', true, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('44b1be2c-2de3-4e7b-b3ad-56b9747fcaf1', 'AIR_FORCE', 'E_8', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('e4dbccd4-3b27-4e8e-af22-31017c54a232', 'AIR_FORCE', 'E_8', 'PERMANENT_CHANGE_OF_STATION', false, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('8f7b52cd-10e0-48b2-8b39-23273d6adb6b', 'AIR_FORCE', 'E_8', 'TEMPORARY_DUTY', false, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('1706d84c-800c-42ed-b4b3-183b0a8019c8', 'AIR_FORCE', 'E_9', 'PERMANENT_CHANGE_OF_STATION', true, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('10e88103-9d69-45d9-8079-1446a0afc18b', 'AIR_FORCE', 'E_9', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('66b3050f-f3ad-4abc-816d-7b96d7aab43f', 'AIR_FORCE', 'E_9', 'PERMANENT_CHANGE_OF_STATION', false, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('4f04c456-a33e-4ca7-9329-e79dd564234a', 'AIR_FORCE', 'E_9', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('b7b9f131-3219-4e31-866b-4b3818f3fd2a', 'AIR_FORCE', 'O_1_ACADEMY_GRADUATE', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('b5d74fd0-aa04-4651-ae87-ed2d75a44568', 'AIR_FORCE', 'O_1_ACADEMY_GRADUATE', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('97cd9119-961c-4b4f-a8a5-727256d79237', 'AIR_FORCE', 'O_1_ACADEMY_GRADUATE', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('6194c5d8-62ed-4a6a-aeff-3324e9d65a84', 'AIR_FORCE', 'O_1_ACADEMY_GRADUATE', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('ebfd191e-ffe8-4d7a-973e-829d24d0c61d', 'AIR_FORCE', 'O_10', 'PERMANENT_CHANGE_OF_STATION', true, false, 1000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('759e1c94-86e9-4358-8049-20d30738f29a', 'AIR_FORCE', 'O_10', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('e23c4e50-1b56-4ce8-ab71-a3de0e97297c', 'AIR_FORCE', 'O_10', 'PERMANENT_CHANGE_OF_STATION', false, false, 1000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('d4bcf745-4ed0-40ea-9dfe-68d43ac97cb4', 'AIR_FORCE', 'O_10', 'TEMPORARY_DUTY', false, false, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('3b61e91b-1e60-455f-baca-02136671754f', 'AIR_FORCE', 'O_2', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('a220f4be-f078-4cf5-8fe0-231aa18365f0', 'AIR_FORCE', 'O_2', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('cb5a8014-29b5-49ad-bb15-31d919ba6ba0', 'AIR_FORCE', 'O_2', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('e77a199f-a85f-4b03-93f3-74f534d95d1c', 'AIR_FORCE', 'O_2', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('36132327-6b46-4655-9c6c-4c0df1a230fb', 'AIR_FORCE', 'O_3', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('31f157cc-a60b-48b5-9924-abf528d04d32', 'AIR_FORCE', 'O_3', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('4e747fdc-7188-4a54-bf46-0bac0ca105a5', 'AIR_FORCE', 'O_3', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('c2499e11-d08e-4296-8705-330a4a32838b', 'AIR_FORCE', 'O_3', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('783d2b8d-dbc2-4e5d-91a7-dd3878040e98', 'AIR_FORCE', 'O_4', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('023ebee7-818f-46de-ae27-e25dbffa4aaf', 'AIR_FORCE', 'O_4', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('513d25eb-9868-432d-a9f8-7bf409fc4602', 'AIR_FORCE', 'O_4', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('cce4eda7-a857-40bf-b046-1dfd29ef7bea', 'AIR_FORCE', 'O_4', 'TEMPORARY_DUTY', false, false, 800); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('316db752-87d8-460c-bdb1-f52fb6988f42', 'AIR_FORCE', 'O_5', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('ed523fef-5171-498c-a7cf-acf27644e4f1', 'AIR_FORCE', 'O_5', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('1bba6195-5fb0-4ab7-84e6-efffcfc59585', 'AIR_FORCE', 'O_5', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('70618fa8-a275-4e07-82bd-9f64f5b57075', 'AIR_FORCE', 'O_5', 'TEMPORARY_DUTY', false, false, 800); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('1997e8cb-09c7-4bd3-9c3a-f81021a826b4', 'AIR_FORCE', 'O_6', 'PERMANENT_CHANGE_OF_STATION', true, false, 800); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('eb0e5259-23d2-4a7e-90d1-1459a9d70f66', 'AIR_FORCE', 'O_6', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('fd3d68f2-e0a8-45e8-afef-190a3cc7e0c3', 'AIR_FORCE', 'O_6', 'PERMANENT_CHANGE_OF_STATION', false, false, 800); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('a0cf0c01-8f08-4b93-aa4e-f758c074714c', 'AIR_FORCE', 'O_6', 'TEMPORARY_DUTY', false, false, 800); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('57abae02-9d09-4e68-a4f8-4c292c124c02', 'AIR_FORCE', 'O_7', 'PERMANENT_CHANGE_OF_STATION', true, false, 1000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('077defc1-7fe3-46c0-8462-2a247a63e26f', 'AIR_FORCE', 'O_7', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('e044fe41-4642-449b-a737-ca2a4b045637', 'AIR_FORCE', 'O_7', 'PERMANENT_CHANGE_OF_STATION', false, false, 1000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('3ae451df-7550-47e5-bd7a-31be276b31e6', 'AIR_FORCE', 'O_7', 'TEMPORARY_DUTY', false, false, 1000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('727b22db-289c-435a-8e49-79963b36b0d2', 'AIR_FORCE', 'O_8', 'PERMANENT_CHANGE_OF_STATION', true, false, 1000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('47df0db9-59c1-4e3e-941e-9e2231914d46', 'AIR_FORCE', 'O_8', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('1a8eacdf-9c6f-4ef6-88fc-5d7a1887cf5a', 'AIR_FORCE', 'O_8', 'PERMANENT_CHANGE_OF_STATION', false, false, 1000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('00471f38-74c6-4a0a-8b5e-45966b61db4a', 'AIR_FORCE', 'O_8', 'TEMPORARY_DUTY', false, false, 1000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('c4f19191-35be-430f-8240-1993c1ffe193', 'AIR_FORCE', 'O_9', 'PERMANENT_CHANGE_OF_STATION', true, false, 1000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('4f901b44-f378-45ca-9ba8-7a775cd74439', 'AIR_FORCE', 'O_9', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('ef4de798-1f5e-4944-8736-25a838350116', 'AIR_FORCE', 'O_9', 'PERMANENT_CHANGE_OF_STATION', false, false, 1000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('35f61261-3090-48f5-8215-dbe119c9ffee', 'AIR_FORCE', 'O_9', 'TEMPORARY_DUTY', false, false, 1500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('827e1327-81d3-44e7-9409-f84182bf9eaf', 'AIR_FORCE', 'W_1', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('438b3c30-696b-48b3-92b3-de351aa625a3', 'AIR_FORCE', 'W_1', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('7dc8da7e-094a-43bc-a35b-da65a32ff422', 'AIR_FORCE', 'W_1', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('dd8695bc-e7fb-47fd-b823-59a67abd39f5', 'AIR_FORCE', 'W_1', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('f0299b39-c717-4f4b-b09b-d9497e632d50', 'AIR_FORCE', 'W_2', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('4c26f1c9-c9de-454f-afe1-236d7f1c087f', 'AIR_FORCE', 'W_2', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('c5d0d56c-982c-4d70-8ec3-0caa30d2eac5', 'AIR_FORCE', 'W_2', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('73d3e886-41ce-4db0-acae-77f17955ef8c', 'AIR_FORCE', 'W_2', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('fb17cf18-5b7b-4f5e-a304-2fb9c5235c94', 'AIR_FORCE', 'W_3', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('4aa260d2-fc81-4cea-98be-2eb5c4bcbe88', 'AIR_FORCE', 'W_3', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('54435412-6005-47d6-9629-3a1d4cd16d76', 'AIR_FORCE', 'W_3', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('dc38499a-cc5a-4fcc-ae07-91b658a0f764', 'AIR_FORCE', 'W_3', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('fa3d9770-690f-41c0-97b7-ff064721438d', 'AIR_FORCE', 'W_4', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('d7fd2cdb-db09-4e94-8deb-cf512a9b8054', 'AIR_FORCE', 'W_4', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('fe7c91b6-3193-4f86-9fee-842fffed44cb', 'AIR_FORCE', 'W_4', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('29763b28-0c88-4a02-abe2-ae67cddf6840', 'AIR_FORCE', 'W_4', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('52869000-1e26-49a9-a2dc-d3be5a511140', 'AIR_FORCE', 'W_5', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('d237cfd4-4119-4b01-9b24-c5ff06417746', 'AIR_FORCE', 'W_5', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('72ea3671-b964-4783-8325-d96fdaa2cee7', 'AIR_FORCE', 'W_5', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('04bee8a7-9135-4bb2-a6d6-ea2f0903cd6b', 'AIR_FORCE', 'W_5', 'TEMPORARY_DUTY', false, false, 800); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('072e9ae2-324c-40f9-b93c-5ff743723de2', 'ARMY', 'E_1', 'PERMANENT_CHANGE_OF_STATION', true, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('c6e3dbcf-6c1a-4c44-ac8c-44e963b9d775', 'ARMY', 'E_1', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('6e848b9a-4e16-403d-bac3-fd1b0a972b26', 'ARMY', 'E_1', 'PERMANENT_CHANGE_OF_STATION', false, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('1be24784-957e-4e76-8d32-57bf3ff864d1', 'ARMY', 'E_1', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('dc9bd8cf-e7e1-4cf3-a783-f952e35efc79', 'ARMY', 'E_2', 'PERMANENT_CHANGE_OF_STATION', true, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('2c73da07-7e25-47f0-8fb1-8de854831e53', 'ARMY', 'E_2', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('15256c04-72ea-4389-8135-a3bf2d69d02f', 'ARMY', 'E_2', 'PERMANENT_CHANGE_OF_STATION', false, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('371c94ab-d5c9-4194-9722-c0664764da12', 'ARMY', 'E_2', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('0f38e837-a3dc-4012-97f5-32841b759c4b', 'ARMY', 'E_3', 'PERMANENT_CHANGE_OF_STATION', true, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('11632f95-6186-44ee-ac6d-28bdcc424494', 'ARMY', 'E_3', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('676b9c61-c2a9-4619-be95-0fdc20fe8f09', 'ARMY', 'E_3', 'PERMANENT_CHANGE_OF_STATION', false, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('e481c5fb-1bbb-4490-a4fa-ce9430bdc97b', 'ARMY', 'E_3', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('26ea7090-96e1-49ab-90e9-dedb234143de', 'ARMY', 'E_4', 'PERMANENT_CHANGE_OF_STATION', true, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('46b28ed3-1e14-4e20-b0b5-23907ef8e16a', 'ARMY', 'E_4', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('be2ef7e6-4589-4f01-abcd-9112854c59be', 'ARMY', 'E_4', 'PERMANENT_CHANGE_OF_STATION', false, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('c9dfdad2-3d86-4f89-8824-552d682824c9', 'ARMY', 'E_4', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('160c17a0-78c4-47ea-9965-3426933fee56', 'ARMY', 'E_5', 'PERMANENT_CHANGE_OF_STATION', true, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('91ce2c5b-f6d1-4f9b-a0ba-9ce34fb104ef', 'ARMY', 'E_5', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('ed60ca94-2a9c-4399-b930-951be78efcfb', 'ARMY', 'E_5', 'PERMANENT_CHANGE_OF_STATION', false, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('9a0542af-7fca-4ace-b5d2-8ac2101c991e', 'ARMY', 'E_5', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('025bd3b9-7ef6-4d6c-a12f-fd6bc4d75520', 'ARMY', 'E_6', 'PERMANENT_CHANGE_OF_STATION', true, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('5e900967-d9b9-4ab6-9eff-848d9d2d7b62', 'ARMY', 'E_6', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('ed1c3d0d-fd25-4f29-9ccc-9de9d250b793', 'ARMY', 'E_6', 'PERMANENT_CHANGE_OF_STATION', false, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('a787cf98-f28b-4fd8-b94b-86a14f1143c3', 'ARMY', 'E_6', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('d5a4773e-6b44-4761-b9ee-c0300c0cc875', 'ARMY', 'E_7', 'PERMANENT_CHANGE_OF_STATION', true, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('d97205f5-1fe7-4e74-8206-d959c62fc453', 'ARMY', 'E_7', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('c7f8b522-7098-471f-99d2-000a917e9f63', 'ARMY', 'E_7', 'PERMANENT_CHANGE_OF_STATION', false, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('11b932ac-1f13-4fa6-a0c7-c57e34a31821', 'ARMY', 'E_7', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('80c0a04c-cc5b-4c6b-b428-358b1a041382', 'ARMY', 'E_8', 'PERMANENT_CHANGE_OF_STATION', true, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('cfaf8378-c67f-498c-9e17-03a50c05f8f5', 'ARMY', 'E_8', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('8ff975a1-cb8a-40cd-9fef-fcb5381793d5', 'ARMY', 'E_8', 'PERMANENT_CHANGE_OF_STATION', false, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('17d8fbb1-307e-4f1d-88e9-2f7979562a10', 'ARMY', 'E_8', 'TEMPORARY_DUTY', false, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('9be0f310-1475-4b42-a6d8-5b2a7951dde8', 'ARMY', 'E_9', 'PERMANENT_CHANGE_OF_STATION', true, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('d0dca800-0950-4c7a-9244-581be65604b2', 'ARMY', 'E_9', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('a03e6190-3ccd-476f-988c-322b8f2f1de7', 'ARMY', 'E_9', 'PERMANENT_CHANGE_OF_STATION', false, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('cdb7548f-8620-4927-8ae5-57ea8f588790', 'ARMY', 'E_9', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('6bba51f1-bee0-49f0-bfe8-e7067ea2a9fa', 'ARMY', 'O_1_ACADEMY_GRADUATE', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('2a091cc3-bacd-4181-891c-604bf32545b9', 'ARMY', 'O_1_ACADEMY_GRADUATE', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('6d778a9e-bb35-4d02-9daa-2e3147bced58', 'ARMY', 'O_1_ACADEMY_GRADUATE', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('503335e1-cedf-4cfb-867e-c7e3a49263c4', 'ARMY', 'O_1_ACADEMY_GRADUATE', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('d4d1ef91-0b93-4214-abf3-08bdd6f7b04d', 'ARMY', 'O_10', 'PERMANENT_CHANGE_OF_STATION', true, false, 1000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('bb50e36c-11d7-4d90-8657-e9d105ce783a', 'ARMY', 'O_10', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('ce88c847-cb76-4a81-b234-f96a6b1ae450', 'ARMY', 'O_10', 'PERMANENT_CHANGE_OF_STATION', false, false, 1000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('2806a5ed-2c15-47ae-b21c-5d07eb584d18', 'ARMY', 'O_10', 'TEMPORARY_DUTY', false, false, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('baa3c27e-7f6a-460a-aa8a-5c7d464eff44', 'ARMY', 'O_2', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('6210afd9-527d-4e0f-8067-461fcceea4e1', 'ARMY', 'O_2', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('fa048408-3865-499e-9200-003167e7c793', 'ARMY', 'O_2', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('6d62d26c-d1c8-4015-85f0-cdb3538fb917', 'ARMY', 'O_2', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('2a414bb4-8ce3-4c4a-a4cd-87907350c757', 'ARMY', 'O_3', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('eb881568-d0cc-446a-8e97-6f37c82134e2', 'ARMY', 'O_3', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('f5641a69-d8b8-45ed-bd66-d4ea42eded87', 'ARMY', 'O_3', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('a5912d68-e1f1-4494-a879-80fe00c115b6', 'ARMY', 'O_3', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('ff865599-b842-4a14-8941-119a8ac7f093', 'ARMY', 'O_4', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('aecf42e3-1ca4-485a-b910-fbcadd995b75', 'ARMY', 'O_4', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('85433d36-1619-4276-99df-6b6e6ca8327e', 'ARMY', 'O_4', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('701aae73-36c3-474f-808e-90f629124750', 'ARMY', 'O_4', 'TEMPORARY_DUTY', false, false, 800); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('12e0c1d9-a513-42c6-850e-340adaf37bde', 'ARMY', 'O_5', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('00fb88b4-5227-40ab-905c-cef42fc86b0e', 'ARMY', 'O_5', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('82a5ab6d-68e8-41d6-a85b-13df6edf7d82', 'ARMY', 'O_5', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('93ba6ec7-4e48-40e9-b609-e255af7ef5d7', 'ARMY', 'O_5', 'TEMPORARY_DUTY', false, false, 800); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('40bb469e-67e7-42bf-9daf-24ba5f700dbd', 'ARMY', 'O_6', 'PERMANENT_CHANGE_OF_STATION', true, false, 800); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('02fdb01c-84f0-480f-a165-0dee10157ee6', 'ARMY', 'O_6', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('e81a2998-2838-41ae-8ba9-7f9df6a8c059', 'ARMY', 'O_6', 'PERMANENT_CHANGE_OF_STATION', false, false, 800); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('aa09253c-e964-45f4-b935-2cfd30880d49', 'ARMY', 'O_6', 'TEMPORARY_DUTY', false, false, 800); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('5d20dd73-9eaf-48d8-9265-d19af5edd631', 'ARMY', 'O_7', 'PERMANENT_CHANGE_OF_STATION', true, false, 800); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('a71fbd3f-883e-44af-8764-4612aecd0718', 'ARMY', 'O_7', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('c3de0bae-004c-42d5-a196-eec9e34cbacf', 'ARMY', 'O_7', 'PERMANENT_CHANGE_OF_STATION', false, false, 800); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('4328550c-9486-4551-b4fe-5d93780cc787', 'ARMY', 'O_7', 'TEMPORARY_DUTY', false, false, 1000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('4fa9459a-bb83-4ecb-8fd0-29d4c4bdc900', 'ARMY', 'O_8', 'PERMANENT_CHANGE_OF_STATION', true, false, 1000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('965a5ffb-a193-49a4-992d-205d34ec168e', 'ARMY', 'O_8', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('5a420d6f-feae-4deb-8bf4-598a2b5fb4a8', 'ARMY', 'O_8', 'PERMANENT_CHANGE_OF_STATION', false, false, 1000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('bbf4942c-9291-46b3-a63e-cdf9899f2650', 'ARMY', 'O_8', 'TEMPORARY_DUTY', false, false, 1000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('9a063113-c205-44d3-8577-06fa8ce4cbcf', 'ARMY', 'O_9', 'PERMANENT_CHANGE_OF_STATION', true, false, 1000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('acba6543-67ea-4b88-b63f-9d5127303c4b', 'ARMY', 'O_9', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('1bac3513-156a-4e9b-a981-b87abfea6383', 'ARMY', 'O_9', 'PERMANENT_CHANGE_OF_STATION', false, false, 1000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('f5e733af-1dfb-4a8f-b097-9e692208a104', 'ARMY', 'O_9', 'TEMPORARY_DUTY', false, false, 1500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('47f54fd9-1794-41c1-a494-64190a3028d8', 'ARMY', 'W_1', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('227db1b3-af4d-4b08-8f3b-36ae4c2cabe1', 'ARMY', 'W_1', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('c88a45f7-910f-46d3-a534-2538d04d33d5', 'ARMY', 'W_1', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('74226d83-a70e-44b2-932f-975fcfd4aa52', 'ARMY', 'W_1', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('b3ffe48d-331d-441e-a25b-805b1b59a02b', 'ARMY', 'W_2', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('74aaeda8-d87c-4b08-b520-489ed0306f7f', 'ARMY', 'W_2', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('2845d662-6198-4e5e-b058-ac4fa50874dd', 'ARMY', 'W_2', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('fd574bfa-2070-490b-850b-3ca795d318ca', 'ARMY', 'W_2', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('050163b7-047c-4c20-a5b8-7240c327b50d', 'ARMY', 'W_3', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('f509feaa-451e-4166-917f-eb6e4d470ede', 'ARMY', 'W_3', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('41dee16a-29f5-4a34-9dd9-c696279c7f6a', 'ARMY', 'W_3', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('fa7d25a6-1524-46c9-aaf1-a26e861876a4', 'ARMY', 'W_3', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('6f87f346-1be2-4f71-9f6a-98630ee7fa86', 'ARMY', 'W_4', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('2ea20064-2492-49a2-81f5-ee33f67bacb8', 'ARMY', 'W_4', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('9584fc5f-88b7-413b-a6af-8f5f6ee53414', 'ARMY', 'W_4', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('ad34c684-f995-464a-8331-6557539098af', 'ARMY', 'W_4', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('e3a55451-efe2-4883-9ff5-dbbb4ba47819', 'ARMY', 'W_5', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('1067b3f3-33b3-40dc-bada-1d87f2f93307', 'ARMY', 'W_5', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('2a92fdb6-d2a4-4a94-8832-200aab0ad9cb', 'ARMY', 'W_5', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('105cddc7-91b3-464b-9b50-63a642ec32f0', 'ARMY', 'W_5', 'TEMPORARY_DUTY', false, false, 800); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('a5f1175a-307c-4614-9c26-ea268fda8021', 'COAST_GUARD', 'E_1', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('9b5a4ac1-ec5e-4548-859e-ed64a72a0397', 'COAST_GUARD', 'E_1', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('1eb39a06-ebc6-428b-a6e4-588ac69de7d8', 'COAST_GUARD', 'E_1', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('54b8bf0a-f934-427a-8c27-000f026500d9', 'COAST_GUARD', 'E_1', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('be5f27a7-cc9b-4364-83a0-7dd7fb4e32ae', 'COAST_GUARD', 'E_2', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('4d2c9093-78d9-4b67-9e65-fa10822c191d', 'COAST_GUARD', 'E_2', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('49ddb2d9-49a2-4e16-93b7-fcbb1774d92c', 'COAST_GUARD', 'E_2', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('be3de3f0-0436-4a16-b5b1-1574730b25db', 'COAST_GUARD', 'E_2', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('d40de9f9-8944-4d56-98b7-5d4667a16b28', 'COAST_GUARD', 'E_3', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('627297b2-5b5f-4b8b-ab1a-f11f39fda8e3', 'COAST_GUARD', 'E_3', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('006c141f-8ec5-4e7e-b672-2a436eb8b553', 'COAST_GUARD', 'E_3', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('d51fa5c3-ac6c-437b-9719-40dcb3f7d880', 'COAST_GUARD', 'E_3', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('a3ddc5cb-efeb-48b4-94ab-d9455eacda50', 'COAST_GUARD', 'E_4', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('2cce2553-b22e-4537-a32c-194228ef1d21', 'COAST_GUARD', 'E_4', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('cd9ac50f-78f4-40ad-93b6-73a89628f582', 'COAST_GUARD', 'E_4', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('8b82f663-6656-461a-af3a-46c64f7cf467', 'COAST_GUARD', 'E_4', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('ef9ca3df-515e-4623-9a48-4b56bc05b2ec', 'COAST_GUARD', 'E_5', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('12344629-b754-4796-a6aa-c4c92bc403d4', 'COAST_GUARD', 'E_5', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('ccc9b23c-b5c6-49bb-81ca-5129648cdfb3', 'COAST_GUARD', 'E_5', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('7e8e5b74-a783-4226-87a5-c2337e7b40f0', 'COAST_GUARD', 'E_5', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('f52fb8d9-3456-45ef-a600-d2a3faa710b7', 'COAST_GUARD', 'E_6', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('ae7cd68c-6ae0-4655-ab07-0952c2153fab', 'COAST_GUARD', 'E_6', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('0d2598e3-74f9-4078-acdc-cb062664228c', 'COAST_GUARD', 'E_6', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('f9eaadc5-296c-4a9b-af63-437579a47d6a', 'COAST_GUARD', 'E_6', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('a7a28289-4ea0-4693-9484-58b062503c3b', 'COAST_GUARD', 'E_7', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('a98ed906-9054-45d5-b072-fcfda44b8103', 'COAST_GUARD', 'E_7', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('c9521942-3d18-4033-8c0a-0db0e34d8515', 'COAST_GUARD', 'E_7', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('0815eb46-b433-4b65-80b9-1adb45842b64', 'COAST_GUARD', 'E_7', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('d28fa353-d62f-4bfd-98ae-91c454010037', 'COAST_GUARD', 'E_8', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('41f82102-4042-4751-9c71-ec499a08f66f', 'COAST_GUARD', 'E_8', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('132ca487-8382-44e5-9d4d-0bb99f560585', 'COAST_GUARD', 'E_8', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('14bf2450-e72d-4bdc-a162-8725ff3fc98d', 'COAST_GUARD', 'E_8', 'TEMPORARY_DUTY', false, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('4dc1742c-db2b-4f9c-97d3-1400950b2164', 'COAST_GUARD', 'E_9', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('90443a14-36fd-4efb-8bee-f6faf4d24bae', 'COAST_GUARD', 'E_9', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('7ae19570-c08b-42f3-ae83-0e67563f3490', 'COAST_GUARD', 'E_9', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('c585c3c1-2284-4730-95d5-94e56f0290b6', 'COAST_GUARD', 'E_9', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('636887b8-300c-47a0-b452-d34576c1a836', 'COAST_GUARD', 'O_1_ACADEMY_GRADUATE', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('f3a5f99d-4a28-4308-bce4-a9d16bb782cf', 'COAST_GUARD', 'O_1_ACADEMY_GRADUATE', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('77fc3df9-ee77-4029-8dda-4bcdd3502229', 'COAST_GUARD', 'O_1_ACADEMY_GRADUATE', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('97c47184-491a-4bf6-a61b-048295897e9c', 'COAST_GUARD', 'O_1_ACADEMY_GRADUATE', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('1731d7c9-5617-418d-84cd-cd1c421eca31', 'COAST_GUARD', 'O_10', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('227866dd-abd7-4d3c-9fb3-93892831655e', 'COAST_GUARD', 'O_10', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('2a980003-8a27-4584-9e2f-adba76648a71', 'COAST_GUARD', 'O_10', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('ec1b26b2-2cee-4ab9-b004-95f1749f6db8', 'COAST_GUARD', 'O_10', 'TEMPORARY_DUTY', false, false, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('0bf110e4-9a21-48b0-bb10-350d8c3f0a66', 'COAST_GUARD', 'O_2', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('15cda6f6-c23e-4f10-85f8-deff039d0fe0', 'COAST_GUARD', 'O_2', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('2ea3e547-925a-4655-905e-63c2f552680f', 'COAST_GUARD', 'O_2', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('c2079de2-88fd-4810-bea9-faaff3df0dc8', 'COAST_GUARD', 'O_2', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('a9a7e13e-328b-44e5-9a98-827244dc99a4', 'COAST_GUARD', 'O_3', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('674e607f-5fe7-4b90-9990-f069f16700e3', 'COAST_GUARD', 'O_3', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('dbfc4462-950f-425c-92d4-371ee0a1d287', 'COAST_GUARD', 'O_3', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('ceb77a5c-da99-4eb3-97a6-c3c1ceaad982', 'COAST_GUARD', 'O_3', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('17aea12b-03b2-4db0-b147-ccdc0da56a47', 'COAST_GUARD', 'O_4', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('933aedb3-041e-4143-ae08-4a65a73f3ade', 'COAST_GUARD', 'O_4', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('daa82c7c-6222-47ad-accf-4cf804529f4d', 'COAST_GUARD', 'O_4', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('c727c021-3b4f-439e-9ce3-105e63e3144b', 'COAST_GUARD', 'O_4', 'TEMPORARY_DUTY', false, false, 800); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('bace5cda-93d7-4d5c-b88d-7540d77e8c4c', 'COAST_GUARD', 'O_5', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('713b82d4-3105-4669-aa95-d7a248914a13', 'COAST_GUARD', 'O_5', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('9e640f81-6059-4f61-8aa7-ef8a41462a82', 'COAST_GUARD', 'O_5', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('11a5aed3-ad7c-43fc-9831-ca44d30c8e2b', 'COAST_GUARD', 'O_5', 'TEMPORARY_DUTY', false, false, 800); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('e75fe966-83ae-4fc7-bee5-96d725e5c612', 'COAST_GUARD', 'O_6', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('3c185260-2b9b-4999-bede-6d9f67419a9f', 'COAST_GUARD', 'O_6', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('1fa1a873-a9cd-4b50-a5ba-74fcb2bacf43', 'COAST_GUARD', 'O_6', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('d7ae71a3-2d98-40dd-a0d6-dca6ad0662fd', 'COAST_GUARD', 'O_6', 'TEMPORARY_DUTY', false, false, 800); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('ca8c36d5-657d-4cc1-a9ea-fc2ebcfde54a', 'COAST_GUARD', 'O_7', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('41842c78-0692-41b7-a7ac-60e88537e768', 'COAST_GUARD', 'O_7', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('e8d12a43-6380-4bf1-924f-68a3ab93ff93', 'COAST_GUARD', 'O_7', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('ae0701ec-466f-43e9-a179-f811644ab66a', 'COAST_GUARD', 'O_7', 'TEMPORARY_DUTY', false, false, 1000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('95fdb3da-2c66-4dbd-82c9-1d2a75f7ec72', 'COAST_GUARD', 'O_8', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('ba915e0b-005e-43cb-a855-00b0c7a344ff', 'COAST_GUARD', 'O_8', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('f8930200-e33b-4a5f-80b8-507c544faaf3', 'COAST_GUARD', 'O_8', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('3cf52ff8-7214-4338-9c50-86849f524fc0', 'COAST_GUARD', 'O_8', 'TEMPORARY_DUTY', false, false, 1000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('35dead2c-2cfc-4af2-8adb-195072e4eb65', 'COAST_GUARD', 'O_9', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('e7e58e9d-350a-45c9-b73c-97e4211c7661', 'COAST_GUARD', 'O_9', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('62c899e4-babd-40f0-9093-124754c46a88', 'COAST_GUARD', 'O_9', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('7ea55f6d-5ec9-49cb-92e7-fe54a6934838', 'COAST_GUARD', 'O_9', 'TEMPORARY_DUTY', false, false, 1500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('3c95ccd4-e5f0-4cec-a6a9-195291a9df4e', 'COAST_GUARD', 'W_1', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('4e6f8b9e-c95d-4377-bed3-837b13feaff7', 'COAST_GUARD', 'W_1', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('5091ec35-db2a-408c-8678-b3dc362dc15e', 'COAST_GUARD', 'W_1', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('f39ddaa3-ddc2-46ae-9e9b-b3b6ba46706d', 'COAST_GUARD', 'W_1', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('01a620c6-1536-4e70-a817-72c7d3e7eb1a', 'COAST_GUARD', 'W_2', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('09d0c468-ac98-4981-8d73-5c7208780ffa', 'COAST_GUARD', 'W_2', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('e8e7500e-1316-44f4-9fad-dfdb4fbcfc20', 'COAST_GUARD', 'W_2', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('5e76280a-5437-440d-86f3-8b72bd026bd1', 'COAST_GUARD', 'W_2', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('07ac21bb-4ace-4f85-be77-1995e4343c4f', 'COAST_GUARD', 'W_3', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('ee85c49b-26ac-423e-8bca-0c040ee7cdc7', 'COAST_GUARD', 'W_3', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('11c34ece-1f9c-4284-b48a-254677377c76', 'COAST_GUARD', 'W_3', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('d71d2ca7-538b-43b9-acaa-8d2e0b1ed322', 'COAST_GUARD', 'W_3', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('1fc71a48-d13f-487f-8349-ea0e4189585b', 'COAST_GUARD', 'W_4', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('d14eebc7-fdd2-42a5-b86e-cf8cde4ec276', 'COAST_GUARD', 'W_4', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('f3d82f49-6f1b-4487-ad4c-c9e40c6038f2', 'COAST_GUARD', 'W_4', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('3a938f3b-d6c8-4e8e-9e5e-ada4999db6d3', 'COAST_GUARD', 'W_4', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('c76b7fec-75e1-4822-b226-94717848278b', 'COAST_GUARD', 'W_5', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('7a51c723-9c63-4731-a22b-24a8569fd6da', 'COAST_GUARD', 'W_5', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('dd6af322-f395-4cae-abbd-ef7045b50584', 'COAST_GUARD', 'W_5', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('3f7573cf-76da-43e4-bf72-cbc23a87784e', 'COAST_GUARD', 'W_5', 'TEMPORARY_DUTY', false, false, 800); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('d386d1b3-4019-4842-8465-cee654d459f5', 'MARINES', 'E_1', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('635fee65-fbb7-48a9-8f92-87883a55f728', 'MARINES', 'E_1', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('447741d2-8cab-42a9-8830-81ff3513b3e4', 'MARINES', 'E_1', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('c2fe3ea5-6d77-4bd3-ae44-e90b819d7b5c', 'MARINES', 'E_1', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('56b2ad5e-8c96-4296-b20c-789e2ff2f50a', 'MARINES', 'E_2', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('bc06ceb2-7fce-48bb-b318-e99bbe52e3bd', 'MARINES', 'E_2', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('11b36f60-3ead-4136-8c6d-e9aaf4e8eed0', 'MARINES', 'E_2', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('f462f683-152f-4f25-b9a4-d05062cabc8e', 'MARINES', 'E_2', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('91f6e859-e8f4-47f1-a0bb-7d4d9896269c', 'MARINES', 'E_3', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('129ce8b1-1314-4a4a-8b50-417ac3254167', 'MARINES', 'E_3', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('889db6dd-0b72-46b7-baba-3e057cdedaef', 'MARINES', 'E_3', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('acd3caf1-0528-4d3b-9225-d490669ffbf4', 'MARINES', 'E_3', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('6fa7111a-6ea8-4afc-ad4c-e2cdb7c916bb', 'MARINES', 'E_4', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('487cce2b-6a52-4a9c-aa5e-e7afc863dfff', 'MARINES', 'E_4', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('8b6734c1-3b1c-4191-937d-a09a5a60e6a4', 'MARINES', 'E_4', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('eb3e910d-4771-489b-a721-213bbc2ed302', 'MARINES', 'E_4', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('3d4f4f49-41c1-422b-a240-4a62323b038e', 'MARINES', 'E_5', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('631b210f-6aab-4300-bcd7-03dc69ff711e', 'MARINES', 'E_5', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('f63294f4-bd1a-40e2-a102-ddc435616245', 'MARINES', 'E_5', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('5784d241-fee0-4ba2-bb7f-8d98480aa6f9', 'MARINES', 'E_5', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('75ba4b06-a2fb-4220-a170-c00bb2f59956', 'MARINES', 'E_6', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('4d63274c-3adc-4c06-b6a4-d94c256bb81d', 'MARINES', 'E_6', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('b9036529-f47e-4100-8021-c0f5424030f0', 'MARINES', 'E_6', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('6c253cea-8c86-49e0-ae83-b44f47ca1080', 'MARINES', 'E_6', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('18a43e8a-df36-4dd1-bd75-196650961d68', 'MARINES', 'E_7', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('9d0d987d-a327-4a63-abdc-4d300ca0a200', 'MARINES', 'E_7', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('74058ac8-265f-45f5-910e-67ced04f22bc', 'MARINES', 'E_7', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('0dc09d32-099a-4409-8a82-fa8348f93821', 'MARINES', 'E_7', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('609fcccc-c52e-4a2f-949e-081dd9e1036b', 'MARINES', 'E_8', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('373327d3-43bb-4798-9155-2ec1173cf928', 'MARINES', 'E_8', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('42101325-f20b-4c26-8c29-9ace1266e861', 'MARINES', 'E_8', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('0f898132-5184-4d52-b945-4c648b4caf53', 'MARINES', 'E_8', 'TEMPORARY_DUTY', false, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('be253b6d-897d-4afe-93be-a30607e5f310', 'MARINES', 'E_9', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('651baac1-1366-4f93-ab24-de7c1c845c48', 'MARINES', 'E_9', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('0653c788-8517-4d4c-b2f8-78136362d8ec', 'MARINES', 'E_9', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('d35b5fea-eb63-413c-adf1-8516aaa8e405', 'MARINES', 'E_9', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('3107bbe0-1e93-4052-bf0e-6c086b82e3e2', 'MARINES', 'O_1_ACADEMY_GRADUATE', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('7344f9f3-0258-478a-941d-b517d2d1e93e', 'MARINES', 'O_1_ACADEMY_GRADUATE', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('c63221e0-7f37-46da-b415-4c1d89dfa713', 'MARINES', 'O_1_ACADEMY_GRADUATE', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('02700e60-5654-4f1c-94c2-97b7a83dd750', 'MARINES', 'O_1_ACADEMY_GRADUATE', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('7d43a56c-d1c2-4e96-9ddb-2e255698a11f', 'MARINES', 'O_10', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('3a3f103a-732f-474f-b589-f0af0520c1ae', 'MARINES', 'O_10', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('f7180268-9b58-4a9c-9d7c-705e7a736dfe', 'MARINES', 'O_10', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('b68d073e-ed4c-4053-9981-3f9a2a6bdf54', 'MARINES', 'O_10', 'TEMPORARY_DUTY', false, false, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('03c7c5e5-c4cc-466f-8497-95f39188d2f5', 'MARINES', 'O_2', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('9573690b-e91e-42c4-a169-c6944cfbc199', 'MARINES', 'O_2', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('8c5bceda-4a44-4172-a6cf-cd57b92edb0e', 'MARINES', 'O_2', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('779de8a5-00c7-43d4-9002-bef88228998f', 'MARINES', 'O_2', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('922daad4-111b-4e24-8766-afda2c5e1d94', 'MARINES', 'O_3', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('1dfd0637-c0fc-4a12-b5fb-18ff2be78d9c', 'MARINES', 'O_3', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('fdc98b2b-7139-4705-973b-c47f36cd0e36', 'MARINES', 'O_3', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('918cedc7-be26-4dd1-90e8-0d92eda2d399', 'MARINES', 'O_3', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('56adcea3-1942-476e-98b1-d5cf1db2ef2d', 'MARINES', 'O_4', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('ad44f23c-194f-42f9-9230-e7cb690078af', 'MARINES', 'O_4', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('1ce7aec7-84f3-4b9e-a9e6-5384f0b496d5', 'MARINES', 'O_4', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('79d6acf1-5a36-4bb2-b804-29b8a07aec6d', 'MARINES', 'O_4', 'TEMPORARY_DUTY', false, false, 800); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('31484fff-d0bc-4d79-b00c-fbec99bb51f4', 'MARINES', 'O_5', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('541c0f2c-24f2-4120-8b0f-fffecabe5186', 'MARINES', 'O_5', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('29d9fb44-08aa-44ca-aa9f-7efd3958cfa1', 'MARINES', 'O_5', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('c537c1c8-7098-4e74-aa61-00236454b153', 'MARINES', 'O_5', 'TEMPORARY_DUTY', false, false, 800); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('d1106ee4-9d80-4344-978f-b8e10162f7d7', 'MARINES', 'O_6', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('cd20fcd2-5c9d-4ee7-bc99-d0e020d418c6', 'MARINES', 'O_6', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('e1ec446c-c885-46c1-a5ae-ea0868575ff5', 'MARINES', 'O_6', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('f0ce8bd8-b184-462f-929b-13270fddf8ad', 'MARINES', 'O_6', 'TEMPORARY_DUTY', false, false, 800); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('dbed1ce4-24ae-4163-86d7-bba16b84dc37', 'MARINES', 'O_7', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('7458207b-2a0a-4805-9dc8-1754a53f2101', 'MARINES', 'O_7', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('a8736e7e-cb33-4268-a8e8-b36a0fdbc170', 'MARINES', 'O_7', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('9fad2811-aa06-45b0-b861-c79c65affce9', 'MARINES', 'O_7', 'TEMPORARY_DUTY', false, false, 1000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('0e86b283-1f07-4be6-a394-fdee02a66ce1', 'MARINES', 'O_8', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('2dccc724-83b4-46d0-8300-f41b620d77e3', 'MARINES', 'O_8', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('91c91201-b35f-4c4d-a5f1-a2035a1865ea', 'MARINES', 'O_8', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('b585a1ae-c579-4567-8baa-1dc872fe6229', 'MARINES', 'O_8', 'TEMPORARY_DUTY', false, false, 1000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('879b1c5e-8a32-4894-b1fe-2bcca24c6ede', 'MARINES', 'O_9', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('529f86ba-0c2c-477c-8d16-2307f5054c87', 'MARINES', 'O_9', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('b033e644-e4fa-4a22-ba14-2e36d8bf06c2', 'MARINES', 'O_9', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('9b303b3f-5e5d-43e2-a7c7-a96c0b51866e', 'MARINES', 'O_9', 'TEMPORARY_DUTY', false, false, 1500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('f9451dec-91ce-48f7-9c8a-ccbef92d0bad', 'MARINES', 'W_1', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('9fa1e331-fec0-433a-a7c5-c8c4630f04bb', 'MARINES', 'W_1', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('21d21fa2-cc99-4784-b40c-f4520e6e6277', 'MARINES', 'W_1', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('780fb291-e3d4-4083-a2e9-3d303618cbb5', 'MARINES', 'W_1', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('046b17f1-2b4c-4329-b137-82787c7c732a', 'MARINES', 'W_2', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('e39e51b1-60a2-4221-87f6-58aaae10c8a2', 'MARINES', 'W_2', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('8aef3b0c-b70e-43fd-80e4-f9919e876cd5', 'MARINES', 'W_2', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('f3a653f9-eeff-4412-b073-733f6fe14c52', 'MARINES', 'W_2', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('762bf155-8fcb-4847-a0bd-6706f4d6db85', 'MARINES', 'W_3', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('806dd0ca-43a5-401d-b87f-300d5ff930bb', 'MARINES', 'W_3', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('e0b97b98-5577-4b17-8aa3-4dc7ae197bba', 'MARINES', 'W_3', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('ca9dcf72-9692-4bff-ac3e-ee65b1969ef1', 'MARINES', 'W_3', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('0c495e56-f806-4a4c-9ae9-3898631f3b7c', 'MARINES', 'W_4', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('73322004-1d48-465a-8210-59047c5b986a', 'MARINES', 'W_4', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('5d52c27e-8e36-494b-900d-25011b8bb5d6', 'MARINES', 'W_4', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('5dec2721-518d-433b-9868-302802970820', 'MARINES', 'W_4', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('6e6866b3-f07f-48f3-ba9b-bac4f9bfd0c2', 'MARINES', 'W_5', 'PERMANENT_CHANGE_OF_STATION', true, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('719152f6-0b5a-4bfe-8e24-f096e6a0014b', 'MARINES', 'W_5', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('ebb855f1-9081-485f-83e1-fd455341f9ce', 'MARINES', 'W_5', 'PERMANENT_CHANGE_OF_STATION', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('5ab6bcab-dc9f-4080-b9e0-a074e167c1d3', 'MARINES', 'W_5', 'TEMPORARY_DUTY', false, false, 800); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('2127ed04-166e-42b4-80ea-ea1341cf4557', 'NAVY', 'E_1', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('db42118d-b477-4374-be75-93729dd4736f', 'NAVY', 'E_1', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('32092d67-04ac-4c75-980e-d95744c71cf4', 'NAVY', 'E_1', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('9b1f177a-65f5-4292-b364-94ca4cfafad1', 'NAVY', 'E_1', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('2e0d8321-9d35-4b4c-9421-96257cbd91aa', 'NAVY', 'E_2', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('10392535-28a1-41b7-a346-bebf9af4a63b', 'NAVY', 'E_2', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('abaa1e96-dc7c-4ec0-ab96-dd9ed48c7ccc', 'NAVY', 'E_2', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('c2951509-75d6-49ca-a530-fb3a206485a8', 'NAVY', 'E_2', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('3b1fc12e-7537-496d-b7f7-251bf961aede', 'NAVY', 'E_3', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('f8060932-33e5-43f6-a161-f1ff714793ea', 'NAVY', 'E_3', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('d9879c16-829d-4b7a-8653-42306a38987c', 'NAVY', 'E_3', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('230d2f7d-fd0b-4ec6-ba86-6bc35d42a57f', 'NAVY', 'E_3', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('8e0aff5c-1172-46d3-b362-294e973279d9', 'NAVY', 'E_4', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('1031570d-2c79-4056-8431-3c012f0d1eaf', 'NAVY', 'E_4', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('c7dc501f-c028-46af-a71e-477889275465', 'NAVY', 'E_4', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('bf77bafa-9c89-4c15-9509-4656e5b9e382', 'NAVY', 'E_4', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('9ebd5ad0-5a57-4157-ac94-8ec70e73ada8', 'NAVY', 'E_5', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('db2d93e7-e193-43f8-894b-842946c5f017', 'NAVY', 'E_5', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('6fc32996-64bb-44c5-989d-157b1b8af8c2', 'NAVY', 'E_5', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('c997234f-e364-4c05-b9b8-72f99221ed7d', 'NAVY', 'E_5', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('b7f89bef-5a4d-4206-81a9-b479238578c2', 'NAVY', 'E_6', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('673d57ae-1ef6-444c-ae3c-41986cc949c7', 'NAVY', 'E_6', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('b0444817-e48a-4652-86f6-13580ff5abbc', 'NAVY', 'E_6', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('a1c70f84-3fcd-44ff-82c5-f57897ec4847', 'NAVY', 'E_6', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('8a88f685-b300-4958-9549-de514633f97d', 'NAVY', 'E_7', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('2dd1b4c1-7bc6-4162-b5e1-7a2d6a3c57e2', 'NAVY', 'E_7', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('4ea0a398-4f2e-44e8-86fa-3f7a23b73c90', 'NAVY', 'E_7', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('c6d8e983-09a3-4400-a296-32fd94a8c9b7', 'NAVY', 'E_7', 'TEMPORARY_DUTY', false, false, 400); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('4aff568d-2f80-489d-8517-7710ea66b791', 'NAVY', 'E_8', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('5a4efc1c-6d43-43c4-95ce-c716dbf66ba7', 'NAVY', 'E_8', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('fae0ca04-7806-475b-8f3e-1d8500d79817', 'NAVY', 'E_8', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('176965b1-bdf3-430e-94b2-65b3190cd49c', 'NAVY', 'E_8', 'TEMPORARY_DUTY', false, false, 500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('f4e5067b-fe13-48eb-ac7c-c86a8b00e6bd', 'NAVY', 'E_9', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('c2ae57bb-63b2-4473-b695-282686ee0669', 'NAVY', 'E_9', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('fad561fe-d09d-420a-bc4b-c7c1ff876fd0', 'NAVY', 'E_9', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('73d42155-7853-433f-a4a3-784720e8af05', 'NAVY', 'E_9', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('2a46c318-cc62-48a9-b552-8cf3e5bffed6', 'NAVY', 'O_1_ACADEMY_GRADUATE', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('1549ad28-8896-4a59-942e-c5dc4c17a881', 'NAVY', 'O_1_ACADEMY_GRADUATE', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('0b31a8e5-adc1-4041-84ad-88239102fa44', 'NAVY', 'O_1_ACADEMY_GRADUATE', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('a5be43be-d3b5-4135-9a4c-210abc9c9e00', 'NAVY', 'O_1_ACADEMY_GRADUATE', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('5ceb1f08-f6f1-43a6-bbe3-2c96076c5d41', 'NAVY', 'O_10', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('6069adf5-f07a-4c17-bdb5-315afd387c61', 'NAVY', 'O_10', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('ad3b4f63-b25d-48ad-ab6d-7fe2556ddfad', 'NAVY', 'O_10', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('f6de8869-5410-45ba-8091-60bd70044843', 'NAVY', 'O_10', 'TEMPORARY_DUTY', false, false, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('aa5707f4-9ab1-4dda-9bcc-fea244794632', 'NAVY', 'O_2', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('b91208fd-26cf-413b-a841-0fd0e1b98893', 'NAVY', 'O_2', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('a691cf98-81d7-45ac-a19b-2fd1b6f1595f', 'NAVY', 'O_2', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('b5c45e5d-bcfe-47e9-af88-fee9a9ff4795', 'NAVY', 'O_2', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('dc633785-630b-4820-92f2-6b2bc29fb23e', 'NAVY', 'O_3', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('f0be2c3d-8cbf-49d2-b644-325b332278ea', 'NAVY', 'O_3', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('97712c2f-ed4e-4814-af74-8a1fca9cecc6', 'NAVY', 'O_3', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('5a575cd1-e155-4abf-a7d4-a9a34cac22a3', 'NAVY', 'O_3', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('2f871885-e19b-43b9-880e-7bae5562e4ae', 'NAVY', 'O_4', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('78637b0d-2e56-40a3-bbc2-ca46f9c6fc17', 'NAVY', 'O_4', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('c94c7eda-1dd9-4898-8c73-2c21b2251a69', 'NAVY', 'O_4', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('7affd93f-ce6f-4d24-b4b2-d50b813ad41d', 'NAVY', 'O_4', 'TEMPORARY_DUTY', false, false, 800); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('e85076c7-1699-4d6a-9b1c-5395c2a4f33d', 'NAVY', 'O_5', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('879461dd-cac3-483e-a240-5b31918575e5', 'NAVY', 'O_5', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('7db1984d-7ee1-4ef2-9381-06f28506a05a', 'NAVY', 'O_5', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('12d5ebba-ba2c-4e0e-bfab-af404aa64e03', 'NAVY', 'O_5', 'TEMPORARY_DUTY', false, false, 800); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('b8962757-d12f-45a0-a446-1599022f05b4', 'NAVY', 'O_6', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('ee9f3dbe-cdeb-4ab2-85a9-40cdc9afbe07', 'NAVY', 'O_6', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('37724212-f14d-4238-b6a0-806daeab08a7', 'NAVY', 'O_6', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('ea316da6-41ab-41cd-ae89-be7c45d8ac95', 'NAVY', 'O_6', 'TEMPORARY_DUTY', false, false, 800); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('b9415245-3304-48c1-97df-53e52c57deed', 'NAVY', 'O_7', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('37b0ce1d-5a82-4b69-ab31-4f2b58e77772', 'NAVY', 'O_7', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('18567c5e-30aa-4310-9835-34fcaa77d24c', 'NAVY', 'O_7', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('dbb84de2-5f30-424a-92d5-e24af8164eb2', 'NAVY', 'O_7', 'TEMPORARY_DUTY', false, false, 1000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('6d23255c-0673-4789-84f7-4c3242935af2', 'NAVY', 'O_8', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('e6cfc916-0650-4a41-9d98-a2be45c57f90', 'NAVY', 'O_8', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('c7be497c-f47e-4d02-80bb-0cd51e71a701', 'NAVY', 'O_8', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('becb91b6-3c19-4106-8cc9-3aa7b213ffa3', 'NAVY', 'O_8', 'TEMPORARY_DUTY', false, false, 1000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('8f859c1b-ea96-46df-80d6-1009dc10e23b', 'NAVY', 'O_9', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('7d714df9-362a-4891-be80-70ea8c780343', 'NAVY', 'O_9', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('84462e2c-6426-4e47-a0b2-7c246f39c6d7', 'NAVY', 'O_9', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('3d7bc4b9-79d9-42ec-9da3-387b8c5dcfbe', 'NAVY', 'O_9', 'TEMPORARY_DUTY', false, false, 1500); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('66f78b8b-9ec4-4ce9-bb81-5642c50f6c25', 'NAVY', 'W_1', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('3915ccfc-f92f-4485-ac14-9993be0cc18e', 'NAVY', 'W_1', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('600af64a-5dde-4708-a6ee-7dfb912ab9a8', 'NAVY', 'W_1', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('b1cb1914-551a-4f02-9194-ea0e682ded5d', 'NAVY', 'W_1', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('76173b8f-746c-4684-9a94-f29c1ae58a92', 'NAVY', 'W_2', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('50f206b0-692d-4bc5-bfa1-e7da8ae625e2', 'NAVY', 'W_2', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('23082473-ac3c-4234-89e6-6c394b742bed', 'NAVY', 'W_2', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('777783a2-425c-4612-b0bd-219ec6f2a422', 'NAVY', 'W_2', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('ca7ec74c-a813-4360-83a0-c0203c365d99', 'NAVY', 'W_3', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('36e2c462-8b4b-4585-8cda-f9bf3f44441c', 'NAVY', 'W_3', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('aec382e4-4e5e-43c5-978c-0b40178684fc', 'NAVY', 'W_3', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('dd087796-8551-422e-8de3-6bee43981a89', 'NAVY', 'W_3', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('38a54224-8d6b-4b72-ac88-84da00fef110', 'NAVY', 'W_4', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('d067662f-924d-43b6-a97d-e6d740e32318', 'NAVY', 'W_4', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('b56e1cab-e1a9-41cb-8950-766a80d86193', 'NAVY', 'W_4', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('8cf8a4d6-6ddf-4e33-8e07-010580efcf23', 'NAVY', 'W_4', 'TEMPORARY_DUTY', false, false, 600); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('e2b3b906-0851-4530-9d39-a6239097c04a', 'NAVY', 'W_5', 'PERMANENT_CHANGE_OF_STATION', true, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('c61ba6ca-48c6-443b-9fda-12aed096aeb9', 'NAVY', 'W_5', 'PERMANENT_CHANGE_OF_STATION', true, true, 2000); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('dc705b6f-bf47-4587-9380-06b8f72e3746', 'NAVY', 'W_5', 'PERMANENT_CHANGE_OF_STATION', false, false, 0); +INSERT INTO ub_allowances (id, branch, grade, orders_type, dependents_authorized, accompanied_tour, ub_weight_allowance) VALUES ('d9672e42-9cbc-4114-8964-0f202b264970', 'NAVY', 'W_5', 'TEMPORARY_DUTY', false, false, 800); \ No newline at end of file diff --git a/migrations/app/schema/20241111221400_add_new_order_types.up.sql b/migrations/app/schema/20241111221400_add_new_order_types.up.sql new file mode 100644 index 00000000000..efbb9f81208 --- /dev/null +++ b/migrations/app/schema/20241111221400_add_new_order_types.up.sql @@ -0,0 +1,5 @@ +-- adding order types to orders_type enum used in the orders table. +ALTER TYPE public.orders_type ADD VALUE 'EARLY_RETURN_OF_DEPENDENTS'; +ALTER TYPE public.orders_type ADD VALUE 'STUDENT_TRAVEL'; +COMMENT ON COLUMN orders.orders_type IS 'MilMove supports 10 orders types: Permanent change of station (PCS), local move, retirement, separation, wounded warrior, bluebark, safety, temporary duty (TDY), early return of dependents, and student travel. +In general, the moving process starts with the job/travel orders a customer receives from their service. In the orders, information describing rank, the duration of job/training, and their assigned location will determine if their entire dependent family can come, what the customer is allowed to bring, and how those items will arrive to their new location.' diff --git a/migrations/app/schema/20241115214553_create_re_fsc_multipliers_table.up.sql b/migrations/app/schema/20241115214553_create_re_fsc_multipliers_table.up.sql new file mode 100644 index 00000000000..25721957d49 --- /dev/null +++ b/migrations/app/schema/20241115214553_create_re_fsc_multipliers_table.up.sql @@ -0,0 +1,19 @@ +CREATE TABLE IF NOT EXISTS re_fsc_multipliers ( + id uuid NOT NULL PRIMARY KEY, + low_weight int NOT NULL, + high_weight int NOT NULL, + multiplier decimal NOT NULL, + created_at timestamp NOT NULL DEFAULT NOW(), + updated_at timestamp NOT NULL DEFAULT NOW() +); + +COMMENT ON TABLE re_fsc_multipliers IS 'Stores data needed to calculate FSC'; +COMMENT ON COLUMN re_fsc_multipliers.low_weight IS 'The lowest weight permitted for a shipment'; +COMMENT ON COLUMN re_fsc_multipliers.high_weight IS 'The highest weight permitted for a shipment'; +COMMENT ON COLUMN re_fsc_multipliers.multiplier IS 'The decimal multiplier used to calculate the FSC'; + +INSERT INTO re_fsc_multipliers (id,low_weight,high_weight,multiplier,created_at,updated_at) VALUES + ('e8053bda-e19e-4343-b858-04d691f1438b',0,5000,0.000417,'2024-11-19 12:59:06.276892-06','2024-11-19 12:59:06.276892-06'), + ('5f2d402e-9c41-4034-bfea-46e699e2ed95',5001,10000,0.0006255,'2024-11-19 12:59:06.276892-06','2024-11-19 12:59:06.276892-06'), + ('0faa2887-bf9e-4fb5-9d96-42bd79d963bd',10001,24000,0.000834,'2024-11-19 12:59:06.276892-06','2024-11-19 12:59:06.276892-06'), + ('9bb374f7-12a2-4c51-8391-a691406b4c2c',24001,99999999,0.00139,'2024-11-19 12:59:06.276892-06','2024-11-19 12:59:06.276892-06'); diff --git a/migrations/app/schema/20241119151019_stored_procs_for_ordering_service_items.up.sql b/migrations/app/schema/20241119151019_stored_procs_for_ordering_service_items.up.sql new file mode 100644 index 00000000000..7757b243aa1 --- /dev/null +++ b/migrations/app/schema/20241119151019_stored_procs_for_ordering_service_items.up.sql @@ -0,0 +1,363 @@ +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +ALTER TABLE mto_service_items + ALTER COLUMN id SET DEFAULT uuid_generate_v4(); + + +-- creating function to get address is_oconus +CREATE OR REPLACE FUNCTION get_is_oconus(address_id UUID) +RETURNS BOOLEAN AS $$ +DECLARE + is_oconus BOOLEAN; +BEGIN + SELECT a.is_oconus + INTO is_oconus + FROM addresses a + WHERE a.id = address_id; + + RETURN is_oconus; +EXCEPTION + WHEN NO_DATA_FOUND THEN + RAISE EXCEPTION 'Address with ID % not found', address_id; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION does_service_item_exist( + service_id UUID, + shipment_id UUID +) RETURNS BOOLEAN AS $$ +BEGIN + IF EXISTS ( + SELECT 1 + FROM mto_service_items + WHERE re_service_id = service_id + AND mto_shipment_id = shipment_id + ) THEN + RAISE EXCEPTION 'Service item already exists for service_id % and shipment_id %', service_id, shipment_id; + END IF; + RETURN FALSE; +END; +$$ LANGUAGE plpgsql; + +-- stored proc that creates auto-approved service items based off of a shipment id +CREATE OR REPLACE PROCEDURE create_approved_service_items_for_shipment( + IN shipment_id UUID +) +AS ' +DECLARE + s_status mto_shipment_status; + s_type mto_shipment_type; + m_code market_code_enum; + move_id UUID; + pickup_address_id UUID; + destination_address_id UUID; + is_pickup_oconus BOOLEAN; + is_destination_oconus BOOLEAN; + service_item RECORD; +BEGIN + -- get shipment type, market code, move_id, and address IDs based on shipment_id + SELECT ms.shipment_type, ms.market_code, ms.move_id, ms.pickup_address_id, ms.destination_address_id, ms.status + INTO s_type, m_code, move_id, pickup_address_id, destination_address_id, s_status + FROM mto_shipments ms + WHERE ms.id = shipment_id; + + IF s_type IS NULL OR m_code IS NULL THEN + RAISE EXCEPTION ''Shipment with ID % not found or missing required details.'', shipment_id; + END IF; + + IF s_status IN (''APPROVED'') THEN + RAISE EXCEPTION ''Shipment with ID % is already in APPROVED status'', shipment_id; + END IF; + + -- get the is_oconus values for both pickup and destination addresses - this determines POD/POE creation + is_pickup_oconus := get_is_oconus(pickup_address_id); + is_destination_oconus := get_is_oconus(destination_address_id); + + -- determine which service item to create based on shipment direction + -- first create the direction-specific service item (POEFSC or PODFSC) + IF is_pickup_oconus AND NOT is_destination_oconus THEN + -- Shipment is OCONUS to CONUS, create PODFSC item + FOR service_item IN + SELECT rsi.id, + rs.id AS re_service_id, + rs.service_location, + rsi.is_auto_approved + FROM re_service_items rsi + JOIN re_services rs ON rsi.service_id = rs.id + WHERE rsi.shipment_type = s_type + AND rsi.market_code = m_code + AND rs.code = ''PODFSC'' + AND rsi.is_auto_approved = true + LOOP + BEGIN + IF NOT does_service_item_exist(service_item.re_service_id, shipment_id) THEN + INSERT INTO mto_service_items ( + mto_shipment_id, + move_id, + re_service_id, + service_location, + status, + created_at, + updated_at, + approved_at + ) + VALUES ( + shipment_id, + move_id, + service_item.re_service_id, + service_item.service_location, + ''APPROVED''::service_item_status, + NOW(), + NOW(), + NOW() + ); + END IF; + EXCEPTION + WHEN OTHERS THEN + RAISE EXCEPTION ''Error creating PODFSC service item for shipment %: %'', shipment_id, SQLERRM; + END; + END LOOP; + ELSIF NOT is_pickup_oconus AND is_destination_oconus THEN + -- Shipment is CONUS to OCONUS, create POEFSC item + FOR service_item IN + SELECT rsi.id, + rs.id AS re_service_id, + rs.service_location, + rsi.is_auto_approved + FROM re_service_items rsi + JOIN re_services rs ON rsi.service_id = rs.id + WHERE rsi.shipment_type = s_type + AND rsi.market_code = m_code + AND rs.code = ''POEFSC'' + AND rsi.is_auto_approved = true + LOOP + BEGIN + IF NOT does_service_item_exist(service_item.re_service_id, shipment_id) THEN + INSERT INTO mto_service_items ( + mto_shipment_id, + move_id, + re_service_id, + service_location, + status, + created_at, + updated_at, + approved_at + ) + VALUES ( + shipment_id, + move_id, + service_item.re_service_id, + service_item.service_location, + ''APPROVED''::service_item_status, + NOW(), + NOW(), + NOW() + ); + END IF; + EXCEPTION + WHEN OTHERS THEN + RAISE EXCEPTION ''Error creating POEFSC service item for shipment %: %'', shipment_id, SQLERRM; + END; + END LOOP; + ELSE + RAISE EXCEPTION ''Invalid shipment direction for shipment %: Pickup is %CONUS, Destination is %CONUS.'', + shipment_id, is_pickup_oconus, is_destination_oconus; + END IF; + + -- create all other auto-approved service items, filtering out the POEFSC or PODFSC service items + FOR service_item IN + SELECT rsi.id, + rs.id AS re_service_id, + rs.service_location, + rsi.is_auto_approved + FROM re_service_items rsi + JOIN re_services rs ON rsi.service_id = rs.id + WHERE rsi.shipment_type = s_type + AND rsi.market_code = m_code + AND rsi.is_auto_approved = true + AND rs.code NOT IN (''POEFSC'', ''PODFSC'') + LOOP + BEGIN + IF NOT does_service_item_exist(service_item.re_service_id, shipment_id) THEN + INSERT INTO mto_service_items ( + mto_shipment_id, + move_id, + re_service_id, + service_location, + status, + created_at, + updated_at, + approved_at + ) + VALUES ( + shipment_id, + move_id, + service_item.re_service_id, + service_item.service_location, + ''APPROVED''::service_item_status, + NOW(), + NOW(), + NOW() + ); + End IF; + EXCEPTION + WHEN OTHERS THEN + RAISE EXCEPTION ''Error creating other service item for shipment %: %'', shipment_id, SQLERRM; + END; + END LOOP; +END; +' +LANGUAGE plpgsql; + +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'mto_service_item_type') THEN + CREATE TYPE mto_service_item_type AS ( + id uuid, + move_id uuid, + mto_shipment_id uuid, + re_service_id uuid, + created_at timestamptz, + updated_at timestamptz, + reason text, + pickup_postal_code text, + description text, + status public.service_item_status, + rejection_reason text, + approved_at timestamp, + rejected_at timestamp, + sit_postal_code text, + sit_entry_date date, + sit_departure_date date, + sit_destination_final_address_id uuid, + sit_origin_hhg_original_address_id uuid, + sit_origin_hhg_actual_address_id uuid, + estimated_weight int4, + actual_weight int4, + sit_destination_original_address_id uuid, + sit_customer_contacted date, + sit_requested_delivery date, + requested_approvals_requested_status bool, + customer_expense bool, + customer_expense_reason text, + sit_delivery_miles int4, + pricing_estimate int4, + standalone_crate bool, + locked_price_cents int4, + service_location public.service_location_enum, + poe_location_id uuid, + pod_location_id uuid +); +END IF; +END +$$; + + +CREATE OR REPLACE PROCEDURE create_accessorial_service_items_for_shipment ( + IN shipment_id UUID, + IN service_items mto_service_item_type[] +) AS ' +DECLARE + s_type mto_shipment_type; + m_code market_code_enum; + move_id UUID; + service_item RECORD; + item mto_service_item_type; +BEGIN + -- get the shipment type, market code, and move_id based on shipment_id + SELECT ms.shipment_type, ms.market_code, ms.move_id + INTO s_type, m_code, move_id + FROM mto_shipments ms + WHERE ms.id = shipment_id; + + IF s_type IS NULL OR m_code IS NULL THEN + RAISE EXCEPTION ''Shipment with ID % not found or missing required details.'', shipment_id; + END IF; + + IF s_type <> item.shipment_type THEN + RAISE EXCEPTION ''Shipment type mismatch. Expected %, but got %.'', s_type, item.shipment_type; + END IF; + + -- loop through each provided service item object + FOREACH item IN ARRAY service_items + LOOP + FOR service_item IN + SELECT rsi.id, + rs.id AS re_service_id, + rs.service_location, + rsi.is_auto_approved, + rs.code AS service_code + FROM re_service_items rsi + JOIN re_services rs ON rsi.service_id = rs.id + WHERE rsi.shipment_type = s_type + AND rsi.market_code = m_code + AND rs.id = (item.re_service_id) + AND rsi.is_auto_approved = false + LOOP + BEGIN + IF NOT does_service_item_exist(service_item.re_service_id, shipment_id) THEN + INSERT INTO mto_service_items ( + mto_shipment_id, + move_id, + re_service_id, + service_location, + status, + created_at, + updated_at, + sit_postal_code, + sit_entry_date, + sit_customer_contacted, + reason, + estimated_weight, + actual_weight, + pickup_postal_code, + description, + sit_destination_original_address_id, + sit_destination_final_address_id, + sit_requested_delivery, + sit_departure_date, + sit_origin_hhg_original_address_id, + sit_origin_hhg_actual_address_id, + customer_expense, + customer_expense_reason, + sit_delivery_miles, + standalone_crate + ) + VALUES ( + shipment_id, + move_id, + service_item.re_service_id, + service_item.service_location, + ''SUBMITTED''::service_item_status, + NOW(), + NOW(), + (item).sit_postal_code, + (item).sit_entry_date, + (item).sit_customer_contacted, + (item).reason, + (item).estimated_weight, + (item).actual_weight, + (item).pickup_postal_code, + (item).description, + (item).sit_destination_original_address_id, + (item).sit_destination_final_address_id, + (item).sit_requested_delivery, + (item).sit_departure_date, + (item).sit_origin_hhg_original_address_id, + (item).sit_origin_hhg_actual_address_id, + (item).customer_expense, + (item).customer_expense_reason, + (item).sit_delivery_miles, + (item).standalone_crate + ); + END IF; + EXCEPTION + WHEN OTHERS THEN + RAISE EXCEPTION ''Error creating accessorial service item with code % for shipment %: %'', + service_item.service_code, shipment_id, SQLERRM; + END; + END LOOP; + END LOOP; +END; +' +LANGUAGE plpgsql; diff --git a/migrations/app/schema/20241119163933_set_inactive_NSRA15_oconus_rate_areas.up.sql b/migrations/app/schema/20241119163933_set_inactive_NSRA15_oconus_rate_areas.up.sql new file mode 100644 index 00000000000..bd6b9e404d3 --- /dev/null +++ b/migrations/app/schema/20241119163933_set_inactive_NSRA15_oconus_rate_areas.up.sql @@ -0,0 +1,288 @@ +-- Set re_oconus_rate_area to active=false per duty location IDs +update public.re_oconus_rate_areas set active = false +where id in ( +select + distinct o.id as oconus_rate_area_id +from + duty_locations d, + addresses a, + v_locations v, + re_oconus_rate_areas o, + re_rate_areas ra +where + d.id in ( + 'bfec82a4-b46f-453b-8b88-c588a6954186', + '3a94f6a8-c2b8-4c95-8d8a-b6ff4573cdbf', + '3fafe753-4ef0-40d0-ad11-0b6603a14ac3', + '86326958-46f1-464e-9fb0-240f479ca06d', + '9c890b8c-77ec-458e-b8a2-f50feb0ea344', + '2475aec9-2cea-4059-82ba-8347363cf0b8', + '619dbdf4-b8e0-4dbe-9876-9cdc9f809049', + 'ef9c390c-8b3e-4c4b-bbe6-d1ad086398c0', + '8c57f17a-29a0-49e0-945a-b9b5abc75bfd', + '1507ad69-7d74-44a3-8359-0e14f12e0a2b', + 'bfcc9ba0-e378-4d4f-a2da-1871e32be45f', + 'b5f2c454-37b3-4529-9db1-cd44f2fa26c4', + '82ef1e92-1507-4124-8b37-8d58ddbbbf91', + '75e3c0c2-b6a3-4100-9396-918fdbdf1018', + 'a93ebf02-38a0-4730-8ed9-969a7a799b55', + '75faec85-1031-4e87-9fc3-96ce68e29350', + 'e057a5cf-da57-48e3-a37b-db1e55239a60', + 'b5af67cb-d727-4430-93f9-d3c2759e0330', + '66db95df-7e88-45dc-ad9f-e9c6618171ca', + '12dbb585-3c54-44e6-8a0c-32f0fbc9deb0', + 'c4b182c5-fb92-4103-a513-dab4535d0168', + 'faf102ef-d5a4-4b2c-a0b3-cc9cef116c86', + '14d2f648-551b-4d76-bcff-5629addbdd4a', + '32fe36df-5b9b-4121-8674-47cf34ba9af8', + '888c25a8-3f64-406a-98d2-d9f6dab4c154', + '91417251-bf6d-4147-aa0f-b8ef85193d79', + '627d448b-8d66-4b6a-9123-84f60a5ffc6f', + 'a59480ee-af9c-49d3-a366-72e23a53abe6', + '0f9cc96f-5fb9-40e7-9bd2-51537246a30f', + 'e02520be-da87-4aed-b10a-dbf4aed85bcd', + 'c4140981-c84a-4591-bd00-b5de8a94b817', + '6cd8c14c-1ee9-4807-9c09-280975e82152', + 'f22d70d6-8164-4daa-a519-79740b4b5f76', + '8a477007-fda2-47ef-aa16-2997f8fdb84b', + 'f3016aa0-7d78-451c-af83-54ef2125e3ec', + 'ddef9079-686f-4e8c-a051-2e154fa92a89', + '260ae74c-89d5-4be2-a7f6-e9f3908f305e', + '14502920-775c-4541-86a1-4538ca7764d3', + '90a607d0-aed9-4c31-a8a2-8496d39d6599', + '83a66bba-6a41-4e64-a7b5-039ee8bd30b9', + '2ed108a7-0796-488e-b712-543517262f33', + '29fc338d-2113-4006-b6ff-cea1682cd533', + '1e87bad6-7e23-481c-be5d-138aecffb69b', + '5b40a43a-0eab-4e9e-999a-bba234510937', + 'd94d35c6-4445-455e-bd6c-4895e81766d6', + 'ad8f5298-c1b0-4db6-8692-3e6f1ba248d2', + '3450ff6c-672a-4cb8-ada7-f3087b3abb21', + '65d47305-1552-4b6b-9769-2dfdddd6c0bb', + '2c9bb970-024b-4264-b757-3836beb97b7f', + '07eb7b9e-b087-46b0-b9c7-73566fa46e26', + '33fbd770-488d-47e2-8f96-5ffb9fc44e31', + '2e7373b8-d6d9-42bc-b346-3713338b1c0d', + '60b106c9-be36-41ba-bcb9-ff10b52f9452', + '15fe1e07-ee3b-4bc1-a093-3cbfbf58c16c', + '34743c0d-5d22-4124-bd7b-acbeb8f97b7b', + 'ccc1429a-ca38-43d8-83a9-24689aa65fe7', + '235b92e6-79c9-4e0b-8048-2244c4ea2b89', + 'd7fa1c78-27e2-41ab-aace-41bca71047df', + '6eea654a-47cd-44ab-8e3b-d58e9aa6581f', + 'f8fe22ee-ea42-415a-b7f6-bd5771f00ad7', + '82adb209-a389-4e4d-ac86-274cad9ed558', + 'b2821ccd-3b8e-41a7-ad4c-f3e3681567c9', + '642dae8b-b31e-4985-adea-fe9d73d14aaa', + 'a169d3aa-3cdc-4bed-a173-3c7b14533c8b', + 'a75cc57c-9357-4616-82ee-d66c88bc7457', + '5a3ee0f8-722d-4c33-b1d2-980170ccb0e7', + '346c9565-408a-46e7-a3d1-962c8dc939a8', + '2c34dd30-a560-4027-8661-e9ce18a4c1a5', + 'ffcc6c1e-99c9-4182-84a0-38c446c53c2c', + '05c7338b-28a7-4d11-98ff-5f23f4e0367b', + 'f83d3521-46b1-4d9b-a175-fdfff0a38ac1', + 'bb819377-4d54-408b-9411-8f04018aba02', + '7b0e14f5-6ad2-428c-b723-ef7564952c47', + '35ab4f71-a488-4e6e-b9d4-285d7b2b426a', + '0fda5d86-25af-4150-a299-ef35270632c5', + 'd90bf455-fc93-4bd1-91a3-a055b784bb37', + '2fa123b0-b260-4166-ab2a-bae912c85220', + '101ff19d-6778-4ea0-a4b3-7d48f7910b56', + 'e3aaaa30-ce30-475f-8f78-fde88863f2a4', + '7f2c1e8d-b107-4053-b9fe-c9fc2af45968', + '693fd3e9-8633-4777-bff0-7a2086ad3a38', + '8eb81d70-a8b8-47c5-b15f-1900c4c47590', + 'c9c4529e-0f81-431f-8f95-5704a5aeccb2', + 'a5f66fe8-3d22-4e9f-aed6-18f15dcec515', + '424faeaf-aa7b-401b-a01e-4d1d605d7236', + '9019c615-e62d-44c5-8050-80d12e0beca5', + 'd1a716d3-1a81-486c-ba56-3905211edcc1', + '73e33e39-8a8f-4f2a-a3f9-cff190d057f7', + '58688401-8e78-4134-aafb-076dff5d2ad4', + '418217b4-52d1-4aac-82ab-9f8969363e41', + '56e288b1-1dfe-4d71-a74b-98d2c821c75c', + 'fd2d20f9-df85-4269-a720-90bdfd48f8c4', + '52fe7ba3-a131-4b60-acc9-332ebee6d8d4', + '020fe814-5bc2-4355-8d3a-e6b6403effc3', + '4b738a07-7211-40ad-8f19-93fc1001e10c', + '45ed75fa-80ba-4c3f-b4a4-79bf5ca0a0ce', + 'd4e25052-ffc8-4367-978c-cc12a46b8363', + 'a76602f3-7e60-4772-8338-e97a4c5ea93d', + '4052a34c-1259-43e2-a769-32e31cf6bfaa', + '1c3a7fc5-ab78-4386-b17c-85255597791d', + '7f452190-055e-47a8-8042-d723f5c0a3f1', + 'f483dfe5-1d6d-4d86-a4be-247f0dd8fec4', + 'aca6d338-0dd2-4c1c-9833-c3e22e21647d', + '4718c8cc-74aa-491d-9d96-e48facc41b55', + '5ef421e6-fc6d-4285-a0fd-90019c38b231', + 'af16181e-4d54-4c4a-9c59-774c650b4965', + '0802dcbb-0130-43bc-ba6f-453854bafb3e', + 'f7d4ada3-aca2-4968-a76c-4c544836518e', + '25eed791-1ecd-4418-bd3f-0912687d51ce', + 'e3c5dd34-b2eb-4655-b287-f78907364c31', + 'a2cb9e0e-e605-47ee-a925-deda970b2a17', + '61194d2d-ce9f-4a2e-b82a-b00be78f888b', + 'd9e59b38-0312-4a8a-b79a-ec1489538203', + '4a8523b4-8616-4f09-9dfe-8832bbd80fef', + '4397e60f-1d3b-4c83-bad5-0fbc1e20a487', + '45dccbc8-f68a-4616-b5ca-c52a3363b680', + '3b3807a3-df7d-44f4-b144-76fb902d6af2', + 'd3c82c58-bd1a-46b1-8e5a-026ef984e60e', + '1757b6c6-e2ca-417f-84cd-08079b2957ed', + 'fee7e6fc-8f10-46e5-96b1-20219f92bd6b', + '656a34a6-11d4-4af3-9975-cfe77d99ed56', + '25864c23-4dba-4a82-a5ef-e6609a66f1cc', + 'b4d817d9-1be2-415d-8386-0a13fc263b90', + '66881121-712d-451f-8e8b-14716155b5bb', + '03a7d3e0-6750-4dc8-a315-2c2282cdb7f9', + '39e52b41-8399-4820-b524-9796942cf3ca', + 'eb17c65e-92a5-474b-b54a-93027055e29b', + '2fac6dd5-14c1-4477-b9b0-cea2e5da388f', + 'f18bfad8-bf61-4784-a9b7-7800a7aba82e', + 'f3fc42af-aaf1-4987-b3c6-34f152a27e0c', + 'e2a4d85b-894a-4e7a-8e9c-c849e972e3be', + '6ef3dced-37a3-47f6-9c81-3bfaf2cc21d5', + 'a73613c5-8482-4451-b541-1f29f5b87845', + 'ec0771d6-7915-43d5-bc56-77be096432c7', + '50b8a513-0de9-4fbd-9f46-34e03ea8b1b4', + '14c05822-3692-4472-ad42-95c56d216554', + '6f96a484-dba0-41e1-921e-c093e364216d', + 'c165daf5-5106-4b3a-8ef1-a9106ecb551e', + '3596bdbb-7c8a-4067-bf87-1636a062a401', + '61c5d94e-4840-4860-9e20-156df9f5e81d', + 'ad5b93fe-113c-435c-b875-344f67a9ebdf', + '5d77f8b5-dcfc-41b1-9ddf-f5627c5d588d', + '9ba7f886-51d2-46b8-aa1d-510c65cddba3', + 'b144a90b-1848-4fae-98a2-a1bad1c58161', + '4a1875d8-80c1-4dbf-aad8-0719da050697', + 'af895e5f-e649-472a-898e-a7b6ee575b5f', + '9da9b122-329a-4593-bd45-34b9bf8154c8', + '6a34a374-0db6-45af-b052-b8513d2bd47c', + '116e37a6-ca0a-45be-8945-0602918bec8e', + '45d120e8-65a0-4120-a413-719ed1891cbe', + '95f23779-67f3-46bd-8a7e-4760a39373c7', + 'd5665968-23bc-42d7-a56a-1bc021e87f45', + '57dd859f-1e2f-49a5-8604-02e328771d85', + 'ccac0df8-0dd2-4978-ad74-b888269b7570', + '389f5341-ce5b-4150-a538-db2aea57776b', + 'dd15411f-6fa5-435b-9c84-425ed2672b5b', + '19fe9597-66f0-4429-ad80-7d0c1b06e1d2', + 'd1201b88-bde1-46cb-8359-618ee71e9ac1', + '02138825-265d-4b33-ae5e-5bcc760b7fdb', + '9e6d0e3e-8ac6-4a7e-96ba-731ede624553', + '2ea7f3d5-6551-4686-8413-a505f419f234', + 'a007afc5-362d-4867-b3dd-702ecde9ce4f', + '15247dc2-0821-4bc7-a083-05dab7827405', + '3c7f2954-0ac8-4893-aac9-2d9b5805fe82', + '4a1959e3-ab4e-4a75-8bf8-daa5e0b87a8d', + '09aea2e2-a5fd-40a8-bb58-bb3f37bd0a8d', + '3c9526b0-9d10-4bab-ac37-f107244cbc91', + 'dc0b1028-a46f-46b8-ba97-cb0cbf36597c', + '9e39b77c-7aae-4ac4-b1b7-1230713a43a3', + '380e1a3d-8fee-4d9e-b710-d4eba6908428', + 'd182f2dc-3dcb-4c05-b324-5068992de6f8', + '518df5fd-d989-455c-b93b-d8328d0b2719', + '5b5be2bc-4bc7-4fd9-9230-d5eea30fade4', + '02a2c910-f4e4-4355-bc42-301ec198011b', + 'abbcf3bb-bcf7-4ef9-bd8d-32967e2be2bf', + '4b754509-0120-4c24-95b7-728d4fcb88b3', + 'bbfe581f-b531-461e-8eed-a9a373418f63', + '39800864-a740-4447-9077-6a081cb54bfd', + '69081a90-e959-4a39-81f4-5d8299b31a97', + 'a9465b37-a570-43de-9915-e99a8671e93d', + '91cbe420-63b3-4eda-9894-d9b3455c9546', + '5284663f-0ed4-410b-8754-81110eeeca02', + '4e339cc4-bdc9-4296-afb9-1f9914e751f8', + '77d23eeb-8dce-4cae-a0fa-9204929b22d9', + 'c5935c65-c15b-44c3-b78b-1de2fb78b14f', + 'fa636bdd-e0a4-425d-99fe-ffb246d7db95', + '7727ddce-9f88-442e-8c1d-96696b9e9ed1', + 'd25ead47-3d4c-4c43-9155-b6e8f2604d92', + 'a28c2b3f-a777-4fb7-b25c-8bb6d6438934', + '0d99124d-8b12-435c-a178-e9860470aed6', + '116fe4d9-bc44-4046-905a-4a63ce15acf1', + 'f83b74f0-b2f8-4311-9433-2240dc9d7445', + '3a592bfb-5a3c-408c-99be-cb3bfac9f829', + '9d9b4e20-ad96-4392-99c4-93555399fc5f', + '1a4ac0ad-d717-4284-8a9c-524145cd5f00', + 'cd1b574b-cdeb-46ae-9241-f10768d718e2', + '14ac646b-1e54-440b-bf0c-c1f50043e6f0', + 'ffe4312d-fe95-459f-b5d9-a2eafe725242', + '8a9e24fa-c11f-4233-96c7-3a049414a92f', + '2127ad64-bf8e-4082-a840-75b3200ef7ae', + '711ec6f0-1d87-4e0e-b444-bf52b4595050', + '4bc0b824-c4f3-4793-9c76-fc5b18893cf8' + ) + and d.address_id = a.id + and a.us_post_region_cities_id = v.uprc_id + and v.uprc_id = o.us_post_region_cities_id + and o.rate_area_id = ra.id +); + +-- insert missing rateArea to oconusRateArea mappings for 18 unaccounted duty locations - B-21608 +INSERT INTO re_oconus_rate_areas (id,rate_area_id,country_id,us_post_region_cities_id,created_at,updated_at,active) VALUES + ('f0b20201-193a-4de5-8f02-7308131787d5'::uuid,'3ec11db4-f821-409f-84ad-07fc8e64d60d'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'e88c70c6-61a6-4b0d-b64a-ee3b16c99199'::uuid,'2024-11-19 13:52:01.200682','2024-11-19 13:52:01.200682',false), + ('a9c5824f-c099-49a1-aada-be8200c68599'::uuid,'3ec11db4-f821-409f-84ad-07fc8e64d60d'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'b030c225-ad1f-4062-8da5-22d9523a0b46'::uuid,'2024-11-19 13:52:01.200682','2024-11-19 13:52:01.200682',false), + ('4d7eef69-59f4-4e22-a011-44df6b77d723'::uuid,'3ec11db4-f821-409f-84ad-07fc8e64d60d'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'874fb615-51a3-46f7-90d0-53837b491cc8'::uuid,'2024-11-19 13:52:01.200682','2024-11-19 13:52:01.200682',false), + ('fdaaf92e-415e-4b88-ad24-10f564eacc90'::uuid,'3ec11db4-f821-409f-84ad-07fc8e64d60d'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'885a18b9-5fed-4a5d-a62b-3f4e6ca0f3c5'::uuid,'2024-11-19 13:52:01.200682','2024-11-19 13:52:01.200682',false), + ('f34e2068-6cbb-4fb7-b76a-c2d63cc78bd1'::uuid,'3ec11db4-f821-409f-84ad-07fc8e64d60d'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'b7dfe9ac-7081-4c52-a80b-cc8d79a185b4'::uuid,'2024-11-19 13:52:01.200682','2024-11-19 13:52:01.200682',false), + ('b9f7071d-2db2-4c8b-b401-c642ca6c9c96'::uuid,'3ec11db4-f821-409f-84ad-07fc8e64d60d'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'5706dc1f-0343-49b4-ad9d-aafc20015404'::uuid,'2024-11-19 13:52:01.200682','2024-11-19 13:52:01.200682',false), + ('300f1d37-f387-41a0-9a06-fdaf4628e7ae'::uuid,'3ec11db4-f821-409f-84ad-07fc8e64d60d'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'42fbf978-5478-4a2f-841a-049de1ccceab'::uuid,'2024-11-19 13:52:01.200682','2024-11-19 13:52:01.200682',false), + ('fff4d0dd-e180-476b-91ba-331393ef44b8'::uuid,'3ec11db4-f821-409f-84ad-07fc8e64d60d'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'abe35bbf-05af-4a2a-bc75-0a08e2e4da6b'::uuid,'2024-11-19 13:52:01.200682','2024-11-19 13:52:01.200682',false), + ('e4bc9404-5466-4a41-993e-09474266afc3'::uuid,'5a27e806-21d4-4672-aa5e-29518f10c0aa'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'cc211f28-f894-43e4-908e-f3af2fbea535'::uuid,'2024-11-19 13:52:42.175627','2024-11-19 13:52:42.175627',true), + ('d4a51d90-3945-4ad3-9cba-a18d8d7b34d7'::uuid,'5a27e806-21d4-4672-aa5e-29518f10c0aa'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'121b6c21-464f-4069-9b2a-16955b57fc30'::uuid,'2024-11-19 13:52:42.175627','2024-11-19 13:52:42.175627',true); +INSERT INTO re_oconus_rate_areas (id,rate_area_id,country_id,us_post_region_cities_id,created_at,updated_at,active) VALUES + ('a5a60d63-d9a8-4bde-9081-f011784b2d31'::uuid,'5a27e806-21d4-4672-aa5e-29518f10c0aa'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'e2cb041d-95fa-47b7-8932-c59f1a31f1ac'::uuid,'2024-11-19 13:52:42.175627','2024-11-19 13:52:42.175627',true), + ('93842d74-1f3e-46cd-aca9-9f0dafbd20a1'::uuid,'5a27e806-21d4-4672-aa5e-29518f10c0aa'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'74f1cbd6-e278-4a20-94da-c7752900e743'::uuid,'2024-11-19 13:52:42.175627','2024-11-19 13:52:42.175627',true), + ('ca8aecda-4642-45c7-96ed-309c35c4b78f'::uuid,'b80a00d4-f829-4051-961a-b8945c62c37d'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'bf5978ad-ca09-4e30-9891-b3e8d4754941'::uuid,'2024-11-19 13:53:12.478276','2024-11-19 13:53:12.478276',true), + ('1bc0dbda-f0ce-4b76-a551-78dbaaa9e3ec'::uuid,'635e4b79-342c-4cfc-8069-39c408a2decd'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'be83670d-32a5-4c9c-9479-48204462c20a'::uuid,'2024-11-19 13:53:12.478276','2024-11-19 13:53:12.478276',true), + ('f1a7ef90-cfa6-4e0c-92c3-f8d70c07ba4d'::uuid,'635e4b79-342c-4cfc-8069-39c408a2decd'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'dfc8d1c7-77ab-4227-87c8-618e1b876f51'::uuid,'2024-11-19 13:53:12.478276','2024-11-19 13:53:12.478276',true), + ('9c5b4c4d-e05c-42ca-bd77-b61f5d8c7afc'::uuid,'635e4b79-342c-4cfc-8069-39c408a2decd'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'817a2d4e-980d-4a43-a151-ff01393b882d'::uuid,'2024-11-19 13:53:12.478276','2024-11-19 13:53:12.478276',true), + ('27ec2576-78dd-4605-b1a8-0b9ca207fc26'::uuid,'635e4b79-342c-4cfc-8069-39c408a2decd'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'a7dad15e-7ce4-4f59-9509-242c7aa0a32b'::uuid,'2024-11-19 13:53:12.478276','2024-11-19 13:53:12.478276',true), + ('12396ebc-59e9-430a-8475-759a38af6b7a'::uuid,'635e4b79-342c-4cfc-8069-39c408a2decd'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'e27c8487-78c5-4b21-8040-df93b5e7375a'::uuid,'2024-11-19 13:53:12.478276','2024-11-19 13:53:12.478276',true), + ('79563390-7962-485b-b20c-680c84f6693d'::uuid,'3ec11db4-f821-409f-84ad-07fc8e64d60d'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'b38881d0-0606-44c1-a064-ef0253edb117'::uuid,'2024-11-20 12:25:37.471462','2024-11-20 12:25:37.471462',false), + ('da82db8a-660b-4188-a1e7-4f10267475f3'::uuid,'3ec11db4-f821-409f-84ad-07fc8e64d60d'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'d21e8533-7816-4467-9cd8-21587a94fc9f'::uuid,'2024-11-20 12:25:37.471462','2024-11-20 12:25:37.471462',false); +INSERT INTO re_oconus_rate_areas (id,rate_area_id,country_id,us_post_region_cities_id,created_at,updated_at,active) VALUES + ('db33fcad-dd6b-4126-b2b3-267732446c19'::uuid,'3ec11db4-f821-409f-84ad-07fc8e64d60d'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'70239ed6-8c72-4378-a312-deaa8375dddb'::uuid,'2024-11-20 12:25:37.471462','2024-11-20 12:25:37.471462',false), + ('78ee3682-163a-4e2e-9ef1-a838a7a8c3fb'::uuid,'3ec11db4-f821-409f-84ad-07fc8e64d60d'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'63769bda-d65c-46eb-ba57-9ddbabc30c86'::uuid,'2024-11-20 12:25:37.471462','2024-11-20 12:25:37.471462',false), + ('d8f53f2b-2bd2-42fa-98e4-b7b292b41c97'::uuid,'3ec11db4-f821-409f-84ad-07fc8e64d60d'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'130102ea-f9fd-4f0e-bd73-cfcc85887456'::uuid,'2024-11-20 12:25:37.471462','2024-11-20 12:25:37.471462',false), + ('d0ca4dbb-5b22-4b56-9793-c536c37e37d4'::uuid,'3ec11db4-f821-409f-84ad-07fc8e64d60d'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'8e9e96cc-33ae-4b08-a7fc-d71718b65b16'::uuid,'2024-11-20 12:25:37.471462','2024-11-20 12:25:37.471462',false), + ('2e40d1fb-e427-481d-8bc6-4d671ae25325'::uuid,'3ec11db4-f821-409f-84ad-07fc8e64d60d'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'68ad8373-4263-4309-9eca-369edf790ad6'::uuid,'2024-11-20 12:25:37.471462','2024-11-20 12:25:37.471462',false), + ('6281967c-30a8-40be-92ea-1a287b9a6a39'::uuid,'3ec11db4-f821-409f-84ad-07fc8e64d60d'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'5268f013-9b86-49e6-a444-528ebdd7a884'::uuid,'2024-11-20 12:25:37.471462','2024-11-20 12:25:37.471462',false), + ('ce1899f0-2e44-4f6f-ae9b-c5d3a5c7beb0'::uuid,'3ec11db4-f821-409f-84ad-07fc8e64d60d'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'789460b8-e419-4903-8d7e-cf9867b0ade5'::uuid,'2024-11-20 12:25:37.471462','2024-11-20 12:25:37.471462',false), + ('a8df190f-d810-4b5b-878f-4eec089c1ed3'::uuid,'3ec11db4-f821-409f-84ad-07fc8e64d60d'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'e5b4b47c-4f6e-458b-a379-e3b0ab621be2'::uuid,'2024-11-20 12:25:37.471462','2024-11-20 12:25:37.471462',false), + ('dc606f1a-5671-48a1-9f86-2dd10b57d825'::uuid,'3ec11db4-f821-409f-84ad-07fc8e64d60d'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'beb84a41-a103-479b-be1f-b2e9ef1ee628'::uuid,'2024-11-20 12:25:37.471462','2024-11-20 12:25:37.471462',false), + ('1bf18f91-43c6-4d77-b5eb-9c864486166f'::uuid,'3ec11db4-f821-409f-84ad-07fc8e64d60d'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'a13303d5-ac62-4017-8d90-54df5cdabb17'::uuid,'2024-11-20 12:25:37.471462','2024-11-20 12:25:37.471462',false); +INSERT INTO re_oconus_rate_areas (id,rate_area_id,country_id,us_post_region_cities_id,created_at,updated_at,active) VALUES + ('0f4215bf-0f86-4ff2-a457-353ab29e4ebd'::uuid,'3ec11db4-f821-409f-84ad-07fc8e64d60d'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'366364cf-5056-4a64-90d1-c2622c6a6ef4'::uuid,'2024-11-20 12:25:37.471462','2024-11-20 12:25:37.471462',false), + ('b7a481d4-b86b-40fe-8ccb-af15e9e0fc63'::uuid,'3ec11db4-f821-409f-84ad-07fc8e64d60d'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'5e599c21-c87c-44c7-979b-174d421e8168'::uuid,'2024-11-20 12:25:37.471462','2024-11-20 12:25:37.471462',false), + ('e0684af6-b647-4d83-9ddb-87625dac1293'::uuid,'3ec11db4-f821-409f-84ad-07fc8e64d60d'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'a4c6b412-ff96-4f70-9fcb-b5e831dc0fec'::uuid,'2024-11-20 12:25:37.471462','2024-11-20 12:25:37.471462',false), + ('10ad028f-66ae-4f6a-9beb-fdf88928a9f4'::uuid,'3ec11db4-f821-409f-84ad-07fc8e64d60d'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'3d781b7f-5c76-465d-b6f7-350e697008de'::uuid,'2024-11-20 12:25:37.471462','2024-11-20 12:25:37.471462',false), + ('2ddd300f-bd46-4f33-b132-b9479df18b56'::uuid,'3ec11db4-f821-409f-84ad-07fc8e64d60d'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'55de46ce-abca-4c4b-b828-14791a1cb330'::uuid,'2024-11-20 12:25:37.471462','2024-11-20 12:25:37.471462',false), + ('9a8d2146-cd56-40b9-9a1a-2d27a20fd4fd'::uuid,'3ec11db4-f821-409f-84ad-07fc8e64d60d'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'e9422bc8-9ea3-43db-b3ae-68c52204fe94'::uuid,'2024-11-20 12:25:37.471462','2024-11-20 12:25:37.471462',false), + ('d5639dd4-a25d-4d41-bdf2-65047c4ec183'::uuid,'3ec11db4-f821-409f-84ad-07fc8e64d60d'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'6d4a3a08-109d-496f-9df7-a9991c82fcb7'::uuid,'2024-11-20 12:25:37.471462','2024-11-20 12:25:37.471462',false), + ('115b48cb-7162-4de1-abb1-150f019d5b58'::uuid,'3ec11db4-f821-409f-84ad-07fc8e64d60d'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'d0cbe916-a82d-4d1d-a4fb-6cc480cd604d'::uuid,'2024-11-20 12:25:37.471462','2024-11-20 12:25:37.471462',false), + ('7228cc3e-fc07-4bf7-8071-f3851ccad0d0'::uuid,'3ec11db4-f821-409f-84ad-07fc8e64d60d'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'aae2e6ff-3737-4d04-a825-26b784de197c'::uuid,'2024-11-20 12:25:37.471462','2024-11-20 12:25:37.471462',false), + ('c5f3a7b8-e852-40c7-aa49-b4880924de95'::uuid,'3ec11db4-f821-409f-84ad-07fc8e64d60d'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'91948b7f-d890-488d-a496-f1cf6e71faad'::uuid,'2024-11-20 12:25:37.471462','2024-11-20 12:25:37.471462',false); +INSERT INTO re_oconus_rate_areas (id,rate_area_id,country_id,us_post_region_cities_id,created_at,updated_at,active) VALUES + ('3852f493-e648-4648-8846-448ca7eb90f6'::uuid,'3ec11db4-f821-409f-84ad-07fc8e64d60d'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'841722ca-4c24-4994-a037-46a16354eb8a'::uuid,'2024-11-20 12:25:37.471462','2024-11-20 12:25:37.471462',false); + +-- re_oconus_rate_area to gbloc_aors mappings +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('b38f098d-e8a8-437a-a887-72b8ccb2661e'::uuid,'98281c1b-6161-4e74-a119-177b2a2fb176'::uuid,'ca8aecda-4642-45c7-96ed-309c35c4b78f'::uuid,NULL,NULL,true,'2024-11-20 12:40:12.949739','2024-11-20 12:40:12.949739'), + ('2b43f91c-16d7-468a-8d3c-6f8cd3454f41'::uuid,'615673a2-d393-4d46-95f9-705fbeb6bc79'::uuid,'e4bc9404-5466-4a41-993e-09474266afc3'::uuid,NULL,NULL,true,'2024-11-20 12:40:22.745108','2024-11-20 12:40:22.745108'), + ('d5a51fd6-7108-4475-a61c-0b00f6fe1855'::uuid,'615673a2-d393-4d46-95f9-705fbeb6bc79'::uuid,'d4a51d90-3945-4ad3-9cba-a18d8d7b34d7'::uuid,NULL,NULL,true,'2024-11-20 12:40:22.745108','2024-11-20 12:40:22.745108'), + ('49a6bb4c-a770-4c1b-9fcb-eb358c7592fe'::uuid,'615673a2-d393-4d46-95f9-705fbeb6bc79'::uuid,'a5a60d63-d9a8-4bde-9081-f011784b2d31'::uuid,NULL,NULL,true,'2024-11-20 12:40:22.745108','2024-11-20 12:40:22.745108'), + ('863a25c7-dbd2-433e-8561-970a33abc0de'::uuid,'615673a2-d393-4d46-95f9-705fbeb6bc79'::uuid,'93842d74-1f3e-46cd-aca9-9f0dafbd20a1'::uuid,NULL,NULL,true,'2024-11-20 12:40:22.745108','2024-11-20 12:40:22.745108'), + ('56661aa4-fa9c-413b-8ef8-72ae05b40c1d'::uuid,'98281c1b-6161-4e74-a119-177b2a2fb176'::uuid,'e4bc9404-5466-4a41-993e-09474266afc3'::uuid,'AIR_AND_SPACE_FORCE',NULL,true,'2024-11-20 12:40:26.472072','2024-11-20 12:40:26.472072'), + ('d9384d9f-9f5e-460f-973b-0f457dc87d79'::uuid,'98281c1b-6161-4e74-a119-177b2a2fb176'::uuid,'d4a51d90-3945-4ad3-9cba-a18d8d7b34d7'::uuid,'AIR_AND_SPACE_FORCE',NULL,true,'2024-11-20 12:40:26.472072','2024-11-20 12:40:26.472072'), + ('54f9fc52-0f4c-4024-aa2b-aff17c31fe5c'::uuid,'98281c1b-6161-4e74-a119-177b2a2fb176'::uuid,'a5a60d63-d9a8-4bde-9081-f011784b2d31'::uuid,'AIR_AND_SPACE_FORCE',NULL,true,'2024-11-20 12:40:26.472072','2024-11-20 12:40:26.472072'), + ('368e0357-94b1-4ea7-9a8b-98720b0f08fe'::uuid,'98281c1b-6161-4e74-a119-177b2a2fb176'::uuid,'93842d74-1f3e-46cd-aca9-9f0dafbd20a1'::uuid,'AIR_AND_SPACE_FORCE',NULL,true,'2024-11-20 12:40:26.472072','2024-11-20 12:40:26.472072'), + ('5697fb28-3f67-449f-bac2-48da92d26886'::uuid,'58510246-905b-4989-b16e-04806904134b'::uuid,'1bc0dbda-f0ce-4b76-a551-78dbaaa9e3ec'::uuid,NULL,NULL,true,'2024-11-20 12:40:29.700158','2024-11-20 12:40:29.700158'); +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('a3db6d33-6a4c-4357-9735-bdf35295f6ce'::uuid,'58510246-905b-4989-b16e-04806904134b'::uuid,'f1a7ef90-cfa6-4e0c-92c3-f8d70c07ba4d'::uuid,NULL,NULL,true,'2024-11-20 12:40:29.700158','2024-11-20 12:40:29.700158'), + ('34938294-0921-4aa4-8e8c-97b40f747f57'::uuid,'58510246-905b-4989-b16e-04806904134b'::uuid,'9c5b4c4d-e05c-42ca-bd77-b61f5d8c7afc'::uuid,NULL,NULL,true,'2024-11-20 12:40:29.700158','2024-11-20 12:40:29.700158'), + ('8747b22c-85ba-4ecc-854a-368a4964b504'::uuid,'58510246-905b-4989-b16e-04806904134b'::uuid,'27ec2576-78dd-4605-b1a8-0b9ca207fc26'::uuid,NULL,NULL,true,'2024-11-20 12:40:29.700158','2024-11-20 12:40:29.700158'), + ('e9548498-d32d-4ac4-aa77-611686784a1c'::uuid,'58510246-905b-4989-b16e-04806904134b'::uuid,'12396ebc-59e9-430a-8475-759a38af6b7a'::uuid,NULL,NULL,true,'2024-11-20 12:40:29.700158','2024-11-20 12:40:29.700158'); + diff --git a/migrations/app/schema/20241120221040_change_port_location_fk_to_correct_table.up.sql b/migrations/app/schema/20241120221040_change_port_location_fk_to_correct_table.up.sql new file mode 100644 index 00000000000..be8040d2875 --- /dev/null +++ b/migrations/app/schema/20241120221040_change_port_location_fk_to_correct_table.up.sql @@ -0,0 +1,5 @@ +--POE Location columns should reference PORT_LOCATIONS table +ALTER TABLE mto_service_items DROP CONSTRAINT fk_poe_location_id; +ALTER TABLE mto_service_items DROP CONSTRAINT fk_pod_location_id; +ALTER TABLE mto_service_items ADD CONSTRAINT fk_poe_location_id FOREIGN KEY (poe_location_id) REFERENCES port_locations (id); +ALTER TABLE mto_service_items ADD CONSTRAINT fk_pod_location_id FOREIGN KEY (pod_location_id) REFERENCES port_locations (id); \ No newline at end of file diff --git a/migrations/app/schema/20241122155416_total_dependents_calculation.up.sql b/migrations/app/schema/20241122155416_total_dependents_calculation.up.sql new file mode 100644 index 00000000000..69484f5c373 --- /dev/null +++ b/migrations/app/schema/20241122155416_total_dependents_calculation.up.sql @@ -0,0 +1,18 @@ +-- Set temp timeout due to large file modification +-- Time is 5 minutes in milliseconds +SET statement_timeout = 300000; +SET lock_timeout = 300000; +SET idle_in_transaction_session_timeout = 300000; +-- Zero downtime not necessary, this is not used at this time +ALTER TABLE entitlements +DROP COLUMN IF EXISTS total_dependents; -- This column has never been used, this has been confirmed prior to the migration +-- The calculation should only ever work if dependents 12 and under or 12 and over are present +-- These fields are only present on OCONUS +-- Since we don't know the number of dependents under 12 or 12 and over on CONUS moves, we don't want to default to 0 total dependents. That'd be confusing +ALTER TABLE entitlements +ADD COLUMN total_dependents integer GENERATED ALWAYS AS ( + CASE + WHEN dependents_under_twelve IS NULL AND dependents_twelve_and_over IS NULL THEN NULL + ELSE COALESCE(dependents_under_twelve, 0) + COALESCE(dependents_twelve_and_over, 0) + END +) STORED; diff --git a/migrations/app/schema/20241126222026_add_sort_column_to_re_service_items.up.sql b/migrations/app/schema/20241126222026_add_sort_column_to_re_service_items.up.sql new file mode 100644 index 00000000000..0aaabd88420 --- /dev/null +++ b/migrations/app/schema/20241126222026_add_sort_column_to_re_service_items.up.sql @@ -0,0 +1,10 @@ +ALTER TABLE re_service_items ADD COLUMN IF NOT EXISTS sort int; + +COMMENT ON COLUMN re_service_items.sort IS 'Sort order for service items to be displayed for a given shipment type.'; + +update re_service_items set sort = 1 where service_id in (select id from re_services where code in ('ISLH','UBP')); +--A shipment will only have either POEFSC or PODFSC, so we set them to the same sort value +update re_service_items set sort = 2 where service_id in (select id from re_services where code = 'POEFSC'); +update re_service_items set sort = 2 where service_id in (select id from re_services where code = 'PODFSC'); +update re_service_items set sort = 3 where service_id in (select id from re_services where code in ('IHPK','IUBPK')); +update re_service_items set sort = 4 where service_id in (select id from re_services where code in ('IHUPK','IUBUPK')); \ No newline at end of file diff --git a/migrations/app/schema/20241127133504_add_indexes_speed_up_counseling_offices.up.sql b/migrations/app/schema/20241127133504_add_indexes_speed_up_counseling_offices.up.sql new file mode 100644 index 00000000000..6dc7439800d --- /dev/null +++ b/migrations/app/schema/20241127133504_add_indexes_speed_up_counseling_offices.up.sql @@ -0,0 +1,12 @@ +--adding indexes to help speed up counseling_offices dropdown +CREATE INDEX IF NOT EXISTS idx_addresses_postal_code ON addresses(postal_code); +CREATE INDEX IF NOT EXISTS idx_addresses_us_post_region_cities_id ON addresses(us_post_region_cities_id); +CREATE INDEX IF NOT EXISTS idx_addresses_country_id ON addresses(country_id); +CREATE INDEX IF NOT EXISTS idx_duty_locations_provides_services_counseling ON duty_locations(provides_services_counseling); +CREATE INDEX IF NOT EXISTS idx_re_us_post_regions_uspr_zip_id ON re_us_post_regions(uspr_zip_id); +CREATE INDEX IF NOT EXISTS idx_zip3_distances_from_zip3_to_zip3 ON zip3_distances(from_zip3, to_zip3); +CREATE INDEX IF NOT EXISTS idx_zip3_distances_to_zip3_from_zip3 ON zip3_distances(to_zip3, from_zip3); +CREATE INDEX IF NOT EXISTS idx_re_oconus_rate_areas_us_post_region_cities_id ON re_oconus_rate_areas(us_post_region_cities_id); +CREATE INDEX IF NOT EXISTS idx_gbloc_aors_oconus_rate_area_id ON gbloc_aors(oconus_rate_area_id); +CREATE INDEX IF NOT EXISTS idx_gbloc_aors_jppso_regions_id ON gbloc_aors(jppso_regions_id); +CREATE INDEX IF NOT EXISTS idx_transportation_offices_provides_ppm_closeout ON transportation_offices(provides_ppm_closeout); \ No newline at end of file diff --git a/migrations/app/schema/20241203024453_add_ppm_max_incentive_column.up.sql b/migrations/app/schema/20241203024453_add_ppm_max_incentive_column.up.sql index 3b42d0042e3..0d93306107b 100644 --- a/migrations/app/schema/20241203024453_add_ppm_max_incentive_column.up.sql +++ b/migrations/app/schema/20241203024453_add_ppm_max_incentive_column.up.sql @@ -1,3 +1,2 @@ --- Add external_crate to mto_service_items ALTER TABLE ppm_shipments ADD COLUMN IF NOT EXISTS max_incentive int; COMMENT ON COLUMN ppm_shipments.max_incentive IS 'The max incentive a PPM can have, based on the max entitlement allowed.'; \ No newline at end of file diff --git a/migrations/app/secure/20241202163059_create_test_sequence_dev_env.up.sql b/migrations/app/secure/20241202163059_create_test_sequence_dev_env.up.sql new file mode 100644 index 00000000000..bab7b7da203 --- /dev/null +++ b/migrations/app/secure/20241202163059_create_test_sequence_dev_env.up.sql @@ -0,0 +1,7 @@ +-- Local test migration. +-- This will be run on development environments. +-- It should mirror what you intend to apply on loadtest/demo/exp/stg/prd +-- DO NOT include any sensitive data. + +-- Create test_sequence in migration, moved from sequencer_test.go +CREATE SEQUENCE IF NOT EXISTS test_sequence; \ No newline at end of file diff --git a/package.json b/package.json index 54d501cfe4b..333d21bb2f2 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "dependencies": { + "canvas": "^2.11.2", "@fortawesome/fontawesome-svg-core": "^6.5.2", "@fortawesome/free-regular-svg-icons": "^6.4.0", "@fortawesome/free-solid-svg-icons": "^6.4.0", @@ -10,7 +11,7 @@ "@opentelemetry/core": "^1.15.1", "@tanstack/react-query": "^4.29.12", "@tanstack/react-query-devtools": "^5.17.12", - "@transcom/react-file-viewer": "git+https://github.com/transcom/react-file-viewer#v1.2.5", + "@transcom/react-file-viewer": "git+https://github.com/transcom/react-file-viewer#v1.4.1", "@trussworks/react-uswds": "3.2.0", "axe-playwright": "^1.2.3", "bytes": "^3.1.2", diff --git a/pkg/assets/notifications/templates/move_payment_reminder_template.html b/pkg/assets/notifications/templates/move_payment_reminder_template.html index bf886147d56..d645b79eb98 100644 --- a/pkg/assets/notifications/templates/move_payment_reminder_template.html +++ b/pkg/assets/notifications/templates/move_payment_reminder_template.html @@ -1,7 +1,7 @@

*** DO NOT REPLY directly to this email ***

This is a reminder that your PPM with the assigned move code {{.Locator}} from -{{.OriginDutyLocation}} to {{.DestinationDutyLocation}} is awaiting action in MilMove.

+{{.OriginDutyLocation}} to {{.DestinationLocation}} is awaiting action in MilMove.

Next steps:

diff --git a/pkg/assets/notifications/templates/move_payment_reminder_template.txt b/pkg/assets/notifications/templates/move_payment_reminder_template.txt index ccb29c418ab..f23d1b1deae 100644 --- a/pkg/assets/notifications/templates/move_payment_reminder_template.txt +++ b/pkg/assets/notifications/templates/move_payment_reminder_template.txt @@ -1,7 +1,7 @@ *** DO NOT REPLY directly to this email *** This is a reminder that your PPM with the assigned move code {{.Locator}} from {{.OriginDutyLocation}} -to {{.DestinationDutyLocation}} is awaiting action in MilMove. +to {{.DestinationLocation}} is awaiting action in MilMove. Next steps: diff --git a/pkg/db/sequence/sequencer_test.go b/pkg/db/sequence/sequencer_test.go index 5dd06751e01..cd2c7f56407 100644 --- a/pkg/db/sequence/sequencer_test.go +++ b/pkg/db/sequence/sequencer_test.go @@ -13,9 +13,7 @@ type SequenceSuite struct { } func (suite *SequenceSuite) SetupTest() { - err := suite.DB().RawQuery("CREATE SEQUENCE IF NOT EXISTS test_sequence;").Exec() - suite.NoError(err, "Error creating test sequence") - err = suite.DB().RawQuery("SELECT setval($1, 1);", testSequence).Exec() + err := suite.DB().RawQuery("SELECT setval($1, 1);", testSequence).Exec() suite.NoError(err, "Error resetting sequence") } diff --git a/pkg/factory/entitlement_factory.go b/pkg/factory/entitlement_factory.go index 2090eb20538..235fdaf3be6 100644 --- a/pkg/factory/entitlement_factory.go +++ b/pkg/factory/entitlement_factory.go @@ -46,6 +46,7 @@ func BuildEntitlement(db *pop.Connection, customs []Customization, traits []Trai ocie := true proGearWeight := 2000 proGearWeightSpouse := 500 + ordersType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION // Create default Entitlement entitlement := models.Entitlement{ @@ -60,7 +61,7 @@ func BuildEntitlement(db *pop.Connection, customs []Customization, traits []Trai OrganizationalClothingAndIndividualEquipment: ocie, } // Set default calculated values - entitlement.SetWeightAllotment(string(*grade)) + entitlement.SetWeightAllotment(string(*grade), ordersType) 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 dffdd89ca39..0c6a0adee52 100644 --- a/pkg/factory/entitlement_factory_test.go +++ b/pkg/factory/entitlement_factory_test.go @@ -27,7 +27,7 @@ func (suite *FactorySuite) TestBuildEntitlement() { RequiredMedicalEquipmentWeight: 1000, OrganizationalClothingAndIndividualEquipment: true, } - defEnt.SetWeightAllotment("E_1") + defEnt.SetWeightAllotment("E_1", internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) defEnt.DBAuthorizedWeight = defEnt.AuthorizedWeight() // FUNCTION UNDER TEST @@ -84,7 +84,7 @@ 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") + custEnt.SetWeightAllotment("E_1", internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) custEnt.DBAuthorizedWeight = custEnt.AuthorizedWeight() // Check that the created object had the correct allotments set @@ -129,7 +129,7 @@ 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") + testEnt.SetWeightAllotment("O_9", internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) testEnt.DBAuthorizedWeight = testEnt.AuthorizedWeight() // Now DBAuthorizedWeight should be appropriate for O_9 grade diff --git a/pkg/factory/move_factory.go b/pkg/factory/move_factory.go index bdaf2692c95..e2192ae93f0 100644 --- a/pkg/factory/move_factory.go +++ b/pkg/factory/move_factory.go @@ -167,7 +167,7 @@ func BuildMoveWithShipment(db *pop.Connection, customs []Customization, traits [ func BuildMoveWithPPMShipment(db *pop.Connection, customs []Customization, traits []Trait) models.Move { move := BuildMove(db, customs, traits) - mtoShipment := buildMTOShipmentWithBuildType(db, customs, traits, mtoShipmentBuildBasic) + mtoShipment := buildMTOShipmentWithBuildType(db, customs, traits, mtoShipmentPPM) mtoShipment.MoveTaskOrder = move mtoShipment.MoveTaskOrderID = move.ID diff --git a/pkg/factory/mto_shipment_factory.go b/pkg/factory/mto_shipment_factory.go index 74f45e3d937..2be575fb33b 100644 --- a/pkg/factory/mto_shipment_factory.go +++ b/pkg/factory/mto_shipment_factory.go @@ -21,6 +21,7 @@ const ( mtoShipmentBuildBasic mtoShipmentBuildType = iota mtoShipmentBuild mtoShipmentNTS + mtoShipmentPPM mtoShipmentNTSR ) @@ -66,6 +67,9 @@ func buildMTOShipmentWithBuildType(db *pop.Connection, customs []Customization, shipmentHasDeliveryDetails = true case mtoShipmentBuildBasic: setupPickupAndDelivery = false + case mtoShipmentPPM: + defaultShipmentType = models.MTOShipmentTypePPM + setupPickupAndDelivery = false default: defaultShipmentType = models.MTOShipmentTypeHHG setupPickupAndDelivery = true diff --git a/pkg/factory/office_user_factory.go b/pkg/factory/office_user_factory.go index b995c81b408..bf2298003ed 100644 --- a/pkg/factory/office_user_factory.go +++ b/pkg/factory/office_user_factory.go @@ -12,7 +12,7 @@ import ( "github.com/transcom/mymove/pkg/testdatagen" ) -// BuildOfficeUser creates an OfficeUser +// BuildOfficeUser creates an OfficeUser, and a transportation office and transportation office assignment if either doesn't exist // Params: // - customs is a slice that will be modified by the factory // - db can be set to nil to create a stubbed model that is not stored in DB. @@ -54,6 +54,73 @@ func BuildOfficeUser(db *pop.Connection, customs []Customization, traits []Trait // Overwrite values with those from assertions testdatagen.MergeModels(&officeUser, cOfficeUser) + // If db is false, it's a stub. No need to create in database + // If OfficeUser or Transportation office is a stub there is nothing to link with + // a transportation office assignment and the link will fail due to nil IDs + if db != nil { + mustCreate(db, &officeUser) + + BuildPrimaryTransportationOfficeAssignment(db, []Customization{ + { + Model: models.OfficeUser{ + ID: officeUser.ID, + }, + LinkOnly: true, + }, + { + Model: models.TransportationOffice{ + ID: transportationOffice.ID, + }, + LinkOnly: true, + }, + }, nil) + } + + return officeUser +} + +// BuildOfficeUserWithoutTransportationAssignment creates an OfficeUser +// Params: +// - customs is a slice that will be modified by the factory +// - db can be set to nil to create a stubbed model that is not stored in DB. +// Notes: +// - To build an office user with one or more roles use BuildOfficeUserWithRoles +// - There's a uniqueness constraint on office user emails so use the GetTraitOfficeUserEmail trait +// when creating a test with multiple office users +// - The OfficeUser returned won't have an ID if the db is nil. If an ID is needed for a stubbed user, +// use trait GetTraitOfficeUserWithID +func BuildOfficeUserWithoutTransportationOfficeAssignment(db *pop.Connection, customs []Customization, traits []Trait) models.OfficeUser { + customs = setupCustomizations(customs, traits) + + // Find officeuser assertion and convert to models officeuser + var cOfficeUser models.OfficeUser + if result := findValidCustomization(customs, OfficeUser); result != nil { + cOfficeUser = result.Model.(models.OfficeUser) + if result.LinkOnly { + return cOfficeUser + } + } + + // Find/create the user model + user := BuildUserAndUsersRoles(db, customs, nil) + + // Find/create the TransportationOffice model + transportationOffice := BuildTransportationOffice(db, customs, nil) + + // create officeuser + officeUser := models.OfficeUser{ + UserID: &user.ID, + User: user, + FirstName: "Leo", + LastName: "Spaceman", + Email: "leo_spaceman_office@example.com", + Telephone: "415-555-1212", + TransportationOffice: transportationOffice, + TransportationOfficeID: transportationOffice.ID, + } + // Overwrite values with those from assertions + testdatagen.MergeModels(&officeUser, cOfficeUser) + // If db is false, it's a stub. No need to create in database if db != nil { mustCreate(db, &officeUser) diff --git a/pkg/factory/order_factory.go b/pkg/factory/order_factory.go index d6f2165e095..9bc3c61fb1d 100644 --- a/pkg/factory/order_factory.go +++ b/pkg/factory/order_factory.go @@ -203,8 +203,8 @@ func buildOrderWithBuildType(db *pop.Connection, customs []Customization, traits defaultSpouseHasProGear := false defaultOrdersType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION defaultOrdersTypeDetail := internalmessages.OrdersTypeDetail("HHG_PERMITTED") - defaultDestinationDutyLocationGbloc := "AGFM" - destinationDutyLocationGbloc := &defaultDestinationDutyLocationGbloc + defaultDestinationGbloc := "AGFM" + destinationGbloc := &defaultDestinationGbloc testYear := 2018 defaultIssueDate := time.Date(testYear, time.March, 15, 0, 0, 0, 0, time.UTC) defaultReportByDate := time.Date(testYear, time.August, 1, 0, 0, 0, 0, time.UTC) @@ -248,7 +248,7 @@ func buildOrderWithBuildType(db *pop.Connection, customs []Customization, traits log.Panicf("Error loading duty location by id %s: %s\n", newDutyLocation.ID.String(), err) } destinationPostalCodeToGBLOC := FetchOrBuildPostalCodeToGBLOC(db, newDutyLocation.Address.PostalCode, "AGFM") - destinationDutyLocationGbloc = &destinationPostalCodeToGBLOC.GBLOC + destinationGbloc = &destinationPostalCodeToGBLOC.GBLOC } } @@ -257,7 +257,7 @@ func buildOrderWithBuildType(db *pop.Connection, customs []Customization, traits ServiceMemberID: serviceMember.ID, NewDutyLocation: newDutyLocation, NewDutyLocationID: newDutyLocation.ID, - DestinationGBLOC: destinationDutyLocationGbloc, + DestinationGBLOC: destinationGbloc, UploadedOrders: uploadedOrders, UploadedOrdersID: uploadedOrders.ID, IssueDate: defaultIssueDate, diff --git a/pkg/factory/ppm_shipment_factory.go b/pkg/factory/ppm_shipment_factory.go index f6a43ab95f2..5a784916749 100644 --- a/pkg/factory/ppm_shipment_factory.go +++ b/pkg/factory/ppm_shipment_factory.go @@ -572,6 +572,16 @@ func BuildPPMShipmentThatNeedsCloseout(db *pop.Connection, userUploader *uploade ppmShipment.SignedCertification = &signedCert + if ppmShipment.AllowableWeight == nil { + var allowableWeight = unit.Pound(0) + if len(ppmShipment.WeightTickets) >= 1 { + for _, weightTicket := range ppmShipment.WeightTickets { + allowableWeight += *weightTicket.FullWeight - *weightTicket.EmptyWeight + } + } + ppmShipment.AllowableWeight = &allowableWeight + } + ppmShipment.Status = models.PPMShipmentStatusNeedsCloseout if ppmShipment.SubmittedAt == nil { ppmShipment.SubmittedAt = models.TimePointer(time.Now()) diff --git a/pkg/factory/shared.go b/pkg/factory/shared.go index 8e1077dd692..7b76ce0529c 100644 --- a/pkg/factory/shared.go +++ b/pkg/factory/shared.go @@ -53,6 +53,7 @@ var CustomerSupportRemark CustomType = "CustomerSupportRemark" var Document CustomType = "Document" var DutyLocation CustomType = "DutyLocation" var Entitlement CustomType = "Entitlement" +var UBAllowance CustomType = "UBAllowances" var EvaluationReport CustomType = "EvaluationReport" var LineOfAccounting CustomType = "LineOfAccounting" var MobileHome CustomType = "MobileHome" @@ -90,6 +91,7 @@ var State CustomType = "State" var StorageFacility CustomType = "StorageFacility" var TransportationAccountingCode CustomType = "TransportationAccountingCode" var TransportationOffice CustomType = "TransportationOffice" +var TransportationOfficeAssignment CustomType = "TransportationOfficeAssignment" var Upload CustomType = "Upload" var UserUpload CustomType = "UserUpload" var User CustomType = "User" @@ -115,6 +117,7 @@ var defaultTypesMap = map[string]CustomType{ "models.Document": Document, "models.DutyLocation": DutyLocation, "models.Entitlement": Entitlement, + "models.UBAllowances": UBAllowance, "models.EvaluationReport": EvaluationReport, "models.LineOfAccounting": LineOfAccounting, "models.MobileHome": MobileHome, @@ -152,6 +155,7 @@ var defaultTypesMap = map[string]CustomType{ "models.TransportationAccountingCode": TransportationAccountingCode, "models.UsPostRegionCity": UsPostRegionCity, "models.TransportationOffice": TransportationOffice, + "models.TransportationOfficeAssignment": TransportationOfficeAssignment, "models.Upload": Upload, "models.UserUpload": UserUpload, "models.User": User, diff --git a/pkg/factory/transportation_office_assignment_factory.go b/pkg/factory/transportation_office_assignment_factory.go new file mode 100644 index 00000000000..a57ede70a0b --- /dev/null +++ b/pkg/factory/transportation_office_assignment_factory.go @@ -0,0 +1,94 @@ +package factory + +import ( + "github.com/gobuffalo/pop/v6" + + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/testdatagen" +) + +// BuildPrimaryTransportationOfficeAssignment creates a Transportation Assignment, and a transportation office and officer user if either doesn't exist +// Params: +// - customs is a slice that will be modified by the factory +// - db can be set to nil to create a stubbed model that is not stored in DB. +// Notes: +// - Marks the transportation office assignment as the office user's primary transportation office, +// use BuildAlternateTransportationOfficeAssignment for non-primary transportation office. +func BuildPrimaryTransportationOfficeAssignment(db *pop.Connection, customs []Customization, traits []Trait) models.TransportationOfficeAssignment { + customs = setupCustomizations(customs, traits) + + // Find TransportationAssignment assertion and convert to models.TransportationOfficeAssignment + var cTransportationOfficeAssignment models.TransportationOfficeAssignment + if result := findValidCustomization(customs, TransportationOfficeAssignment); result != nil { + cTransportationOfficeAssignment = result.Model.(models.TransportationOfficeAssignment) + if result.LinkOnly { + return cTransportationOfficeAssignment + } + } + + // Find/Create the associated transportation office model + transportationOffice := BuildTransportationOffice(db, customs, traits) + + // Find/Create the associated office user model + officeUser := BuildOfficeUser(db, customs, traits) + + // Create transportationOffice + transportationOfficeAssignment := models.TransportationOfficeAssignment{ + ID: officeUser.ID, + TransportationOfficeID: transportationOffice.ID, + TransportationOffice: transportationOffice, + PrimaryOffice: models.BoolPointer(true), + } + + // Overwrite values with those from customizations + testdatagen.MergeModels(&transportationOfficeAssignment, cTransportationOfficeAssignment) + + // If db is false, it's a stub. No need to create in database + if db != nil { + mustCreate(db, &transportationOfficeAssignment) + } + return transportationOfficeAssignment +} + +// BuildAlternateTransportationAssignment creates a Transportation Assignment, and a transportation office and officer user if either doesn't exist +// Params: +// - customs is a slice that will be modified by the factory +// - db can be set to nil to create a stubbed model that is not stored in DB. +// Notes: +// - Marks the transportation office assignment as a non-primary transportation office assignment, +// use BuildPrimaryTransportationOfficeAssignment for primary transportation office assignments. +func BuildAlternateTransportationOfficeAssignment(db *pop.Connection, customs []Customization, traits []Trait) models.TransportationOfficeAssignment { + customs = setupCustomizations(customs, traits) + + // Find TransportationAssignment assertion and convert to models.TransportationAssignment + var cTransportationOfficeAssignment models.TransportationOfficeAssignment + if result := findValidCustomization(customs, TransportationOfficeAssignment); result != nil { + cTransportationOfficeAssignment = result.Model.(models.TransportationOfficeAssignment) + if result.LinkOnly { + return cTransportationOfficeAssignment + } + } + + // Find/Create the associated office user model + officeUser := BuildOfficeUser(db, customs, traits) + + // Find/Create the associated transportation office model + transportationOffice := BuildTransportationOffice(db, customs, traits) + + // Create transportationOffice + transportationOfficeAssignment := models.TransportationOfficeAssignment{ + ID: officeUser.ID, + TransportationOfficeID: transportationOffice.ID, + TransportationOffice: transportationOffice, + PrimaryOffice: models.BoolPointer(false), + } + + // Overwrite values with those from customizations + testdatagen.MergeModels(&transportationOfficeAssignment, cTransportationOfficeAssignment) + + // If db is false, it's a stub. No need to create in database + if db != nil { + mustCreate(db, &transportationOfficeAssignment) + } + return transportationOfficeAssignment +} diff --git a/pkg/factory/transportation_office_assignment_factory_test.go b/pkg/factory/transportation_office_assignment_factory_test.go new file mode 100644 index 00000000000..2df32cc8bea --- /dev/null +++ b/pkg/factory/transportation_office_assignment_factory_test.go @@ -0,0 +1,171 @@ +package factory + +import ( + "github.com/gofrs/uuid" + + "github.com/transcom/mymove/pkg/models" +) + +func (suite *FactorySuite) TestBuildPrimaryTransportationOfficeAssignment() { + suite.Run("Successful creation of default Primary TransportationOfficeAssignment", func() { + // Under test: BuildPrimaryTransportationOfficeAssignment + // Mocked: None + // Set up: Create a transportation office assignment with no customizations or traits + // Expected outcome:transportationOfficeAssignment should be created with default values + + // SETUP + defaultOffice := models.TransportationOffice{ + Name: "JPPSO Testy McTest", + Gbloc: "KKFA", + Latitude: 1.23445, + Longitude: -23.34455, + } + defaultAddress := models.Address{ + StreetAddress1: "123 Any Street", + } + defaultOfficeUser := models.OfficeUser{ + FirstName: "Leo", + LastName: "Spaceman", + Telephone: "415-555-1212", + } + + // CALL FUNCTION UNDER TEST + // transportationOffice := BuildTransportationOffice(suite.DB(), nil, nil) + // officeUser := BuildOfficeUserWithoutTransportationOfficeAssignment(suite.DB(), nil, nil) + primaryTransportationOfficeAssignment := BuildPrimaryTransportationOfficeAssignment(suite.DB(), nil, nil) + + // VALIDATE RESULTS + suite.Equal(models.BoolPointer(true), primaryTransportationOfficeAssignment.PrimaryOffice) + + // Associated Transportation Office is the default transportation office + suite.Equal(defaultOffice.Name, primaryTransportationOfficeAssignment.TransportationOffice.Name) + suite.Equal(defaultOffice.Gbloc, primaryTransportationOfficeAssignment.TransportationOffice.Gbloc) + suite.Equal(defaultOffice.Latitude, primaryTransportationOfficeAssignment.TransportationOffice.Latitude) + suite.Equal(defaultOffice.Longitude, primaryTransportationOfficeAssignment.TransportationOffice.Longitude) + suite.Equal((*string)(nil), primaryTransportationOfficeAssignment.TransportationOffice.Hours) + suite.Equal((*string)(nil), primaryTransportationOfficeAssignment.TransportationOffice.Services) + suite.Equal((*string)(nil), primaryTransportationOfficeAssignment.TransportationOffice.Note) + suite.Equal(defaultAddress.StreetAddress1, primaryTransportationOfficeAssignment.TransportationOffice.Address.StreetAddress1) + + // Associated Office User is the default Office User + var matchingOfficeUser models.OfficeUser + err := suite.DB().Find(&matchingOfficeUser, primaryTransportationOfficeAssignment.ID) + suite.NoError(err) + suite.Equal(defaultOfficeUser.FirstName, matchingOfficeUser.FirstName) + suite.Equal(defaultOfficeUser.LastName, matchingOfficeUser.LastName) + suite.Equal(defaultOfficeUser.Telephone, matchingOfficeUser.Telephone) + }) + + suite.Run("Successful creation of default Alternate TransportationOfficeAssignment", func() { + // Under test: BuildAlternateTransportationOfficeAssignment + // Mocked: None + // Set up: Create a transportation office assignment with no customizations or traits + // Expected outcome:transportationOfficeAssignment should be created with default values + + // SETUP + defaultOffice := models.TransportationOffice{ + Name: "JPPSO Testy McTest", + Gbloc: "KKFA", + Latitude: 1.23445, + Longitude: -23.34455, + } + defaultAddress := models.Address{ + StreetAddress1: "123 Any Street", + } + defaultOfficeUser := models.OfficeUser{ + FirstName: "Leo", + LastName: "Spaceman", + Telephone: "415-555-1212", + } + + // CALL FUNCTION UNDER TEST + primaryTransportationOfficeAssignment := BuildAlternateTransportationOfficeAssignment(suite.DB(), nil, nil) + + // VALIDATE RESULTS + suite.Equal(models.BoolPointer(false), primaryTransportationOfficeAssignment.PrimaryOffice) + + // Associated Transportation Office is the default transportation office + suite.Equal(defaultOffice.Name, primaryTransportationOfficeAssignment.TransportationOffice.Name) + suite.Equal(defaultOffice.Gbloc, primaryTransportationOfficeAssignment.TransportationOffice.Gbloc) + suite.Equal(defaultOffice.Latitude, primaryTransportationOfficeAssignment.TransportationOffice.Latitude) + suite.Equal(defaultOffice.Longitude, primaryTransportationOfficeAssignment.TransportationOffice.Longitude) + suite.Equal((*string)(nil), primaryTransportationOfficeAssignment.TransportationOffice.Hours) + suite.Equal((*string)(nil), primaryTransportationOfficeAssignment.TransportationOffice.Services) + suite.Equal((*string)(nil), primaryTransportationOfficeAssignment.TransportationOffice.Note) + suite.Equal(defaultAddress.StreetAddress1, primaryTransportationOfficeAssignment.TransportationOffice.Address.StreetAddress1) + + // Associated Office User is the default Office User + var matchingOfficeUser models.OfficeUser + err := suite.DB().Find(&matchingOfficeUser, primaryTransportationOfficeAssignment.ID) + suite.NoError(err) + suite.Equal(defaultOfficeUser.FirstName, matchingOfficeUser.FirstName) + suite.Equal(defaultOfficeUser.LastName, matchingOfficeUser.LastName) + suite.Equal(defaultOfficeUser.Telephone, matchingOfficeUser.Telephone) + }) + + suite.Run("Successful creation of customized TransportationOfficeAssignments", func() { + // Under test: BuildPrimaryTransportationOfficeAssignment & BuildAlternateTransportationOfficeAssignment + // Set up: Create a Transportation Office Assignments and pass custom fields + // Expected outcome:transportationOfficeAssignments should be created with custom fields + // SETUP + customOffice := models.TransportationOffice{ + ID: uuid.Must(uuid.NewV4()), + Name: "JPPSO Coronado", + Gbloc: "CCRD", + Latitude: 32.6806, + Longitude: -117.1779, + } + + secondaryCustomOffice := models.TransportationOffice{ + ID: uuid.Must(uuid.NewV4()), + Name: "JPPSO Coronadon't", + Gbloc: "CCRD", + Latitude: 33.6806, + Longitude: -118.1779, + } + + transportationOffice := BuildTransportationOfficeWithPhoneLine(suite.DB(), []Customization{ + {Model: customOffice}, + }, nil) + + secondaryTransportationOffice := BuildTransportationOffice(suite.DB(), []Customization{ + {Model: secondaryCustomOffice}, + }, nil) + + customOfficeUser := models.OfficeUser{ + ID: uuid.Must(uuid.NewV4()), + FirstName: "TOA", + LastName: "Tester", + Email: "toa.tester@mail.com", + TransportationOfficeID: transportationOffice.ID, + } + + officeUser := BuildOfficeUserWithoutTransportationOfficeAssignment(suite.DB(), []Customization{ + {Model: customOfficeUser}, + }, nil) + + transportationOfficeAssignment := BuildPrimaryTransportationOfficeAssignment(suite.DB(), []Customization{ + {Model: officeUser, LinkOnly: true}, + {Model: transportationOffice, LinkOnly: true}, + }, nil) + + secondaryTransportationOfficeAssignment := BuildAlternateTransportationOfficeAssignment(suite.DB(), []Customization{ + {Model: officeUser, LinkOnly: true}, + {Model: secondaryTransportationOffice, LinkOnly: true}, + }, nil) + + // VALIDATE RESULTS + suite.Equal(customOffice.ID, transportationOfficeAssignment.TransportationOffice.ID) + suite.Equal(customOffice.Name, transportationOfficeAssignment.TransportationOffice.Name) + suite.Equal(customOffice.Gbloc, transportationOfficeAssignment.TransportationOffice.Gbloc) + suite.Equal(customOfficeUser.ID, transportationOfficeAssignment.ID) + suite.Equal(models.BoolPointer(true), transportationOfficeAssignment.PrimaryOffice) + + suite.Equal(secondaryCustomOffice.ID, secondaryTransportationOfficeAssignment.TransportationOffice.ID) + suite.Equal(secondaryCustomOffice.Name, secondaryTransportationOfficeAssignment.TransportationOffice.Name) + suite.Equal(secondaryCustomOffice.Gbloc, secondaryTransportationOfficeAssignment.TransportationOffice.Gbloc) + suite.Equal(customOfficeUser.ID, secondaryTransportationOfficeAssignment.ID) + suite.Equal(models.BoolPointer(false), secondaryTransportationOfficeAssignment.PrimaryOffice) + + }) +} diff --git a/pkg/gen/adminapi/adminoperations/mymove_api.go b/pkg/gen/adminapi/adminoperations/mymove_api.go index 50e2bfaca71..e2ed2892e29 100644 --- a/pkg/gen/adminapi/adminoperations/mymove_api.go +++ b/pkg/gen/adminapi/adminoperations/mymove_api.go @@ -85,6 +85,9 @@ func NewMymoveAPI(spec *loads.Document) *MymoveAPI { MovesGetMoveHandler: moves.GetMoveHandlerFunc(func(params moves.GetMoveParams) middleware.Responder { return middleware.NotImplemented("operation moves.GetMove has not yet been implemented") }), + TransportationOfficesGetOfficeByIDHandler: transportation_offices.GetOfficeByIDHandlerFunc(func(params transportation_offices.GetOfficeByIDParams) middleware.Responder { + return middleware.NotImplemented("operation transportation_offices.GetOfficeByID has not yet been implemented") + }), OfficeUsersGetOfficeUserHandler: office_users.GetOfficeUserHandlerFunc(func(params office_users.GetOfficeUserParams) middleware.Responder { return middleware.NotImplemented("operation office_users.GetOfficeUser has not yet been implemented") }), @@ -221,6 +224,8 @@ type MymoveAPI struct { UserGetLoggedInAdminUserHandler user.GetLoggedInAdminUserHandler // MovesGetMoveHandler sets the operation handler for the get move operation MovesGetMoveHandler moves.GetMoveHandler + // TransportationOfficesGetOfficeByIDHandler sets the operation handler for the get office by Id operation + TransportationOfficesGetOfficeByIDHandler transportation_offices.GetOfficeByIDHandler // OfficeUsersGetOfficeUserHandler sets the operation handler for the get office user operation OfficeUsersGetOfficeUserHandler office_users.GetOfficeUserHandler // RequestedOfficeUsersGetRequestedOfficeUserHandler sets the operation handler for the get requested office user operation @@ -377,6 +382,9 @@ func (o *MymoveAPI) Validate() error { if o.MovesGetMoveHandler == nil { unregistered = append(unregistered, "moves.GetMoveHandler") } + if o.TransportationOfficesGetOfficeByIDHandler == nil { + unregistered = append(unregistered, "transportation_offices.GetOfficeByIDHandler") + } if o.OfficeUsersGetOfficeUserHandler == nil { unregistered = append(unregistered, "office_users.GetOfficeUserHandler") } @@ -582,6 +590,10 @@ func (o *MymoveAPI) initHandlerCache() { if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) } + o.handlers["GET"]["/offices/{officeId}"] = transportation_offices.NewGetOfficeByID(o.context, o.TransportationOfficesGetOfficeByIDHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } o.handlers["GET"]["/office-users/{officeUserId}"] = office_users.NewGetOfficeUser(o.context, o.OfficeUsersGetOfficeUserHandler) if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) diff --git a/pkg/gen/adminapi/adminoperations/transportation_offices/get_office_by_id.go b/pkg/gen/adminapi/adminoperations/transportation_offices/get_office_by_id.go new file mode 100644 index 00000000000..ab375956669 --- /dev/null +++ b/pkg/gen/adminapi/adminoperations/transportation_offices/get_office_by_id.go @@ -0,0 +1,59 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package transportation_offices + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// GetOfficeByIDHandlerFunc turns a function with the right signature into a get office by Id handler +type GetOfficeByIDHandlerFunc func(GetOfficeByIDParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn GetOfficeByIDHandlerFunc) Handle(params GetOfficeByIDParams) middleware.Responder { + return fn(params) +} + +// GetOfficeByIDHandler interface for that can handle valid get office by Id params +type GetOfficeByIDHandler interface { + Handle(GetOfficeByIDParams) middleware.Responder +} + +// NewGetOfficeByID creates a new http.Handler for the get office by Id operation +func NewGetOfficeByID(ctx *middleware.Context, handler GetOfficeByIDHandler) *GetOfficeByID { + return &GetOfficeByID{Context: ctx, Handler: handler} +} + +/* + GetOfficeByID swagger:route GET /offices/{officeId} Transportation offices getOfficeById + +# Get Transportation Office by ID + +This endpoint returns a list of Transportation Offices. Do not use this endpoint +directly as it is meant to be used with the Admin UI exclusively. +*/ +type GetOfficeByID struct { + Context *middleware.Context + Handler GetOfficeByIDHandler +} + +func (o *GetOfficeByID) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewGetOfficeByIDParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/pkg/gen/adminapi/adminoperations/transportation_offices/get_office_by_id_parameters.go b/pkg/gen/adminapi/adminoperations/transportation_offices/get_office_by_id_parameters.go new file mode 100644 index 00000000000..ae0bcafa12e --- /dev/null +++ b/pkg/gen/adminapi/adminoperations/transportation_offices/get_office_by_id_parameters.go @@ -0,0 +1,91 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package transportation_offices + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/validate" +) + +// NewGetOfficeByIDParams creates a new GetOfficeByIDParams object +// +// There are no default values defined in the spec. +func NewGetOfficeByIDParams() GetOfficeByIDParams { + + return GetOfficeByIDParams{} +} + +// GetOfficeByIDParams contains all the bound params for the get office by Id operation +// typically these are obtained from a http.Request +// +// swagger:parameters getOfficeById +type GetOfficeByIDParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: path + */ + OfficeID strfmt.UUID +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewGetOfficeByIDParams() beforehand. +func (o *GetOfficeByIDParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + rOfficeID, rhkOfficeID, _ := route.Params.GetOK("officeId") + if err := o.bindOfficeID(rOfficeID, rhkOfficeID, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindOfficeID binds and validates parameter OfficeID from path. +func (o *GetOfficeByIDParams) bindOfficeID(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + + // Format: uuid + value, err := formats.Parse("uuid", raw) + if err != nil { + return errors.InvalidType("officeId", "path", "strfmt.UUID", raw) + } + o.OfficeID = *(value.(*strfmt.UUID)) + + if err := o.validateOfficeID(formats); err != nil { + return err + } + + return nil +} + +// validateOfficeID carries on validations for parameter OfficeID +func (o *GetOfficeByIDParams) validateOfficeID(formats strfmt.Registry) error { + + if err := validate.FormatOf("officeId", "path", "uuid", o.OfficeID.String(), formats); err != nil { + return err + } + return nil +} diff --git a/pkg/gen/adminapi/adminoperations/transportation_offices/get_office_by_id_responses.go b/pkg/gen/adminapi/adminoperations/transportation_offices/get_office_by_id_responses.go new file mode 100644 index 00000000000..417b2ba1d00 --- /dev/null +++ b/pkg/gen/adminapi/adminoperations/transportation_offices/get_office_by_id_responses.go @@ -0,0 +1,159 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package transportation_offices + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "github.com/transcom/mymove/pkg/gen/adminmessages" +) + +// GetOfficeByIDOKCode is the HTTP code returned for type GetOfficeByIDOK +const GetOfficeByIDOKCode int = 200 + +/* +GetOfficeByIDOK success + +swagger:response getOfficeByIdOK +*/ +type GetOfficeByIDOK struct { + + /* + In: Body + */ + Payload *adminmessages.TransportationOffice `json:"body,omitempty"` +} + +// NewGetOfficeByIDOK creates GetOfficeByIDOK with default headers values +func NewGetOfficeByIDOK() *GetOfficeByIDOK { + + return &GetOfficeByIDOK{} +} + +// WithPayload adds the payload to the get office by Id o k response +func (o *GetOfficeByIDOK) WithPayload(payload *adminmessages.TransportationOffice) *GetOfficeByIDOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get office by Id o k response +func (o *GetOfficeByIDOK) SetPayload(payload *adminmessages.TransportationOffice) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetOfficeByIDOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// GetOfficeByIDBadRequestCode is the HTTP code returned for type GetOfficeByIDBadRequest +const GetOfficeByIDBadRequestCode int = 400 + +/* +GetOfficeByIDBadRequest invalid request + +swagger:response getOfficeByIdBadRequest +*/ +type GetOfficeByIDBadRequest struct { +} + +// NewGetOfficeByIDBadRequest creates GetOfficeByIDBadRequest with default headers values +func NewGetOfficeByIDBadRequest() *GetOfficeByIDBadRequest { + + return &GetOfficeByIDBadRequest{} +} + +// WriteResponse to the client +func (o *GetOfficeByIDBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(400) +} + +// GetOfficeByIDUnauthorizedCode is the HTTP code returned for type GetOfficeByIDUnauthorized +const GetOfficeByIDUnauthorizedCode int = 401 + +/* +GetOfficeByIDUnauthorized request requires user authentication + +swagger:response getOfficeByIdUnauthorized +*/ +type GetOfficeByIDUnauthorized struct { +} + +// NewGetOfficeByIDUnauthorized creates GetOfficeByIDUnauthorized with default headers values +func NewGetOfficeByIDUnauthorized() *GetOfficeByIDUnauthorized { + + return &GetOfficeByIDUnauthorized{} +} + +// WriteResponse to the client +func (o *GetOfficeByIDUnauthorized) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(401) +} + +// GetOfficeByIDNotFoundCode is the HTTP code returned for type GetOfficeByIDNotFound +const GetOfficeByIDNotFoundCode int = 404 + +/* +GetOfficeByIDNotFound Transportation Office not found + +swagger:response getOfficeByIdNotFound +*/ +type GetOfficeByIDNotFound struct { +} + +// NewGetOfficeByIDNotFound creates GetOfficeByIDNotFound with default headers values +func NewGetOfficeByIDNotFound() *GetOfficeByIDNotFound { + + return &GetOfficeByIDNotFound{} +} + +// WriteResponse to the client +func (o *GetOfficeByIDNotFound) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(404) +} + +// GetOfficeByIDInternalServerErrorCode is the HTTP code returned for type GetOfficeByIDInternalServerError +const GetOfficeByIDInternalServerErrorCode int = 500 + +/* +GetOfficeByIDInternalServerError server error + +swagger:response getOfficeByIdInternalServerError +*/ +type GetOfficeByIDInternalServerError struct { +} + +// NewGetOfficeByIDInternalServerError creates GetOfficeByIDInternalServerError with default headers values +func NewGetOfficeByIDInternalServerError() *GetOfficeByIDInternalServerError { + + return &GetOfficeByIDInternalServerError{} +} + +// WriteResponse to the client +func (o *GetOfficeByIDInternalServerError) 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/gen/adminapi/adminoperations/transportation_offices/get_office_by_id_urlbuilder.go b/pkg/gen/adminapi/adminoperations/transportation_offices/get_office_by_id_urlbuilder.go new file mode 100644 index 00000000000..84b2be6dc43 --- /dev/null +++ b/pkg/gen/adminapi/adminoperations/transportation_offices/get_office_by_id_urlbuilder.go @@ -0,0 +1,101 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package transportation_offices + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" + + "github.com/go-openapi/strfmt" +) + +// GetOfficeByIDURL generates an URL for the get office by Id operation +type GetOfficeByIDURL struct { + OfficeID strfmt.UUID + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetOfficeByIDURL) WithBasePath(bp string) *GetOfficeByIDURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetOfficeByIDURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *GetOfficeByIDURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/offices/{officeId}" + + officeID := o.OfficeID.String() + if officeID != "" { + _path = strings.Replace(_path, "{officeId}", officeID, -1) + } else { + return nil, errors.New("officeId is required on GetOfficeByIDURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/admin/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *GetOfficeByIDURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *GetOfficeByIDURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *GetOfficeByIDURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on GetOfficeByIDURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on GetOfficeByIDURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *GetOfficeByIDURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/pkg/gen/adminapi/configure_mymove.go b/pkg/gen/adminapi/configure_mymove.go index 5fe0a5e00f2..1e8322fbfe9 100644 --- a/pkg/gen/adminapi/configure_mymove.go +++ b/pkg/gen/adminapi/configure_mymove.go @@ -97,6 +97,11 @@ func configureAPI(api *adminoperations.MymoveAPI) http.Handler { return middleware.NotImplemented("operation moves.GetMove has not yet been implemented") }) } + if api.TransportationOfficesGetOfficeByIDHandler == nil { + api.TransportationOfficesGetOfficeByIDHandler = transportation_offices.GetOfficeByIDHandlerFunc(func(params transportation_offices.GetOfficeByIDParams) middleware.Responder { + return middleware.NotImplemented("operation transportation_offices.GetOfficeByID has not yet been implemented") + }) + } if api.OfficeUsersGetOfficeUserHandler == nil { api.OfficeUsersGetOfficeUserHandler = office_users.GetOfficeUserHandlerFunc(func(params office_users.GetOfficeUserParams) middleware.Responder { return middleware.NotImplemented("operation office_users.GetOfficeUser has not yet been implemented") diff --git a/pkg/gen/adminapi/embedded_spec.go b/pkg/gen/adminapi/embedded_spec.go index 03274b71847..c92c3d50045 100644 --- a/pkg/gen/adminapi/embedded_spec.go +++ b/pkg/gen/adminapi/embedded_spec.go @@ -1115,6 +1115,48 @@ func init() { } } }, + "/offices/{officeId}": { + "get": { + "description": "This endpoint returns a list of Transportation Offices. Do not use this endpoint\ndirectly as it is meant to be used with the Admin UI exclusively.\n", + "produces": [ + "application/json" + ], + "tags": [ + "Transportation offices" + ], + "summary": "Get Transportation Office by ID", + "operationId": "getOfficeById", + "parameters": [ + { + "type": "string", + "format": "uuid", + "name": "officeId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "success", + "schema": { + "$ref": "#/definitions/TransportationOffice" + } + }, + "400": { + "description": "invalid request" + }, + "401": { + "description": "request requires user authentication" + }, + "404": { + "description": "Transportation Office not found" + }, + "500": { + "description": "server error" + } + } + } + }, "/organizations": { "get": { "description": "This endpoint returns a list of Organizations. Do not use this endpoint directly\nas it is meant to be used with the Admin UI exclusively.\n", @@ -2686,6 +2728,7 @@ func init() { "email", "telephone", "transportationOfficeId", + "transportationOfficeAssignments", "active", "roles", "edipi", @@ -2757,6 +2800,12 @@ func init() { "format": "telephone", "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$" }, + "transportationOfficeAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/TransportationOfficeAssignment" + } + }, "transportationOfficeId": { "type": "string", "format": "uuid" @@ -2813,10 +2862,11 @@ func init() { "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$", "example": "212-555-5555" }, - "transportationOfficeId": { - "type": "string", - "format": "uuid", - "example": "c56a4180-65aa-42ec-a945-5fd21dec0538" + "transportationOfficeAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/OfficeUserTransportationOfficeAssignment" + } } } }, @@ -2854,6 +2904,22 @@ func init() { } } }, + "OfficeUserTransportationOfficeAssignment": { + "type": "object", + "properties": { + "primaryOffice": { + "type": "boolean", + "title": "primaryOffice", + "x-nullable": true + }, + "transportationOfficeId": { + "type": "string", + "format": "uuid", + "title": "transportationOfficeId", + "example": "c56a4180-65aa-42ec-a945-5fd21dec0538" + } + } + }, "OfficeUserUpdate": { "type": "object", "properties": { @@ -2896,10 +2962,11 @@ func init() { "x-nullable": true, "example": "212-555-5555" }, - "transportationOfficeId": { - "type": "string", - "format": "uuid", - "example": "c56a4180-65aa-42ec-a945-5fd21dec0538" + "transportationOfficeAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/OfficeUserTransportationOfficeAssignment" + } } } }, @@ -3225,6 +3292,38 @@ func init() { } } }, + "TransportationOfficeAssignment": { + "type": "object", + "properties": { + "createdAt": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "officeUserId": { + "type": "string", + "format": "uuid", + "example": "c56a4780-65aa-42ec-a945-5fd87dec0538" + }, + "primaryOffice": { + "type": "boolean", + "x-omitempty": false + }, + "transportationOffice": { + "$ref": "#/definitions/TransportationOffice" + }, + "transportationOfficeId": { + "type": "string", + "format": "uuid", + "example": "d67a4780-65aa-42ec-a945-5fd87dec0549" + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "readOnly": true + } + } + }, "TransportationOffices": { "type": "array", "items": { @@ -4673,6 +4772,48 @@ func init() { } } }, + "/offices/{officeId}": { + "get": { + "description": "This endpoint returns a list of Transportation Offices. Do not use this endpoint\ndirectly as it is meant to be used with the Admin UI exclusively.\n", + "produces": [ + "application/json" + ], + "tags": [ + "Transportation offices" + ], + "summary": "Get Transportation Office by ID", + "operationId": "getOfficeById", + "parameters": [ + { + "type": "string", + "format": "uuid", + "name": "officeId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "success", + "schema": { + "$ref": "#/definitions/TransportationOffice" + } + }, + "400": { + "description": "invalid request" + }, + "401": { + "description": "request requires user authentication" + }, + "404": { + "description": "Transportation Office not found" + }, + "500": { + "description": "server error" + } + } + } + }, "/organizations": { "get": { "description": "This endpoint returns a list of Organizations. Do not use this endpoint directly\nas it is meant to be used with the Admin UI exclusively.\n", @@ -6245,6 +6386,7 @@ func init() { "email", "telephone", "transportationOfficeId", + "transportationOfficeAssignments", "active", "roles", "edipi", @@ -6316,6 +6458,12 @@ func init() { "format": "telephone", "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$" }, + "transportationOfficeAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/TransportationOfficeAssignment" + } + }, "transportationOfficeId": { "type": "string", "format": "uuid" @@ -6372,10 +6520,11 @@ func init() { "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$", "example": "212-555-5555" }, - "transportationOfficeId": { - "type": "string", - "format": "uuid", - "example": "c56a4180-65aa-42ec-a945-5fd21dec0538" + "transportationOfficeAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/OfficeUserTransportationOfficeAssignment" + } } } }, @@ -6413,6 +6562,22 @@ func init() { } } }, + "OfficeUserTransportationOfficeAssignment": { + "type": "object", + "properties": { + "primaryOffice": { + "type": "boolean", + "title": "primaryOffice", + "x-nullable": true + }, + "transportationOfficeId": { + "type": "string", + "format": "uuid", + "title": "transportationOfficeId", + "example": "c56a4180-65aa-42ec-a945-5fd21dec0538" + } + } + }, "OfficeUserUpdate": { "type": "object", "properties": { @@ -6455,10 +6620,11 @@ func init() { "x-nullable": true, "example": "212-555-5555" }, - "transportationOfficeId": { - "type": "string", - "format": "uuid", - "example": "c56a4180-65aa-42ec-a945-5fd21dec0538" + "transportationOfficeAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/OfficeUserTransportationOfficeAssignment" + } } } }, @@ -6784,6 +6950,38 @@ func init() { } } }, + "TransportationOfficeAssignment": { + "type": "object", + "properties": { + "createdAt": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "officeUserId": { + "type": "string", + "format": "uuid", + "example": "c56a4780-65aa-42ec-a945-5fd87dec0538" + }, + "primaryOffice": { + "type": "boolean", + "x-omitempty": false + }, + "transportationOffice": { + "$ref": "#/definitions/TransportationOffice" + }, + "transportationOfficeId": { + "type": "string", + "format": "uuid", + "example": "d67a4780-65aa-42ec-a945-5fd87dec0549" + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "readOnly": true + } + } + }, "TransportationOffices": { "type": "array", "items": { diff --git a/pkg/gen/adminmessages/office_user.go b/pkg/gen/adminmessages/office_user.go index 648b2f3ddaa..0a58b7a0861 100644 --- a/pkg/gen/adminmessages/office_user.go +++ b/pkg/gen/adminmessages/office_user.go @@ -83,6 +83,10 @@ type OfficeUser struct { // Pattern: ^[2-9]\d{2}-\d{3}-\d{4}$ Telephone *string `json:"telephone"` + // transportation office assignments + // Required: true + TransportationOfficeAssignments []*TransportationOfficeAssignment `json:"transportationOfficeAssignments"` + // transportation office Id // Required: true // Format: uuid @@ -159,6 +163,10 @@ func (m *OfficeUser) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateTransportationOfficeAssignments(formats); err != nil { + res = append(res, err) + } + if err := m.validateTransportationOfficeID(formats); err != nil { res = append(res, err) } @@ -391,6 +399,33 @@ func (m *OfficeUser) validateTelephone(formats strfmt.Registry) error { return nil } +func (m *OfficeUser) validateTransportationOfficeAssignments(formats strfmt.Registry) error { + + if err := validate.Required("transportationOfficeAssignments", "body", m.TransportationOfficeAssignments); err != nil { + return err + } + + for i := 0; i < len(m.TransportationOfficeAssignments); i++ { + if swag.IsZero(m.TransportationOfficeAssignments[i]) { // not required + continue + } + + if m.TransportationOfficeAssignments[i] != nil { + if err := m.TransportationOfficeAssignments[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("transportationOfficeAssignments" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("transportationOfficeAssignments" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + func (m *OfficeUser) validateTransportationOfficeID(formats strfmt.Registry) error { if err := validate.Required("transportationOfficeId", "body", m.TransportationOfficeID); err != nil { @@ -445,6 +480,10 @@ func (m *OfficeUser) ContextValidate(ctx context.Context, formats strfmt.Registr res = append(res, err) } + if err := m.contextValidateTransportationOfficeAssignments(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateUpdatedAt(ctx, formats); err != nil { res = append(res, err) } @@ -514,6 +553,31 @@ func (m *OfficeUser) contextValidateRoles(ctx context.Context, formats strfmt.Re return nil } +func (m *OfficeUser) contextValidateTransportationOfficeAssignments(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.TransportationOfficeAssignments); i++ { + + if m.TransportationOfficeAssignments[i] != nil { + + if swag.IsZero(m.TransportationOfficeAssignments[i]) { // not required + return nil + } + + if err := m.TransportationOfficeAssignments[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("transportationOfficeAssignments" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("transportationOfficeAssignments" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + func (m *OfficeUser) contextValidateUpdatedAt(ctx context.Context, formats strfmt.Registry) error { if err := validate.ReadOnly(ctx, "updatedAt", "body", strfmt.DateTime(m.UpdatedAt)); err != nil { diff --git a/pkg/gen/adminmessages/office_user_create.go b/pkg/gen/adminmessages/office_user_create.go index 404719daa25..6ad14306927 100644 --- a/pkg/gen/adminmessages/office_user_create.go +++ b/pkg/gen/adminmessages/office_user_create.go @@ -45,10 +45,8 @@ type OfficeUserCreate struct { // Pattern: ^[2-9]\d{2}-\d{3}-\d{4}$ Telephone string `json:"telephone,omitempty"` - // transportation office Id - // Example: c56a4180-65aa-42ec-a945-5fd21dec0538 - // Format: uuid - TransportationOfficeID strfmt.UUID `json:"transportationOfficeId,omitempty"` + // transportation office assignments + TransportationOfficeAssignments []*OfficeUserTransportationOfficeAssignment `json:"transportationOfficeAssignments"` } // Validate validates this office user create @@ -67,7 +65,7 @@ func (m *OfficeUserCreate) Validate(formats strfmt.Registry) error { res = append(res, err) } - if err := m.validateTransportationOfficeID(formats); err != nil { + if err := m.validateTransportationOfficeAssignments(formats); err != nil { res = append(res, err) } @@ -141,13 +139,27 @@ func (m *OfficeUserCreate) validateTelephone(formats strfmt.Registry) error { return nil } -func (m *OfficeUserCreate) validateTransportationOfficeID(formats strfmt.Registry) error { - if swag.IsZero(m.TransportationOfficeID) { // not required +func (m *OfficeUserCreate) validateTransportationOfficeAssignments(formats strfmt.Registry) error { + if swag.IsZero(m.TransportationOfficeAssignments) { // not required return nil } - if err := validate.FormatOf("transportationOfficeId", "body", "uuid", m.TransportationOfficeID.String(), formats); err != nil { - return err + for i := 0; i < len(m.TransportationOfficeAssignments); i++ { + if swag.IsZero(m.TransportationOfficeAssignments[i]) { // not required + continue + } + + if m.TransportationOfficeAssignments[i] != nil { + if err := m.TransportationOfficeAssignments[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("transportationOfficeAssignments" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("transportationOfficeAssignments" + "." + strconv.Itoa(i)) + } + return err + } + } + } return nil @@ -165,6 +177,10 @@ func (m *OfficeUserCreate) ContextValidate(ctx context.Context, formats strfmt.R res = append(res, err) } + if err := m.contextValidateTransportationOfficeAssignments(ctx, formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { return errors.CompositeValidationError(res...) } @@ -221,6 +237,31 @@ func (m *OfficeUserCreate) contextValidateRoles(ctx context.Context, formats str return nil } +func (m *OfficeUserCreate) contextValidateTransportationOfficeAssignments(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.TransportationOfficeAssignments); i++ { + + if m.TransportationOfficeAssignments[i] != nil { + + if swag.IsZero(m.TransportationOfficeAssignments[i]) { // not required + return nil + } + + if err := m.TransportationOfficeAssignments[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("transportationOfficeAssignments" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("transportationOfficeAssignments" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + // MarshalBinary interface implementation func (m *OfficeUserCreate) MarshalBinary() ([]byte, error) { if m == nil { diff --git a/pkg/gen/adminmessages/office_user_transportation_office_assignment.go b/pkg/gen/adminmessages/office_user_transportation_office_assignment.go new file mode 100644 index 00000000000..ad1fcaffffb --- /dev/null +++ b/pkg/gen/adminmessages/office_user_transportation_office_assignment.go @@ -0,0 +1,78 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package adminmessages + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// OfficeUserTransportationOfficeAssignment office user transportation office assignment +// +// swagger:model OfficeUserTransportationOfficeAssignment +type OfficeUserTransportationOfficeAssignment struct { + + // primaryOffice + PrimaryOffice *bool `json:"primaryOffice,omitempty"` + + // transportationOfficeId + // Example: c56a4180-65aa-42ec-a945-5fd21dec0538 + // Format: uuid + TransportationOfficeID strfmt.UUID `json:"transportationOfficeId,omitempty"` +} + +// Validate validates this office user transportation office assignment +func (m *OfficeUserTransportationOfficeAssignment) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateTransportationOfficeID(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *OfficeUserTransportationOfficeAssignment) validateTransportationOfficeID(formats strfmt.Registry) error { + if swag.IsZero(m.TransportationOfficeID) { // not required + return nil + } + + if err := validate.FormatOf("transportationOfficeId", "body", "uuid", m.TransportationOfficeID.String(), formats); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this office user transportation office assignment based on context it is used +func (m *OfficeUserTransportationOfficeAssignment) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *OfficeUserTransportationOfficeAssignment) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *OfficeUserTransportationOfficeAssignment) UnmarshalBinary(b []byte) error { + var res OfficeUserTransportationOfficeAssignment + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/gen/adminmessages/office_user_update.go b/pkg/gen/adminmessages/office_user_update.go index db9fbcb98ef..1210927a8e6 100644 --- a/pkg/gen/adminmessages/office_user_update.go +++ b/pkg/gen/adminmessages/office_user_update.go @@ -44,10 +44,8 @@ type OfficeUserUpdate struct { // Pattern: ^[2-9]\d{2}-\d{3}-\d{4}$ Telephone *string `json:"telephone,omitempty"` - // transportation office Id - // Example: c56a4180-65aa-42ec-a945-5fd21dec0538 - // Format: uuid - TransportationOfficeID strfmt.UUID `json:"transportationOfficeId,omitempty"` + // transportation office assignments + TransportationOfficeAssignments []*OfficeUserTransportationOfficeAssignment `json:"transportationOfficeAssignments"` } // Validate validates this office user update @@ -66,7 +64,7 @@ func (m *OfficeUserUpdate) Validate(formats strfmt.Registry) error { res = append(res, err) } - if err := m.validateTransportationOfficeID(formats); err != nil { + if err := m.validateTransportationOfficeAssignments(formats); err != nil { res = append(res, err) } @@ -140,13 +138,27 @@ func (m *OfficeUserUpdate) validateTelephone(formats strfmt.Registry) error { return nil } -func (m *OfficeUserUpdate) validateTransportationOfficeID(formats strfmt.Registry) error { - if swag.IsZero(m.TransportationOfficeID) { // not required +func (m *OfficeUserUpdate) validateTransportationOfficeAssignments(formats strfmt.Registry) error { + if swag.IsZero(m.TransportationOfficeAssignments) { // not required return nil } - if err := validate.FormatOf("transportationOfficeId", "body", "uuid", m.TransportationOfficeID.String(), formats); err != nil { - return err + for i := 0; i < len(m.TransportationOfficeAssignments); i++ { + if swag.IsZero(m.TransportationOfficeAssignments[i]) { // not required + continue + } + + if m.TransportationOfficeAssignments[i] != nil { + if err := m.TransportationOfficeAssignments[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("transportationOfficeAssignments" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("transportationOfficeAssignments" + "." + strconv.Itoa(i)) + } + return err + } + } + } return nil @@ -164,6 +176,10 @@ func (m *OfficeUserUpdate) ContextValidate(ctx context.Context, formats strfmt.R res = append(res, err) } + if err := m.contextValidateTransportationOfficeAssignments(ctx, formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { return errors.CompositeValidationError(res...) } @@ -220,6 +236,31 @@ func (m *OfficeUserUpdate) contextValidateRoles(ctx context.Context, formats str return nil } +func (m *OfficeUserUpdate) contextValidateTransportationOfficeAssignments(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.TransportationOfficeAssignments); i++ { + + if m.TransportationOfficeAssignments[i] != nil { + + if swag.IsZero(m.TransportationOfficeAssignments[i]) { // not required + return nil + } + + if err := m.TransportationOfficeAssignments[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("transportationOfficeAssignments" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("transportationOfficeAssignments" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + // MarshalBinary interface implementation func (m *OfficeUserUpdate) MarshalBinary() ([]byte, error) { if m == nil { diff --git a/pkg/gen/adminmessages/transportation_office_assignment.go b/pkg/gen/adminmessages/transportation_office_assignment.go new file mode 100644 index 00000000000..8deb239d7a5 --- /dev/null +++ b/pkg/gen/adminmessages/transportation_office_assignment.go @@ -0,0 +1,223 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package adminmessages + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// TransportationOfficeAssignment transportation office assignment +// +// swagger:model TransportationOfficeAssignment +type TransportationOfficeAssignment struct { + + // created at + // Read Only: true + // Format: date-time + CreatedAt strfmt.DateTime `json:"createdAt,omitempty"` + + // office user Id + // Example: c56a4780-65aa-42ec-a945-5fd87dec0538 + // Format: uuid + OfficeUserID strfmt.UUID `json:"officeUserId,omitempty"` + + // primary office + PrimaryOffice bool `json:"primaryOffice"` + + // transportation office + TransportationOffice *TransportationOffice `json:"transportationOffice,omitempty"` + + // transportation office Id + // Example: d67a4780-65aa-42ec-a945-5fd87dec0549 + // Format: uuid + TransportationOfficeID strfmt.UUID `json:"transportationOfficeId,omitempty"` + + // updated at + // Read Only: true + // Format: date-time + UpdatedAt strfmt.DateTime `json:"updatedAt,omitempty"` +} + +// Validate validates this transportation office assignment +func (m *TransportationOfficeAssignment) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateCreatedAt(formats); err != nil { + res = append(res, err) + } + + if err := m.validateOfficeUserID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateTransportationOffice(formats); err != nil { + res = append(res, err) + } + + if err := m.validateTransportationOfficeID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateUpdatedAt(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *TransportationOfficeAssignment) validateCreatedAt(formats strfmt.Registry) error { + if swag.IsZero(m.CreatedAt) { // not required + return nil + } + + if err := validate.FormatOf("createdAt", "body", "date-time", m.CreatedAt.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *TransportationOfficeAssignment) validateOfficeUserID(formats strfmt.Registry) error { + if swag.IsZero(m.OfficeUserID) { // not required + return nil + } + + if err := validate.FormatOf("officeUserId", "body", "uuid", m.OfficeUserID.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *TransportationOfficeAssignment) validateTransportationOffice(formats strfmt.Registry) error { + if swag.IsZero(m.TransportationOffice) { // not required + return nil + } + + if m.TransportationOffice != nil { + if err := m.TransportationOffice.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("transportationOffice") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("transportationOffice") + } + return err + } + } + + return nil +} + +func (m *TransportationOfficeAssignment) validateTransportationOfficeID(formats strfmt.Registry) error { + if swag.IsZero(m.TransportationOfficeID) { // not required + return nil + } + + if err := validate.FormatOf("transportationOfficeId", "body", "uuid", m.TransportationOfficeID.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *TransportationOfficeAssignment) validateUpdatedAt(formats strfmt.Registry) error { + if swag.IsZero(m.UpdatedAt) { // not required + return nil + } + + if err := validate.FormatOf("updatedAt", "body", "date-time", m.UpdatedAt.String(), formats); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this transportation office assignment based on the context it is used +func (m *TransportationOfficeAssignment) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateCreatedAt(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateTransportationOffice(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateUpdatedAt(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *TransportationOfficeAssignment) contextValidateCreatedAt(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "createdAt", "body", strfmt.DateTime(m.CreatedAt)); err != nil { + return err + } + + return nil +} + +func (m *TransportationOfficeAssignment) contextValidateTransportationOffice(ctx context.Context, formats strfmt.Registry) error { + + if m.TransportationOffice != nil { + + if swag.IsZero(m.TransportationOffice) { // not required + return nil + } + + if err := m.TransportationOffice.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("transportationOffice") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("transportationOffice") + } + return err + } + } + + return nil +} + +func (m *TransportationOfficeAssignment) contextValidateUpdatedAt(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "updatedAt", "body", strfmt.DateTime(m.UpdatedAt)); err != nil { + return err + } + + return nil +} + +// MarshalBinary interface implementation +func (m *TransportationOfficeAssignment) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *TransportationOfficeAssignment) UnmarshalBinary(b []byte) error { + var res TransportationOfficeAssignment + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/gen/ghcapi/configure_mymove.go b/pkg/gen/ghcapi/configure_mymove.go index 40957c936cf..4ca4c62707f 100644 --- a/pkg/gen/ghcapi/configure_mymove.go +++ b/pkg/gen/ghcapi/configure_mymove.go @@ -30,6 +30,7 @@ import ( "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/ppm" "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/pws_violations" "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/queues" + "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/re_service_items" "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/report_violations" "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/shipment" "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/tac" @@ -215,6 +216,11 @@ func configureAPI(api *ghcoperations.MymoveAPI) http.Handler { return middleware.NotImplemented("operation ppm.FinishDocumentReview has not yet been implemented") }) } + if api.ReServiceItemsGetAllReServiceItemsHandler == nil { + api.ReServiceItemsGetAllReServiceItemsHandler = re_service_items.GetAllReServiceItemsHandlerFunc(func(params re_service_items.GetAllReServiceItemsParams) middleware.Responder { + return middleware.NotImplemented("operation re_service_items.GetAllReServiceItems has not yet been implemented") + }) + } if api.CustomerGetCustomerHandler == nil { api.CustomerGetCustomerHandler = customer.GetCustomerHandlerFunc(func(params customer.GetCustomerParams) middleware.Responder { return middleware.NotImplemented("operation customer.GetCustomer has not yet been implemented") diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index ca5c893e1ee..831c9e36d20 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -2563,7 +2563,7 @@ func init() { }, "/moves/{moveID}/financial-review-flag": { "post": { - "description": "This sets a flag which indicates that the move should be reviewed by a fincancial office. For example, if the origin or destination address of a shipment is far from the duty location and may incur excess costs to the customer.", + "description": "This sets a flag which indicates that the move should be reviewed by a fincancial office. For example, if the origin or delivery address of a shipment is far from the duty location and may incur excess costs to the customer.", "consumes": [ "application/json" ], @@ -4466,7 +4466,7 @@ func init() { }, { "type": "string", - "description": "Used to return a queue for a GBLOC other than the default of the current user. Requires the HQ role. The parameter is ignored if the requesting user does not have the necessary role.\n", + "description": "Used to return a queue for a GBLOC other than the default of the current user. Requires the HQ role or a secondary transportation office assignment. The parameter is ignored if the requesting user does not have the necessary role or assignment.\n", "name": "viewAsGBLOC", "in": "query" }, @@ -4510,6 +4510,12 @@ func init() { "description": "Only used for Services Counseling queue. If true, show PPM moves origin locations that are ready for closeout. Otherwise, show all other moves origin locations.", "name": "needsPPMCloseout", "in": "query" + }, + { + "type": "string", + "description": "Used to return an origins list for a GBLOC other than the default of the current user. Requires the HQ role or a secondary transportation office assignment. The parameter is ignored if the requesting user does not have the necessary role or assignment.", + "name": "viewAsGBLOC", + "in": "query" } ], "responses": { @@ -4657,7 +4663,7 @@ func init() { }, { "type": "string", - "description": "Used to return a queue for a GBLOC other than the default of the current user. Requires the HQ role. The parameter is ignored if the requesting user does not have the necessary role.\n", + "description": "Used to return a queue for a GBLOC other than the default of the current user. Requires the HQ role or a secondary transportation office assignment. The parameter is ignored if the requesting user does not have the necessary role or assignment.\n", "name": "viewAsGBLOC", "in": "query" }, @@ -4823,7 +4829,7 @@ func init() { }, { "type": "string", - "description": "Used to return a queue for a GBLOC other than the default of the current user. Requires the HQ role. The parameter is ignored if the requesting user does not have the necessary role.\n", + "description": "Used to return a queue for a GBLOC other than the default of the current user. Requires the HQ role or a secondary transportation office assignment. The parameter is ignored if the requesting user does not have the necessary role or assignment.\n", "name": "viewAsGBLOC", "in": "query" } @@ -4908,6 +4914,39 @@ func init() { } } }, + "/re-service-items": { + "get": { + "description": "Get ReServiceItems", + "produces": [ + "application/json" + ], + "tags": [ + "reServiceItems" + ], + "summary": "Returns all ReServiceItems (Service Code, Service Name, Market, Shipment Type, Auto Approved)", + "operationId": "getAllReServiceItems", + "responses": { + "200": { + "description": "Successfully retrieved all ReServiceItems.", + "schema": { + "$ref": "#/definitions/ReServiceItems" + } + }, + "400": { + "$ref": "#/responses/InvalidRequest" + }, + "401": { + "$ref": "#/responses/PermissionDenied" + }, + "404": { + "$ref": "#/responses/NotFound" + }, + "500": { + "$ref": "#/responses/ServerError" + } + } + } + }, "/report-violations/{reportID}": { "get": { "description": "Fetch the report violations for an evaluation report", @@ -6605,9 +6644,6 @@ func init() { "firstName": { "type": "string" }, - "hasSafetyPrivilege": { - "type": "boolean" - }, "lastName": { "type": "string" }, @@ -6777,6 +6813,12 @@ func init() { "CounselingUpdateAllowancePayload": { "type": "object", "properties": { + "accompaniedTour": { + "description": "Indicates if the move entitlement allows dependents to travel to the new Permanent Duty Station (PDS). This is only present on OCONUS moves.", + "type": "boolean", + "x-nullable": true, + "example": true + }, "agency": { "$ref": "#/definitions/Affiliation" }, @@ -6784,6 +6826,18 @@ func init() { "type": "boolean", "x-nullable": true }, + "dependentsTwelveAndOver": { + "description": "Indicates the number of dependents of the age twelve or older for a move. This is only present on OCONUS moves.", + "type": "integer", + "x-nullable": true, + "example": 3 + }, + "dependentsUnderTwelve": { + "description": "Indicates the number of dependents under the age of twelve for a move. This is only present on OCONUS moves.", + "type": "integer", + "x-nullable": true, + "example": 5 + }, "grade": { "$ref": "#/definitions/Grade" }, @@ -6822,6 +6876,11 @@ func init() { "storageInTransit": { "description": "the number of storage in transit days that the customer is entitled to for a given shipment on their move", "type": "integer" + }, + "ubAllowance": { + "type": "integer", + "x-nullable": true, + "example": 500 } } }, @@ -7346,9 +7405,27 @@ func init() { "newDutyLocationId" ], "properties": { + "accompaniedTour": { + "description": "Indicates if the move entitlement allows dependents to travel to the new Permanent Duty Station (PDS). This is only present on OCONUS moves.", + "type": "boolean", + "x-nullable": true, + "example": true + }, "departmentIndicator": { "$ref": "#/definitions/DeptIndicator" }, + "dependentsTwelveAndOver": { + "description": "Indicates the number of dependents of the age twelve or older for a move. This is only present on OCONUS moves.", + "type": "integer", + "x-nullable": true, + "example": 3 + }, + "dependentsUnderTwelve": { + "description": "Indicates the number of dependents under the age of twelve for a move. This is only present on OCONUS moves.", + "type": "integer", + "x-nullable": true, + "example": 5 + }, "grade": { "$ref": "#/definitions/Grade" }, @@ -7917,6 +7994,12 @@ func init() { "Entitlements": { "type": "object", "properties": { + "accompaniedTour": { + "description": "Indicates if the move entitlement allows dependents to travel to the new Permanent Duty Station (PDS). This is only present on OCONUS moves.", + "type": "boolean", + "x-nullable": true, + "example": true + }, "authorizedWeight": { "type": "integer", "x-formatting": "weight", @@ -7928,6 +8011,18 @@ func init() { "x-nullable": true, "example": true }, + "dependentsTwelveAndOver": { + "description": "Indicates the number of dependents of the age twelve or older for a move. This is only present on OCONUS moves.", + "type": "integer", + "x-nullable": true, + "example": 3 + }, + "dependentsUnderTwelve": { + "description": "Indicates the number of dependents under the age of twelve for a move. This is only present on OCONUS moves.", + "type": "integer", + "x-nullable": true, + "example": 5 + }, "eTag": { "type": "string" }, @@ -7982,6 +8077,12 @@ func init() { "type": "integer", "x-formatting": "weight", "example": 500 + }, + "unaccompaniedBaggageAllowance": { + "description": "The amount of weight in pounds that the move is entitled for shipment types of Unaccompanied Baggage.", + "type": "integer", + "x-nullable": true, + "example": 3 } } }, @@ -8800,6 +8901,14 @@ func init() { "format": "date-time", "readOnly": true }, + "destinationGBLOC": { + "type": "string", + "example": "AGFM" + }, + "destinationPostalCode": { + "type": "string", + "example": "90210" + }, "eTag": { "type": "string", "readOnly": true @@ -9377,7 +9486,7 @@ func init() { "MTOShipment": { "properties": { "actualDeliveryDate": { - "description": "The actual date that the shipment was delivered to the destination address by the Prime", + "description": "The actual date that the shipment was delivered to the delivery address by the Prime", "type": "string", "format": "date", "x-nullable": true @@ -9845,7 +9954,7 @@ func init() { "type": "string", "x-nullable": true, "readOnly": true, - "example": "Destination address is too far from duty location" + "example": "Delivery Address is too far from duty location" }, "id": { "type": "string", @@ -10546,6 +10655,12 @@ func init() { "transportationOffice": { "$ref": "#/definitions/TransportationOffice" }, + "transportationOfficeAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/TransportationOfficeAssignment" + } + }, "transportationOfficeId": { "type": "string", "format": "uuid" @@ -10861,15 +10976,19 @@ func init() { "WOUNDED_WARRIOR", "BLUEBARK", "SAFETY", - "TEMPORARY_DUTY" + "TEMPORARY_DUTY", + "EARLY_RETURN_OF_DEPENDENTS", + "STUDENT_TRAVEL" ], "x-display-value": { "BLUEBARK": "BLUEBARK", + "EARLY_RETURN_OF_DEPENDENTS": "Early Return of Dependents", "LOCAL_MOVE": "Local Move", "PERMANENT_CHANGE_OF_STATION": "Permanent Change Of Station", "RETIREMENT": "Retirement", "SAFETY": "Safety", "SEPARATION": "Separation", + "STUDENT_TRAVEL": "Student Travel", "TEMPORARY_DUTY": "Temporary Duty (TDY)", "WOUNDED_WARRIOR": "Wounded Warrior" } @@ -11434,6 +11553,13 @@ func init() { "advanceStatus": { "$ref": "#/definitions/PPMAdvanceStatus" }, + "allowableWeight": { + "description": "The allowable weight of the PPM shipment goods being moved.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4300 + }, "approvedAt": { "description": "The timestamp of when the shipment was approved and the service member can begin their move.", "type": "string", @@ -12460,6 +12586,104 @@ func init() { } } }, + "ReServiceItem": { + "description": "A Service Item which ties an ReService, Market, and Shipment Type together", + "type": "object", + "properties": { + "isAutoApproved": { + "type": "boolean", + "example": true + }, + "marketCode": { + "type": "string", + "enum": [ + "i", + "d" + ], + "example": "i (International), d (Domestic)" + }, + "serviceCode": { + "type": "string", + "enum": [ + "CS", + "DBHF", + "DBTF", + "DCRT", + "DCRTSA", + "DDASIT", + "DDDSIT", + "DDFSIT", + "DDP", + "DDSFSC", + "DDSHUT", + "DLH", + "DMHF", + "DNPK", + "DOASIT", + "DOFSIT", + "DOP", + "DOPSIT", + "DOSFSC", + "DOSHUT", + "DPK", + "DSH", + "DUCRT", + "DUPK", + "FSC", + "IBHF", + "IBTF", + "ICRT", + "ICRTSA", + "IDASIT", + "IDDSIT", + "IDFSIT", + "IDSFSC", + "IDSHUT", + "IHPK", + "IHUPK", + "INPK", + "IOASIT", + "IOFSIT", + "IOPSIT", + "IOSFSC", + "IOSHUT", + "ISLH", + "IUBPK", + "IUBUPK", + "IUCRT", + "MS", + "PODFSC", + "POEFSC", + "UBP" + ], + "example": "UBP" + }, + "serviceName": { + "type": "string", + "example": "International UB, International Shipping \u0026 Linehaul" + }, + "shipmentType": { + "type": "string", + "enum": [ + "BOAT_HAUL_AWAY", + "BOAT_TOW_AWAY", + "HHG", + "HHG_INTO_NTS_DOMESTIC", + "HHG_OUTOF_NTS_DOMESTIC", + "MOBILE_HOME", + "PPM", + "UNACCOMPANIED_BAGGAGE" + ], + "example": "HHG, UNACCOMPANIED_BAGGAGE" + } + } + }, + "ReServiceItems": { + "type": "array", + "items": { + "$ref": "#/definitions/ReServiceItem" + } + }, "RejectShipment": { "required": [ "rejectionReason" @@ -12884,16 +13108,16 @@ func init() { "branch": { "type": "string" }, - "destinationDutyLocationPostalCode": { + "destinationGBLOC": { + "$ref": "#/definitions/GBLOC" + }, + "destinationPostalCode": { "type": "string", "format": "zip", "title": "ZIP", "pattern": "^(\\d{5})$", "example": "90210" }, - "destinationGBLOC": { - "$ref": "#/definitions/GBLOC" - }, "edipi": { "type": "string", "x-nullable": true, @@ -13123,7 +13347,7 @@ func init() { } }, "ShipmentAddressUpdate": { - "description": "This represents a destination address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO.\n", + "description": "This represents a delivery address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO.\n", "type": "object", "required": [ "id", @@ -13151,7 +13375,7 @@ func init() { "$ref": "#/definitions/Address" }, "newSitDistanceBetween": { - "description": "The distance between the original SIT address and requested new destination address of shipment", + "description": "The distance between the original SIT address and requested new delivery address of shipment", "type": "integer", "example": 88 }, @@ -13163,7 +13387,7 @@ func init() { "example": "This is an office remark" }, "oldSitDistanceBetween": { - "description": "The distance between the original SIT address and the previous/old destination address of shipment", + "description": "The distance between the original SIT address and the previous/old delivery address of shipment", "type": "integer", "example": 50 }, @@ -13474,6 +13698,43 @@ func init() { } } }, + "TransportationOfficeAssignment": { + "type": "object", + "required": [ + "officeUserId", + "transportationOfficeId", + "primaryOffice" + ], + "properties": { + "createdAt": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "officeUserId": { + "type": "string", + "format": "uuid", + "example": "c56a4780-65aa-42ec-a945-5fd87dec0538" + }, + "primaryOffice": { + "type": "boolean", + "x-omitempty": false + }, + "transportationOffice": { + "$ref": "#/definitions/TransportationOffice" + }, + "transportationOfficeId": { + "type": "string", + "format": "uuid", + "example": "d67a4780-65aa-42ec-a945-5fd87dec0549" + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "readOnly": true + } + } + }, "TransportationOffices": { "type": "array", "items": { @@ -13483,6 +13744,12 @@ func init() { "UpdateAllowancePayload": { "type": "object", "properties": { + "accompaniedTour": { + "description": "Indicates if the move entitlement allows dependents to travel to the new Permanent Duty Station (PDS). This is only present on OCONUS moves.", + "type": "boolean", + "x-nullable": true, + "example": true + }, "agency": { "$ref": "#/definitions/Affiliation" }, @@ -13490,6 +13757,18 @@ func init() { "type": "boolean", "x-nullable": true }, + "dependentsTwelveAndOver": { + "description": "Indicates the number of dependents of the age twelve or older for a move. This is only present on OCONUS moves.", + "type": "integer", + "x-nullable": true, + "example": 3 + }, + "dependentsUnderTwelve": { + "description": "Indicates the number of dependents under the age of twelve for a move. This is only present on OCONUS moves.", + "type": "integer", + "x-nullable": true, + "example": 5 + }, "grade": { "$ref": "#/definitions/Grade" }, @@ -13528,6 +13807,11 @@ func init() { "storageInTransit": { "description": "the number of storage in transit days that the customer is entitled to for a given shipment on their move", "type": "integer" + }, + "ubAllowance": { + "type": "integer", + "x-nullable": true, + "example": 500 } } }, @@ -13926,6 +14210,12 @@ func init() { "x-nullable": true, "$ref": "#/definitions/PPMAdvanceStatus" }, + "allowableWeight": { + "description": "The allowable weight of the PPM shipment goods being moved.", + "type": "integer", + "x-nullable": true, + "example": 4300 + }, "destinationAddress": { "allOf": [ { @@ -14282,10 +14572,6 @@ func init() { "description": "Indicates the adjusted net weight of the vehicle", "type": "integer" }, - "allowableWeight": { - "description": "Indicates the maximum reimbursable weight of the shipment", - "type": "integer" - }, "emptyWeight": { "description": "Weight of the vehicle when empty.", "type": "integer" @@ -14441,12 +14727,6 @@ func init() { "x-nullable": true, "x-omitempty": false }, - "allowableWeight": { - "description": "Maximum reimbursable weight.", - "type": "integer", - "x-nullable": true, - "x-omitempty": false - }, "createdAt": { "type": "string", "format": "date-time", @@ -14746,6 +15026,9 @@ func init() { }, { "name": "paymentRequests" + }, + { + "name": "reServiceItems" } ] }`)) @@ -17961,7 +18244,7 @@ func init() { }, "/moves/{moveID}/financial-review-flag": { "post": { - "description": "This sets a flag which indicates that the move should be reviewed by a fincancial office. For example, if the origin or destination address of a shipment is far from the duty location and may incur excess costs to the customer.", + "description": "This sets a flag which indicates that the move should be reviewed by a fincancial office. For example, if the origin or delivery address of a shipment is far from the duty location and may incur excess costs to the customer.", "consumes": [ "application/json" ], @@ -20329,7 +20612,7 @@ func init() { }, { "type": "string", - "description": "Used to return a queue for a GBLOC other than the default of the current user. Requires the HQ role. The parameter is ignored if the requesting user does not have the necessary role.\n", + "description": "Used to return a queue for a GBLOC other than the default of the current user. Requires the HQ role or a secondary transportation office assignment. The parameter is ignored if the requesting user does not have the necessary role or assignment.\n", "name": "viewAsGBLOC", "in": "query" }, @@ -20379,6 +20662,12 @@ func init() { "description": "Only used for Services Counseling queue. If true, show PPM moves origin locations that are ready for closeout. Otherwise, show all other moves origin locations.", "name": "needsPPMCloseout", "in": "query" + }, + { + "type": "string", + "description": "Used to return an origins list for a GBLOC other than the default of the current user. Requires the HQ role or a secondary transportation office assignment. The parameter is ignored if the requesting user does not have the necessary role or assignment.", + "name": "viewAsGBLOC", + "in": "query" } ], "responses": { @@ -20532,7 +20821,7 @@ func init() { }, { "type": "string", - "description": "Used to return a queue for a GBLOC other than the default of the current user. Requires the HQ role. The parameter is ignored if the requesting user does not have the necessary role.\n", + "description": "Used to return a queue for a GBLOC other than the default of the current user. Requires the HQ role or a secondary transportation office assignment. The parameter is ignored if the requesting user does not have the necessary role or assignment.\n", "name": "viewAsGBLOC", "in": "query" }, @@ -20704,7 +20993,7 @@ func init() { }, { "type": "string", - "description": "Used to return a queue for a GBLOC other than the default of the current user. Requires the HQ role. The parameter is ignored if the requesting user does not have the necessary role.\n", + "description": "Used to return a queue for a GBLOC other than the default of the current user. Requires the HQ role or a secondary transportation office assignment. The parameter is ignored if the requesting user does not have the necessary role or assignment.\n", "name": "viewAsGBLOC", "in": "query" } @@ -20801,6 +21090,51 @@ func init() { } } }, + "/re-service-items": { + "get": { + "description": "Get ReServiceItems", + "produces": [ + "application/json" + ], + "tags": [ + "reServiceItems" + ], + "summary": "Returns all ReServiceItems (Service Code, Service Name, Market, Shipment Type, Auto Approved)", + "operationId": "getAllReServiceItems", + "responses": { + "200": { + "description": "Successfully retrieved all ReServiceItems.", + "schema": { + "$ref": "#/definitions/ReServiceItems" + } + }, + "400": { + "description": "The request payload is invalid", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "401": { + "description": "The request was denied", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "404": { + "description": "The requested resource wasn't found", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "500": { + "description": "A server error occurred", + "schema": { + "$ref": "#/definitions/Error" + } + } + } + } + }, "/report-violations/{reportID}": { "get": { "description": "Fetch the report violations for an evaluation report", @@ -22850,9 +23184,6 @@ func init() { "firstName": { "type": "string" }, - "hasSafetyPrivilege": { - "type": "boolean" - }, "lastName": { "type": "string" }, @@ -23022,6 +23353,12 @@ func init() { "CounselingUpdateAllowancePayload": { "type": "object", "properties": { + "accompaniedTour": { + "description": "Indicates if the move entitlement allows dependents to travel to the new Permanent Duty Station (PDS). This is only present on OCONUS moves.", + "type": "boolean", + "x-nullable": true, + "example": true + }, "agency": { "$ref": "#/definitions/Affiliation" }, @@ -23029,6 +23366,18 @@ func init() { "type": "boolean", "x-nullable": true }, + "dependentsTwelveAndOver": { + "description": "Indicates the number of dependents of the age twelve or older for a move. This is only present on OCONUS moves.", + "type": "integer", + "x-nullable": true, + "example": 3 + }, + "dependentsUnderTwelve": { + "description": "Indicates the number of dependents under the age of twelve for a move. This is only present on OCONUS moves.", + "type": "integer", + "x-nullable": true, + "example": 5 + }, "grade": { "$ref": "#/definitions/Grade" }, @@ -23071,6 +23420,11 @@ func init() { "description": "the number of storage in transit days that the customer is entitled to for a given shipment on their move", "type": "integer", "minimum": 0 + }, + "ubAllowance": { + "type": "integer", + "x-nullable": true, + "example": 500 } } }, @@ -23595,9 +23949,27 @@ func init() { "newDutyLocationId" ], "properties": { + "accompaniedTour": { + "description": "Indicates if the move entitlement allows dependents to travel to the new Permanent Duty Station (PDS). This is only present on OCONUS moves.", + "type": "boolean", + "x-nullable": true, + "example": true + }, "departmentIndicator": { "$ref": "#/definitions/DeptIndicator" }, + "dependentsTwelveAndOver": { + "description": "Indicates the number of dependents of the age twelve or older for a move. This is only present on OCONUS moves.", + "type": "integer", + "x-nullable": true, + "example": 3 + }, + "dependentsUnderTwelve": { + "description": "Indicates the number of dependents under the age of twelve for a move. This is only present on OCONUS moves.", + "type": "integer", + "x-nullable": true, + "example": 5 + }, "grade": { "$ref": "#/definitions/Grade" }, @@ -24166,6 +24538,12 @@ func init() { "Entitlements": { "type": "object", "properties": { + "accompaniedTour": { + "description": "Indicates if the move entitlement allows dependents to travel to the new Permanent Duty Station (PDS). This is only present on OCONUS moves.", + "type": "boolean", + "x-nullable": true, + "example": true + }, "authorizedWeight": { "type": "integer", "x-formatting": "weight", @@ -24177,6 +24555,18 @@ func init() { "x-nullable": true, "example": true }, + "dependentsTwelveAndOver": { + "description": "Indicates the number of dependents of the age twelve or older for a move. This is only present on OCONUS moves.", + "type": "integer", + "x-nullable": true, + "example": 3 + }, + "dependentsUnderTwelve": { + "description": "Indicates the number of dependents under the age of twelve for a move. This is only present on OCONUS moves.", + "type": "integer", + "x-nullable": true, + "example": 5 + }, "eTag": { "type": "string" }, @@ -24231,6 +24621,12 @@ func init() { "type": "integer", "x-formatting": "weight", "example": 500 + }, + "unaccompaniedBaggageAllowance": { + "description": "The amount of weight in pounds that the move is entitled for shipment types of Unaccompanied Baggage.", + "type": "integer", + "x-nullable": true, + "example": 3 } } }, @@ -25049,6 +25445,14 @@ func init() { "format": "date-time", "readOnly": true }, + "destinationGBLOC": { + "type": "string", + "example": "AGFM" + }, + "destinationPostalCode": { + "type": "string", + "example": "90210" + }, "eTag": { "type": "string", "readOnly": true @@ -25626,7 +26030,7 @@ func init() { "MTOShipment": { "properties": { "actualDeliveryDate": { - "description": "The actual date that the shipment was delivered to the destination address by the Prime", + "description": "The actual date that the shipment was delivered to the delivery address by the Prime", "type": "string", "format": "date", "x-nullable": true @@ -26094,7 +26498,7 @@ func init() { "type": "string", "x-nullable": true, "readOnly": true, - "example": "Destination address is too far from duty location" + "example": "Delivery Address is too far from duty location" }, "id": { "type": "string", @@ -26795,6 +27199,12 @@ func init() { "transportationOffice": { "$ref": "#/definitions/TransportationOffice" }, + "transportationOfficeAssignments": { + "type": "array", + "items": { + "$ref": "#/definitions/TransportationOfficeAssignment" + } + }, "transportationOfficeId": { "type": "string", "format": "uuid" @@ -27110,15 +27520,19 @@ func init() { "WOUNDED_WARRIOR", "BLUEBARK", "SAFETY", - "TEMPORARY_DUTY" + "TEMPORARY_DUTY", + "EARLY_RETURN_OF_DEPENDENTS", + "STUDENT_TRAVEL" ], "x-display-value": { "BLUEBARK": "BLUEBARK", + "EARLY_RETURN_OF_DEPENDENTS": "Early Return of Dependents", "LOCAL_MOVE": "Local Move", "PERMANENT_CHANGE_OF_STATION": "Permanent Change Of Station", "RETIREMENT": "Retirement", "SAFETY": "Safety", "SEPARATION": "Separation", + "STUDENT_TRAVEL": "Student Travel", "TEMPORARY_DUTY": "Temporary Duty (TDY)", "WOUNDED_WARRIOR": "Wounded Warrior" } @@ -27756,6 +28170,14 @@ func init() { "advanceStatus": { "$ref": "#/definitions/PPMAdvanceStatus" }, + "allowableWeight": { + "description": "The allowable weight of the PPM shipment goods being moved.", + "type": "integer", + "minimum": 0, + "x-nullable": true, + "x-omitempty": false, + "example": 4300 + }, "approvedAt": { "description": "The timestamp of when the shipment was approved and the service member can begin their move.", "type": "string", @@ -28784,6 +29206,104 @@ func init() { } } }, + "ReServiceItem": { + "description": "A Service Item which ties an ReService, Market, and Shipment Type together", + "type": "object", + "properties": { + "isAutoApproved": { + "type": "boolean", + "example": true + }, + "marketCode": { + "type": "string", + "enum": [ + "i", + "d" + ], + "example": "i (International), d (Domestic)" + }, + "serviceCode": { + "type": "string", + "enum": [ + "CS", + "DBHF", + "DBTF", + "DCRT", + "DCRTSA", + "DDASIT", + "DDDSIT", + "DDFSIT", + "DDP", + "DDSFSC", + "DDSHUT", + "DLH", + "DMHF", + "DNPK", + "DOASIT", + "DOFSIT", + "DOP", + "DOPSIT", + "DOSFSC", + "DOSHUT", + "DPK", + "DSH", + "DUCRT", + "DUPK", + "FSC", + "IBHF", + "IBTF", + "ICRT", + "ICRTSA", + "IDASIT", + "IDDSIT", + "IDFSIT", + "IDSFSC", + "IDSHUT", + "IHPK", + "IHUPK", + "INPK", + "IOASIT", + "IOFSIT", + "IOPSIT", + "IOSFSC", + "IOSHUT", + "ISLH", + "IUBPK", + "IUBUPK", + "IUCRT", + "MS", + "PODFSC", + "POEFSC", + "UBP" + ], + "example": "UBP" + }, + "serviceName": { + "type": "string", + "example": "International UB, International Shipping \u0026 Linehaul" + }, + "shipmentType": { + "type": "string", + "enum": [ + "BOAT_HAUL_AWAY", + "BOAT_TOW_AWAY", + "HHG", + "HHG_INTO_NTS_DOMESTIC", + "HHG_OUTOF_NTS_DOMESTIC", + "MOBILE_HOME", + "PPM", + "UNACCOMPANIED_BAGGAGE" + ], + "example": "HHG, UNACCOMPANIED_BAGGAGE" + } + } + }, + "ReServiceItems": { + "type": "array", + "items": { + "$ref": "#/definitions/ReServiceItem" + } + }, "RejectShipment": { "required": [ "rejectionReason" @@ -29258,16 +29778,16 @@ func init() { "branch": { "type": "string" }, - "destinationDutyLocationPostalCode": { + "destinationGBLOC": { + "$ref": "#/definitions/GBLOC" + }, + "destinationPostalCode": { "type": "string", "format": "zip", "title": "ZIP", "pattern": "^(\\d{5})$", "example": "90210" }, - "destinationGBLOC": { - "$ref": "#/definitions/GBLOC" - }, "edipi": { "type": "string", "x-nullable": true, @@ -29497,7 +30017,7 @@ func init() { } }, "ShipmentAddressUpdate": { - "description": "This represents a destination address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO.\n", + "description": "This represents a delivery address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO.\n", "type": "object", "required": [ "id", @@ -29525,7 +30045,7 @@ func init() { "$ref": "#/definitions/Address" }, "newSitDistanceBetween": { - "description": "The distance between the original SIT address and requested new destination address of shipment", + "description": "The distance between the original SIT address and requested new delivery address of shipment", "type": "integer", "minimum": 0, "example": 88 @@ -29538,7 +30058,7 @@ func init() { "example": "This is an office remark" }, "oldSitDistanceBetween": { - "description": "The distance between the original SIT address and the previous/old destination address of shipment", + "description": "The distance between the original SIT address and the previous/old delivery address of shipment", "type": "integer", "minimum": 0, "example": 50 @@ -29850,6 +30370,43 @@ func init() { } } }, + "TransportationOfficeAssignment": { + "type": "object", + "required": [ + "officeUserId", + "transportationOfficeId", + "primaryOffice" + ], + "properties": { + "createdAt": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "officeUserId": { + "type": "string", + "format": "uuid", + "example": "c56a4780-65aa-42ec-a945-5fd87dec0538" + }, + "primaryOffice": { + "type": "boolean", + "x-omitempty": false + }, + "transportationOffice": { + "$ref": "#/definitions/TransportationOffice" + }, + "transportationOfficeId": { + "type": "string", + "format": "uuid", + "example": "d67a4780-65aa-42ec-a945-5fd87dec0549" + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "readOnly": true + } + } + }, "TransportationOffices": { "type": "array", "items": { @@ -29859,6 +30416,12 @@ func init() { "UpdateAllowancePayload": { "type": "object", "properties": { + "accompaniedTour": { + "description": "Indicates if the move entitlement allows dependents to travel to the new Permanent Duty Station (PDS). This is only present on OCONUS moves.", + "type": "boolean", + "x-nullable": true, + "example": true + }, "agency": { "$ref": "#/definitions/Affiliation" }, @@ -29866,6 +30429,18 @@ func init() { "type": "boolean", "x-nullable": true }, + "dependentsTwelveAndOver": { + "description": "Indicates the number of dependents of the age twelve or older for a move. This is only present on OCONUS moves.", + "type": "integer", + "x-nullable": true, + "example": 3 + }, + "dependentsUnderTwelve": { + "description": "Indicates the number of dependents under the age of twelve for a move. This is only present on OCONUS moves.", + "type": "integer", + "x-nullable": true, + "example": 5 + }, "grade": { "$ref": "#/definitions/Grade" }, @@ -29908,6 +30483,11 @@ func init() { "description": "the number of storage in transit days that the customer is entitled to for a given shipment on their move", "type": "integer", "minimum": 0 + }, + "ubAllowance": { + "type": "integer", + "x-nullable": true, + "example": 500 } } }, @@ -30306,6 +30886,13 @@ func init() { "x-nullable": true, "$ref": "#/definitions/PPMAdvanceStatus" }, + "allowableWeight": { + "description": "The allowable weight of the PPM shipment goods being moved.", + "type": "integer", + "minimum": 0, + "x-nullable": true, + "example": 4300 + }, "destinationAddress": { "allOf": [ { @@ -30664,11 +31251,6 @@ func init() { "type": "integer", "minimum": 0 }, - "allowableWeight": { - "description": "Indicates the maximum reimbursable weight of the shipment", - "type": "integer", - "minimum": 0 - }, "emptyWeight": { "description": "Weight of the vehicle when empty.", "type": "integer", @@ -30830,13 +31412,6 @@ func init() { "x-nullable": true, "x-omitempty": false }, - "allowableWeight": { - "description": "Maximum reimbursable weight.", - "type": "integer", - "minimum": 0, - "x-nullable": true, - "x-omitempty": false - }, "createdAt": { "type": "string", "format": "date-time", @@ -31140,6 +31715,9 @@ func init() { }, { "name": "paymentRequests" + }, + { + "name": "reServiceItems" } ] }`)) diff --git a/pkg/gen/ghcapi/ghcoperations/move/set_financial_review_flag.go b/pkg/gen/ghcapi/ghcoperations/move/set_financial_review_flag.go index 6ff0754f458..439a4363a7f 100644 --- a/pkg/gen/ghcapi/ghcoperations/move/set_financial_review_flag.go +++ b/pkg/gen/ghcapi/ghcoperations/move/set_financial_review_flag.go @@ -39,7 +39,7 @@ func NewSetFinancialReviewFlag(ctx *middleware.Context, handler SetFinancialRevi # Flags a move for financial office review -This sets a flag which indicates that the move should be reviewed by a fincancial office. For example, if the origin or destination address of a shipment is far from the duty location and may incur excess costs to the customer. +This sets a flag which indicates that the move should be reviewed by a fincancial office. For example, if the origin or delivery address of a shipment is far from the duty location and may incur excess costs to the customer. */ type SetFinancialReviewFlag struct { Context *middleware.Context diff --git a/pkg/gen/ghcapi/ghcoperations/mymove_api.go b/pkg/gen/ghcapi/ghcoperations/mymove_api.go index f89554384f9..0809cb3b701 100644 --- a/pkg/gen/ghcapi/ghcoperations/mymove_api.go +++ b/pkg/gen/ghcapi/ghcoperations/mymove_api.go @@ -38,6 +38,7 @@ import ( "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/ppm" "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/pws_violations" "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/queues" + "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/re_service_items" "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/report_violations" "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/shipment" "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/tac" @@ -156,6 +157,9 @@ func NewMymoveAPI(spec *loads.Document) *MymoveAPI { PpmFinishDocumentReviewHandler: ppm.FinishDocumentReviewHandlerFunc(func(params ppm.FinishDocumentReviewParams) middleware.Responder { return middleware.NotImplemented("operation ppm.FinishDocumentReview has not yet been implemented") }), + ReServiceItemsGetAllReServiceItemsHandler: re_service_items.GetAllReServiceItemsHandlerFunc(func(params re_service_items.GetAllReServiceItemsParams) middleware.Responder { + return middleware.NotImplemented("operation re_service_items.GetAllReServiceItems has not yet been implemented") + }), CustomerGetCustomerHandler: customer.GetCustomerHandlerFunc(func(params customer.GetCustomerParams) middleware.Responder { return middleware.NotImplemented("operation customer.GetCustomer has not yet been implemented") }), @@ -485,6 +489,8 @@ type MymoveAPI struct { MtoAgentFetchMTOAgentListHandler mto_agent.FetchMTOAgentListHandler // PpmFinishDocumentReviewHandler sets the operation handler for the finish document review operation PpmFinishDocumentReviewHandler ppm.FinishDocumentReviewHandler + // ReServiceItemsGetAllReServiceItemsHandler sets the operation handler for the get all re service items operation + ReServiceItemsGetAllReServiceItemsHandler re_service_items.GetAllReServiceItemsHandler // CustomerGetCustomerHandler sets the operation handler for the get customer operation CustomerGetCustomerHandler customer.GetCustomerHandler // CustomerSupportRemarksGetCustomerSupportRemarksForMoveHandler sets the operation handler for the get customer support remarks for move operation @@ -805,6 +811,9 @@ func (o *MymoveAPI) Validate() error { if o.PpmFinishDocumentReviewHandler == nil { unregistered = append(unregistered, "ppm.FinishDocumentReviewHandler") } + if o.ReServiceItemsGetAllReServiceItemsHandler == nil { + unregistered = append(unregistered, "re_service_items.GetAllReServiceItemsHandler") + } if o.CustomerGetCustomerHandler == nil { unregistered = append(unregistered, "customer.GetCustomerHandler") } @@ -1241,6 +1250,10 @@ func (o *MymoveAPI) initHandlerCache() { if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) } + o.handlers["GET"]["/re-service-items"] = re_service_items.NewGetAllReServiceItems(o.context, o.ReServiceItemsGetAllReServiceItemsHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } o.handlers["GET"]["/customer/{customerID}"] = customer.NewGetCustomer(o.context, o.CustomerGetCustomerHandler) if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_parameters.go b/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_parameters.go index 1538d7b4b9a..f1cfe32cfae 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_parameters.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_parameters.go @@ -106,7 +106,7 @@ type GetMovesQueueParams struct { In: query */ Status []string - /*Used to return a queue for a GBLOC other than the default of the current user. Requires the HQ role. The parameter is ignored if the requesting user does not have the necessary role. + /*Used to return a queue for a GBLOC other than the default of the current user. Requires the HQ role or a secondary transportation office assignment. The parameter is ignored if the requesting user does not have the necessary role or assignment. In: query */ diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_payment_requests_queue_parameters.go b/pkg/gen/ghcapi/ghcoperations/queues/get_payment_requests_queue_parameters.go index be6e0fe5d60..fe0d201031e 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_payment_requests_queue_parameters.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_payment_requests_queue_parameters.go @@ -100,7 +100,7 @@ type GetPaymentRequestsQueueParams struct { In: query */ SubmittedAt *strfmt.DateTime - /*Used to return a queue for a GBLOC other than the default of the current user. Requires the HQ role. The parameter is ignored if the requesting user does not have the necessary role. + /*Used to return a queue for a GBLOC other than the default of the current user. Requires the HQ role or a secondary transportation office assignment. The parameter is ignored if the requesting user does not have the necessary role or assignment. In: query */ diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_origin_list_parameters.go b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_origin_list_parameters.go index 5e55bad2298..df70ba4cbab 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_origin_list_parameters.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_origin_list_parameters.go @@ -36,6 +36,10 @@ type GetServicesCounselingOriginListParams struct { In: query */ NeedsPPMCloseout *bool + /*Used to return an origins list for a GBLOC other than the default of the current user. Requires the HQ role or a secondary transportation office assignment. The parameter is ignored if the requesting user does not have the necessary role or assignment. + In: query + */ + ViewAsGBLOC *string } // BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface @@ -53,6 +57,11 @@ func (o *GetServicesCounselingOriginListParams) BindRequest(r *http.Request, rou if err := o.bindNeedsPPMCloseout(qNeedsPPMCloseout, qhkNeedsPPMCloseout, route.Formats); err != nil { res = append(res, err) } + + qViewAsGBLOC, qhkViewAsGBLOC, _ := qs.GetOK("viewAsGBLOC") + if err := o.bindViewAsGBLOC(qViewAsGBLOC, qhkViewAsGBLOC, route.Formats); err != nil { + res = append(res, err) + } if len(res) > 0 { return errors.CompositeValidationError(res...) } @@ -81,3 +90,21 @@ func (o *GetServicesCounselingOriginListParams) bindNeedsPPMCloseout(rawData []s return nil } + +// bindViewAsGBLOC binds and validates parameter ViewAsGBLOC from query. +func (o *GetServicesCounselingOriginListParams) bindViewAsGBLOC(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.ViewAsGBLOC = &raw + + return nil +} diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_origin_list_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_origin_list_urlbuilder.go index 1a71962fdd6..adc6d175249 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_origin_list_urlbuilder.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_origin_list_urlbuilder.go @@ -16,6 +16,7 @@ import ( // GetServicesCounselingOriginListURL generates an URL for the get services counseling origin list operation type GetServicesCounselingOriginListURL struct { NeedsPPMCloseout *bool + ViewAsGBLOC *string _basePath string // avoid unkeyed usage @@ -59,6 +60,14 @@ func (o *GetServicesCounselingOriginListURL) Build() (*url.URL, error) { qs.Set("needsPPMCloseout", needsPPMCloseoutQ) } + var viewAsGBLOCQ string + if o.ViewAsGBLOC != nil { + viewAsGBLOCQ = *o.ViewAsGBLOC + } + if viewAsGBLOCQ != "" { + qs.Set("viewAsGBLOC", viewAsGBLOCQ) + } + _result.RawQuery = qs.Encode() return &_result, nil diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_parameters.go b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_parameters.go index 13bb6b7aea5..2b03f53918f 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_parameters.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_parameters.go @@ -130,7 +130,7 @@ type GetServicesCounselingQueueParams struct { In: query */ SubmittedAt *strfmt.DateTime - /*Used to return a queue for a GBLOC other than the default of the current user. Requires the HQ role. The parameter is ignored if the requesting user does not have the necessary role. + /*Used to return a queue for a GBLOC other than the default of the current user. Requires the HQ role or a secondary transportation office assignment. The parameter is ignored if the requesting user does not have the necessary role or assignment. In: query */ diff --git a/pkg/gen/ghcapi/ghcoperations/re_service_items/get_all_re_service_items.go b/pkg/gen/ghcapi/ghcoperations/re_service_items/get_all_re_service_items.go new file mode 100644 index 00000000000..1dd08a72a7c --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/re_service_items/get_all_re_service_items.go @@ -0,0 +1,58 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package re_service_items + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// GetAllReServiceItemsHandlerFunc turns a function with the right signature into a get all re service items handler +type GetAllReServiceItemsHandlerFunc func(GetAllReServiceItemsParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn GetAllReServiceItemsHandlerFunc) Handle(params GetAllReServiceItemsParams) middleware.Responder { + return fn(params) +} + +// GetAllReServiceItemsHandler interface for that can handle valid get all re service items params +type GetAllReServiceItemsHandler interface { + Handle(GetAllReServiceItemsParams) middleware.Responder +} + +// NewGetAllReServiceItems creates a new http.Handler for the get all re service items operation +func NewGetAllReServiceItems(ctx *middleware.Context, handler GetAllReServiceItemsHandler) *GetAllReServiceItems { + return &GetAllReServiceItems{Context: ctx, Handler: handler} +} + +/* + GetAllReServiceItems swagger:route GET /re-service-items reServiceItems getAllReServiceItems + +Returns all ReServiceItems (Service Code, Service Name, Market, Shipment Type, Auto Approved) + +Get ReServiceItems +*/ +type GetAllReServiceItems struct { + Context *middleware.Context + Handler GetAllReServiceItemsHandler +} + +func (o *GetAllReServiceItems) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewGetAllReServiceItemsParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/pkg/gen/ghcapi/ghcoperations/re_service_items/get_all_re_service_items_parameters.go b/pkg/gen/ghcapi/ghcoperations/re_service_items/get_all_re_service_items_parameters.go new file mode 100644 index 00000000000..3e66348bc46 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/re_service_items/get_all_re_service_items_parameters.go @@ -0,0 +1,46 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package re_service_items + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime/middleware" +) + +// NewGetAllReServiceItemsParams creates a new GetAllReServiceItemsParams object +// +// There are no default values defined in the spec. +func NewGetAllReServiceItemsParams() GetAllReServiceItemsParams { + + return GetAllReServiceItemsParams{} +} + +// GetAllReServiceItemsParams contains all the bound params for the get all re service items operation +// typically these are obtained from a http.Request +// +// swagger:parameters getAllReServiceItems +type GetAllReServiceItemsParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewGetAllReServiceItemsParams() beforehand. +func (o *GetAllReServiceItemsParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/pkg/gen/ghcapi/ghcoperations/re_service_items/get_all_re_service_items_responses.go b/pkg/gen/ghcapi/ghcoperations/re_service_items/get_all_re_service_items_responses.go new file mode 100644 index 00000000000..c5b7d2e48d3 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/re_service_items/get_all_re_service_items_responses.go @@ -0,0 +1,242 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package re_service_items + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "github.com/transcom/mymove/pkg/gen/ghcmessages" +) + +// GetAllReServiceItemsOKCode is the HTTP code returned for type GetAllReServiceItemsOK +const GetAllReServiceItemsOKCode int = 200 + +/* +GetAllReServiceItemsOK Successfully retrieved all ReServiceItems. + +swagger:response getAllReServiceItemsOK +*/ +type GetAllReServiceItemsOK struct { + + /* + In: Body + */ + Payload ghcmessages.ReServiceItems `json:"body,omitempty"` +} + +// NewGetAllReServiceItemsOK creates GetAllReServiceItemsOK with default headers values +func NewGetAllReServiceItemsOK() *GetAllReServiceItemsOK { + + return &GetAllReServiceItemsOK{} +} + +// WithPayload adds the payload to the get all re service items o k response +func (o *GetAllReServiceItemsOK) WithPayload(payload ghcmessages.ReServiceItems) *GetAllReServiceItemsOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get all re service items o k response +func (o *GetAllReServiceItemsOK) SetPayload(payload ghcmessages.ReServiceItems) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetAllReServiceItemsOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + payload := o.Payload + if payload == nil { + // return empty array + payload = ghcmessages.ReServiceItems{} + } + + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } +} + +// GetAllReServiceItemsBadRequestCode is the HTTP code returned for type GetAllReServiceItemsBadRequest +const GetAllReServiceItemsBadRequestCode int = 400 + +/* +GetAllReServiceItemsBadRequest The request payload is invalid + +swagger:response getAllReServiceItemsBadRequest +*/ +type GetAllReServiceItemsBadRequest struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewGetAllReServiceItemsBadRequest creates GetAllReServiceItemsBadRequest with default headers values +func NewGetAllReServiceItemsBadRequest() *GetAllReServiceItemsBadRequest { + + return &GetAllReServiceItemsBadRequest{} +} + +// WithPayload adds the payload to the get all re service items bad request response +func (o *GetAllReServiceItemsBadRequest) WithPayload(payload *ghcmessages.Error) *GetAllReServiceItemsBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get all re service items bad request response +func (o *GetAllReServiceItemsBadRequest) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetAllReServiceItemsBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// GetAllReServiceItemsUnauthorizedCode is the HTTP code returned for type GetAllReServiceItemsUnauthorized +const GetAllReServiceItemsUnauthorizedCode int = 401 + +/* +GetAllReServiceItemsUnauthorized The request was denied + +swagger:response getAllReServiceItemsUnauthorized +*/ +type GetAllReServiceItemsUnauthorized struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewGetAllReServiceItemsUnauthorized creates GetAllReServiceItemsUnauthorized with default headers values +func NewGetAllReServiceItemsUnauthorized() *GetAllReServiceItemsUnauthorized { + + return &GetAllReServiceItemsUnauthorized{} +} + +// WithPayload adds the payload to the get all re service items unauthorized response +func (o *GetAllReServiceItemsUnauthorized) WithPayload(payload *ghcmessages.Error) *GetAllReServiceItemsUnauthorized { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get all re service items unauthorized response +func (o *GetAllReServiceItemsUnauthorized) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetAllReServiceItemsUnauthorized) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(401) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// GetAllReServiceItemsNotFoundCode is the HTTP code returned for type GetAllReServiceItemsNotFound +const GetAllReServiceItemsNotFoundCode int = 404 + +/* +GetAllReServiceItemsNotFound The requested resource wasn't found + +swagger:response getAllReServiceItemsNotFound +*/ +type GetAllReServiceItemsNotFound struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewGetAllReServiceItemsNotFound creates GetAllReServiceItemsNotFound with default headers values +func NewGetAllReServiceItemsNotFound() *GetAllReServiceItemsNotFound { + + return &GetAllReServiceItemsNotFound{} +} + +// WithPayload adds the payload to the get all re service items not found response +func (o *GetAllReServiceItemsNotFound) WithPayload(payload *ghcmessages.Error) *GetAllReServiceItemsNotFound { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get all re service items not found response +func (o *GetAllReServiceItemsNotFound) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetAllReServiceItemsNotFound) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(404) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// GetAllReServiceItemsInternalServerErrorCode is the HTTP code returned for type GetAllReServiceItemsInternalServerError +const GetAllReServiceItemsInternalServerErrorCode int = 500 + +/* +GetAllReServiceItemsInternalServerError A server error occurred + +swagger:response getAllReServiceItemsInternalServerError +*/ +type GetAllReServiceItemsInternalServerError struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewGetAllReServiceItemsInternalServerError creates GetAllReServiceItemsInternalServerError with default headers values +func NewGetAllReServiceItemsInternalServerError() *GetAllReServiceItemsInternalServerError { + + return &GetAllReServiceItemsInternalServerError{} +} + +// WithPayload adds the payload to the get all re service items internal server error response +func (o *GetAllReServiceItemsInternalServerError) WithPayload(payload *ghcmessages.Error) *GetAllReServiceItemsInternalServerError { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get all re service items internal server error response +func (o *GetAllReServiceItemsInternalServerError) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetAllReServiceItemsInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(500) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/pkg/gen/ghcapi/ghcoperations/re_service_items/get_all_re_service_items_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/re_service_items/get_all_re_service_items_urlbuilder.go new file mode 100644 index 00000000000..be1ff8cd69e --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/re_service_items/get_all_re_service_items_urlbuilder.go @@ -0,0 +1,87 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package re_service_items + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" +) + +// GetAllReServiceItemsURL generates an URL for the get all re service items operation +type GetAllReServiceItemsURL struct { + _basePath string +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetAllReServiceItemsURL) WithBasePath(bp string) *GetAllReServiceItemsURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *GetAllReServiceItemsURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *GetAllReServiceItemsURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/re-service-items" + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/ghc/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *GetAllReServiceItemsURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *GetAllReServiceItemsURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *GetAllReServiceItemsURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on GetAllReServiceItemsURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on GetAllReServiceItemsURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *GetAllReServiceItemsURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/pkg/gen/ghcmessages/available_office_user.go b/pkg/gen/ghcmessages/available_office_user.go index 98f0ce1804f..5023b2ea1dd 100644 --- a/pkg/gen/ghcmessages/available_office_user.go +++ b/pkg/gen/ghcmessages/available_office_user.go @@ -22,9 +22,6 @@ type AvailableOfficeUser struct { // first name FirstName string `json:"firstName,omitempty"` - // has safety privilege - HasSafetyPrivilege bool `json:"hasSafetyPrivilege,omitempty"` - // last name LastName string `json:"lastName,omitempty"` diff --git a/pkg/gen/ghcmessages/counseling_update_allowance_payload.go b/pkg/gen/ghcmessages/counseling_update_allowance_payload.go index 8aff2d86426..d6bed9fac0c 100644 --- a/pkg/gen/ghcmessages/counseling_update_allowance_payload.go +++ b/pkg/gen/ghcmessages/counseling_update_allowance_payload.go @@ -19,12 +19,24 @@ import ( // swagger:model CounselingUpdateAllowancePayload type CounselingUpdateAllowancePayload struct { + // Indicates if the move entitlement allows dependents to travel to the new Permanent Duty Station (PDS). This is only present on OCONUS moves. + // Example: true + AccompaniedTour *bool `json:"accompaniedTour,omitempty"` + // agency Agency *Affiliation `json:"agency,omitempty"` // dependents authorized DependentsAuthorized *bool `json:"dependentsAuthorized,omitempty"` + // Indicates the number of dependents of the age twelve or older for a move. This is only present on OCONUS moves. + // Example: 3 + DependentsTwelveAndOver *int64 `json:"dependentsTwelveAndOver,omitempty"` + + // Indicates the number of dependents under the age of twelve for a move. This is only present on OCONUS moves. + // Example: 5 + DependentsUnderTwelve *int64 `json:"dependentsUnderTwelve,omitempty"` + // grade Grade *Grade `json:"grade,omitempty"` @@ -54,6 +66,10 @@ type CounselingUpdateAllowancePayload struct { // the number of storage in transit days that the customer is entitled to for a given shipment on their move // Minimum: 0 StorageInTransit *int64 `json:"storageInTransit,omitempty"` + + // ub allowance + // Example: 500 + UbAllowance *int64 `json:"ubAllowance,omitempty"` } // Validate validates this counseling update allowance payload diff --git a/pkg/gen/ghcmessages/create_orders.go b/pkg/gen/ghcmessages/create_orders.go index 34dd9671569..25f77f42c15 100644 --- a/pkg/gen/ghcmessages/create_orders.go +++ b/pkg/gen/ghcmessages/create_orders.go @@ -19,9 +19,21 @@ import ( // swagger:model CreateOrders type CreateOrders struct { + // Indicates if the move entitlement allows dependents to travel to the new Permanent Duty Station (PDS). This is only present on OCONUS moves. + // Example: true + AccompaniedTour *bool `json:"accompaniedTour,omitempty"` + // department indicator DepartmentIndicator *DeptIndicator `json:"departmentIndicator,omitempty"` + // Indicates the number of dependents of the age twelve or older for a move. This is only present on OCONUS moves. + // Example: 3 + DependentsTwelveAndOver *int64 `json:"dependentsTwelveAndOver,omitempty"` + + // Indicates the number of dependents under the age of twelve for a move. This is only present on OCONUS moves. + // Example: 5 + DependentsUnderTwelve *int64 `json:"dependentsUnderTwelve,omitempty"` + // grade Grade *Grade `json:"grade,omitempty"` diff --git a/pkg/gen/ghcmessages/entitlements.go b/pkg/gen/ghcmessages/entitlements.go index 371451ad198..2ee15f3d03a 100644 --- a/pkg/gen/ghcmessages/entitlements.go +++ b/pkg/gen/ghcmessages/entitlements.go @@ -19,6 +19,10 @@ import ( // swagger:model Entitlements type Entitlements struct { + // Indicates if the move entitlement allows dependents to travel to the new Permanent Duty Station (PDS). This is only present on OCONUS moves. + // Example: true + AccompaniedTour *bool `json:"accompaniedTour,omitempty"` + // authorized weight // Example: 2000 AuthorizedWeight *int64 `json:"authorizedWeight,omitempty"` @@ -27,6 +31,14 @@ type Entitlements struct { // Example: true DependentsAuthorized *bool `json:"dependentsAuthorized,omitempty"` + // Indicates the number of dependents of the age twelve or older for a move. This is only present on OCONUS moves. + // Example: 3 + DependentsTwelveAndOver *int64 `json:"dependentsTwelveAndOver,omitempty"` + + // Indicates the number of dependents under the age of twelve for a move. This is only present on OCONUS moves. + // Example: 5 + DependentsUnderTwelve *int64 `json:"dependentsUnderTwelve,omitempty"` + // e tag ETag string `json:"eTag,omitempty"` @@ -74,6 +86,10 @@ type Entitlements struct { // total weight // Example: 500 TotalWeight int64 `json:"totalWeight,omitempty"` + + // The amount of weight in pounds that the move is entitled for shipment types of Unaccompanied Baggage. + // Example: 3 + UnaccompaniedBaggageAllowance *int64 `json:"unaccompaniedBaggageAllowance,omitempty"` } // Validate validates this entitlements diff --git a/pkg/gen/ghcmessages/list_prime_move.go b/pkg/gen/ghcmessages/list_prime_move.go index 22a8f47b561..690f8097816 100644 --- a/pkg/gen/ghcmessages/list_prime_move.go +++ b/pkg/gen/ghcmessages/list_prime_move.go @@ -35,6 +35,14 @@ type ListPrimeMove struct { // Format: date-time CreatedAt strfmt.DateTime `json:"createdAt,omitempty"` + // destination g b l o c + // Example: AGFM + DestinationGBLOC string `json:"destinationGBLOC,omitempty"` + + // destination postal code + // Example: 90210 + DestinationPostalCode string `json:"destinationPostalCode,omitempty"` + // e tag // Read Only: true ETag string `json:"eTag,omitempty"` diff --git a/pkg/gen/ghcmessages/m_t_o_shipment.go b/pkg/gen/ghcmessages/m_t_o_shipment.go index ca5097209b4..78a6420cee4 100644 --- a/pkg/gen/ghcmessages/m_t_o_shipment.go +++ b/pkg/gen/ghcmessages/m_t_o_shipment.go @@ -20,7 +20,7 @@ import ( // swagger:model MTOShipment type MTOShipment struct { - // The actual date that the shipment was delivered to the destination address by the Prime + // The actual date that the shipment was delivered to the delivery address by the Prime // Format: date ActualDeliveryDate *strfmt.Date `json:"actualDeliveryDate,omitempty"` diff --git a/pkg/gen/ghcmessages/move.go b/pkg/gen/ghcmessages/move.go index 1bea0079581..bb67e748b11 100644 --- a/pkg/gen/ghcmessages/move.go +++ b/pkg/gen/ghcmessages/move.go @@ -82,7 +82,7 @@ type Move struct { FinancialReviewFlag bool `json:"financialReviewFlag,omitempty"` // financial review remarks - // Example: Destination address is too far from duty location + // Example: Delivery Address is too far from duty location // Read Only: true FinancialReviewRemarks *string `json:"financialReviewRemarks,omitempty"` diff --git a/pkg/gen/ghcmessages/office_user.go b/pkg/gen/ghcmessages/office_user.go index bc82c670482..3614d465a18 100644 --- a/pkg/gen/ghcmessages/office_user.go +++ b/pkg/gen/ghcmessages/office_user.go @@ -83,6 +83,9 @@ type OfficeUser struct { // transportation office TransportationOffice *TransportationOffice `json:"transportationOffice,omitempty"` + // transportation office assignments + TransportationOfficeAssignments []*TransportationOfficeAssignment `json:"transportationOfficeAssignments"` + // transportation office Id // Required: true // Format: uuid @@ -159,6 +162,10 @@ func (m *OfficeUser) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateTransportationOfficeAssignments(formats); err != nil { + res = append(res, err) + } + if err := m.validateTransportationOfficeID(formats); err != nil { res = append(res, err) } @@ -384,6 +391,32 @@ func (m *OfficeUser) validateTransportationOffice(formats strfmt.Registry) error return nil } +func (m *OfficeUser) validateTransportationOfficeAssignments(formats strfmt.Registry) error { + if swag.IsZero(m.TransportationOfficeAssignments) { // not required + return nil + } + + for i := 0; i < len(m.TransportationOfficeAssignments); i++ { + if swag.IsZero(m.TransportationOfficeAssignments[i]) { // not required + continue + } + + if m.TransportationOfficeAssignments[i] != nil { + if err := m.TransportationOfficeAssignments[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("transportationOfficeAssignments" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("transportationOfficeAssignments" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + func (m *OfficeUser) validateTransportationOfficeID(formats strfmt.Registry) error { if err := validate.Required("transportationOfficeId", "body", m.TransportationOfficeID); err != nil { @@ -438,6 +471,10 @@ func (m *OfficeUser) ContextValidate(ctx context.Context, formats strfmt.Registr res = append(res, err) } + if err := m.contextValidateTransportationOfficeAssignments(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateUpdatedAt(ctx, formats); err != nil { res = append(res, err) } @@ -503,6 +540,31 @@ func (m *OfficeUser) contextValidateTransportationOffice(ctx context.Context, fo return nil } +func (m *OfficeUser) contextValidateTransportationOfficeAssignments(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.TransportationOfficeAssignments); i++ { + + if m.TransportationOfficeAssignments[i] != nil { + + if swag.IsZero(m.TransportationOfficeAssignments[i]) { // not required + return nil + } + + if err := m.TransportationOfficeAssignments[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("transportationOfficeAssignments" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("transportationOfficeAssignments" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + func (m *OfficeUser) contextValidateUpdatedAt(ctx context.Context, formats strfmt.Registry) error { if err := validate.ReadOnly(ctx, "updatedAt", "body", strfmt.DateTime(m.UpdatedAt)); err != nil { diff --git a/pkg/gen/ghcmessages/orders_type.go b/pkg/gen/ghcmessages/orders_type.go index a578f540afe..13ed0b535c1 100644 --- a/pkg/gen/ghcmessages/orders_type.go +++ b/pkg/gen/ghcmessages/orders_type.go @@ -53,6 +53,12 @@ const ( // OrdersTypeTEMPORARYDUTY captures enum value "TEMPORARY_DUTY" OrdersTypeTEMPORARYDUTY OrdersType = "TEMPORARY_DUTY" + + // OrdersTypeEARLYRETURNOFDEPENDENTS captures enum value "EARLY_RETURN_OF_DEPENDENTS" + OrdersTypeEARLYRETURNOFDEPENDENTS OrdersType = "EARLY_RETURN_OF_DEPENDENTS" + + // OrdersTypeSTUDENTTRAVEL captures enum value "STUDENT_TRAVEL" + OrdersTypeSTUDENTTRAVEL OrdersType = "STUDENT_TRAVEL" ) // for schema @@ -60,7 +66,7 @@ var ordersTypeEnum []interface{} func init() { var res []OrdersType - if err := json.Unmarshal([]byte(`["PERMANENT_CHANGE_OF_STATION","LOCAL_MOVE","RETIREMENT","SEPARATION","WOUNDED_WARRIOR","BLUEBARK","SAFETY","TEMPORARY_DUTY"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["PERMANENT_CHANGE_OF_STATION","LOCAL_MOVE","RETIREMENT","SEPARATION","WOUNDED_WARRIOR","BLUEBARK","SAFETY","TEMPORARY_DUTY","EARLY_RETURN_OF_DEPENDENTS","STUDENT_TRAVEL"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/ghcmessages/p_p_m_shipment.go b/pkg/gen/ghcmessages/p_p_m_shipment.go index 117201a70f6..3b563ef0515 100644 --- a/pkg/gen/ghcmessages/p_p_m_shipment.go +++ b/pkg/gen/ghcmessages/p_p_m_shipment.go @@ -51,6 +51,11 @@ type PPMShipment struct { // advance status AdvanceStatus *PPMAdvanceStatus `json:"advanceStatus,omitempty"` + // The allowable weight of the PPM shipment goods being moved. + // Example: 4300 + // Minimum: 0 + AllowableWeight *int64 `json:"allowableWeight"` + // The timestamp of when the shipment was approved and the service member can begin their move. // Format: date-time ApprovedAt *strfmt.DateTime `json:"approvedAt"` @@ -231,6 +236,10 @@ func (m *PPMShipment) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateAllowableWeight(formats); err != nil { + res = append(res, err) + } + if err := m.validateApprovedAt(formats); err != nil { res = append(res, err) } @@ -392,6 +401,18 @@ func (m *PPMShipment) validateAdvanceStatus(formats strfmt.Registry) error { return nil } +func (m *PPMShipment) validateAllowableWeight(formats strfmt.Registry) error { + if swag.IsZero(m.AllowableWeight) { // not required + return nil + } + + if err := validate.MinimumInt("allowableWeight", "body", *m.AllowableWeight, 0, false); err != nil { + return err + } + + return nil +} + func (m *PPMShipment) validateApprovedAt(formats strfmt.Registry) error { if swag.IsZero(m.ApprovedAt) { // not required return nil diff --git a/pkg/gen/ghcmessages/re_service_item.go b/pkg/gen/ghcmessages/re_service_item.go new file mode 100644 index 00000000000..b0cae65c087 --- /dev/null +++ b/pkg/gen/ghcmessages/re_service_item.go @@ -0,0 +1,378 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package ghcmessages + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "encoding/json" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// ReServiceItem A Service Item which ties an ReService, Market, and Shipment Type together +// +// swagger:model ReServiceItem +type ReServiceItem struct { + + // is auto approved + // Example: true + IsAutoApproved bool `json:"isAutoApproved,omitempty"` + + // market code + // Example: i (International), d (Domestic) + // Enum: [i d] + MarketCode string `json:"marketCode,omitempty"` + + // service code + // Example: UBP + // Enum: [CS DBHF DBTF DCRT DCRTSA DDASIT DDDSIT DDFSIT DDP DDSFSC DDSHUT DLH DMHF DNPK DOASIT DOFSIT DOP DOPSIT DOSFSC DOSHUT DPK DSH DUCRT DUPK FSC IBHF IBTF ICRT ICRTSA IDASIT IDDSIT IDFSIT IDSFSC IDSHUT IHPK IHUPK INPK IOASIT IOFSIT IOPSIT IOSFSC IOSHUT ISLH IUBPK IUBUPK IUCRT MS PODFSC POEFSC UBP] + ServiceCode string `json:"serviceCode,omitempty"` + + // service name + // Example: International UB, International Shipping \u0026 Linehaul + ServiceName string `json:"serviceName,omitempty"` + + // shipment type + // Example: HHG, UNACCOMPANIED_BAGGAGE + // Enum: [BOAT_HAUL_AWAY BOAT_TOW_AWAY HHG HHG_INTO_NTS_DOMESTIC HHG_OUTOF_NTS_DOMESTIC MOBILE_HOME PPM UNACCOMPANIED_BAGGAGE] + ShipmentType string `json:"shipmentType,omitempty"` +} + +// Validate validates this re service item +func (m *ReServiceItem) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateMarketCode(formats); err != nil { + res = append(res, err) + } + + if err := m.validateServiceCode(formats); err != nil { + res = append(res, err) + } + + if err := m.validateShipmentType(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +var reServiceItemTypeMarketCodePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["i","d"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + reServiceItemTypeMarketCodePropEnum = append(reServiceItemTypeMarketCodePropEnum, v) + } +} + +const ( + + // ReServiceItemMarketCodeI captures enum value "i" + ReServiceItemMarketCodeI string = "i" + + // ReServiceItemMarketCodeD captures enum value "d" + ReServiceItemMarketCodeD string = "d" +) + +// prop value enum +func (m *ReServiceItem) validateMarketCodeEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, reServiceItemTypeMarketCodePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *ReServiceItem) validateMarketCode(formats strfmt.Registry) error { + if swag.IsZero(m.MarketCode) { // not required + return nil + } + + // value enum + if err := m.validateMarketCodeEnum("marketCode", "body", m.MarketCode); err != nil { + return err + } + + return nil +} + +var reServiceItemTypeServiceCodePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["CS","DBHF","DBTF","DCRT","DCRTSA","DDASIT","DDDSIT","DDFSIT","DDP","DDSFSC","DDSHUT","DLH","DMHF","DNPK","DOASIT","DOFSIT","DOP","DOPSIT","DOSFSC","DOSHUT","DPK","DSH","DUCRT","DUPK","FSC","IBHF","IBTF","ICRT","ICRTSA","IDASIT","IDDSIT","IDFSIT","IDSFSC","IDSHUT","IHPK","IHUPK","INPK","IOASIT","IOFSIT","IOPSIT","IOSFSC","IOSHUT","ISLH","IUBPK","IUBUPK","IUCRT","MS","PODFSC","POEFSC","UBP"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + reServiceItemTypeServiceCodePropEnum = append(reServiceItemTypeServiceCodePropEnum, v) + } +} + +const ( + + // ReServiceItemServiceCodeCS captures enum value "CS" + ReServiceItemServiceCodeCS string = "CS" + + // ReServiceItemServiceCodeDBHF captures enum value "DBHF" + ReServiceItemServiceCodeDBHF string = "DBHF" + + // ReServiceItemServiceCodeDBTF captures enum value "DBTF" + ReServiceItemServiceCodeDBTF string = "DBTF" + + // ReServiceItemServiceCodeDCRT captures enum value "DCRT" + ReServiceItemServiceCodeDCRT string = "DCRT" + + // ReServiceItemServiceCodeDCRTSA captures enum value "DCRTSA" + ReServiceItemServiceCodeDCRTSA string = "DCRTSA" + + // ReServiceItemServiceCodeDDASIT captures enum value "DDASIT" + ReServiceItemServiceCodeDDASIT string = "DDASIT" + + // ReServiceItemServiceCodeDDDSIT captures enum value "DDDSIT" + ReServiceItemServiceCodeDDDSIT string = "DDDSIT" + + // ReServiceItemServiceCodeDDFSIT captures enum value "DDFSIT" + ReServiceItemServiceCodeDDFSIT string = "DDFSIT" + + // ReServiceItemServiceCodeDDP captures enum value "DDP" + ReServiceItemServiceCodeDDP string = "DDP" + + // ReServiceItemServiceCodeDDSFSC captures enum value "DDSFSC" + ReServiceItemServiceCodeDDSFSC string = "DDSFSC" + + // ReServiceItemServiceCodeDDSHUT captures enum value "DDSHUT" + ReServiceItemServiceCodeDDSHUT string = "DDSHUT" + + // ReServiceItemServiceCodeDLH captures enum value "DLH" + ReServiceItemServiceCodeDLH string = "DLH" + + // ReServiceItemServiceCodeDMHF captures enum value "DMHF" + ReServiceItemServiceCodeDMHF string = "DMHF" + + // ReServiceItemServiceCodeDNPK captures enum value "DNPK" + ReServiceItemServiceCodeDNPK string = "DNPK" + + // ReServiceItemServiceCodeDOASIT captures enum value "DOASIT" + ReServiceItemServiceCodeDOASIT string = "DOASIT" + + // ReServiceItemServiceCodeDOFSIT captures enum value "DOFSIT" + ReServiceItemServiceCodeDOFSIT string = "DOFSIT" + + // ReServiceItemServiceCodeDOP captures enum value "DOP" + ReServiceItemServiceCodeDOP string = "DOP" + + // ReServiceItemServiceCodeDOPSIT captures enum value "DOPSIT" + ReServiceItemServiceCodeDOPSIT string = "DOPSIT" + + // ReServiceItemServiceCodeDOSFSC captures enum value "DOSFSC" + ReServiceItemServiceCodeDOSFSC string = "DOSFSC" + + // ReServiceItemServiceCodeDOSHUT captures enum value "DOSHUT" + ReServiceItemServiceCodeDOSHUT string = "DOSHUT" + + // ReServiceItemServiceCodeDPK captures enum value "DPK" + ReServiceItemServiceCodeDPK string = "DPK" + + // ReServiceItemServiceCodeDSH captures enum value "DSH" + ReServiceItemServiceCodeDSH string = "DSH" + + // ReServiceItemServiceCodeDUCRT captures enum value "DUCRT" + ReServiceItemServiceCodeDUCRT string = "DUCRT" + + // ReServiceItemServiceCodeDUPK captures enum value "DUPK" + ReServiceItemServiceCodeDUPK string = "DUPK" + + // ReServiceItemServiceCodeFSC captures enum value "FSC" + ReServiceItemServiceCodeFSC string = "FSC" + + // ReServiceItemServiceCodeIBHF captures enum value "IBHF" + ReServiceItemServiceCodeIBHF string = "IBHF" + + // ReServiceItemServiceCodeIBTF captures enum value "IBTF" + ReServiceItemServiceCodeIBTF string = "IBTF" + + // ReServiceItemServiceCodeICRT captures enum value "ICRT" + ReServiceItemServiceCodeICRT string = "ICRT" + + // ReServiceItemServiceCodeICRTSA captures enum value "ICRTSA" + ReServiceItemServiceCodeICRTSA string = "ICRTSA" + + // ReServiceItemServiceCodeIDASIT captures enum value "IDASIT" + ReServiceItemServiceCodeIDASIT string = "IDASIT" + + // ReServiceItemServiceCodeIDDSIT captures enum value "IDDSIT" + ReServiceItemServiceCodeIDDSIT string = "IDDSIT" + + // ReServiceItemServiceCodeIDFSIT captures enum value "IDFSIT" + ReServiceItemServiceCodeIDFSIT string = "IDFSIT" + + // ReServiceItemServiceCodeIDSFSC captures enum value "IDSFSC" + ReServiceItemServiceCodeIDSFSC string = "IDSFSC" + + // ReServiceItemServiceCodeIDSHUT captures enum value "IDSHUT" + ReServiceItemServiceCodeIDSHUT string = "IDSHUT" + + // ReServiceItemServiceCodeIHPK captures enum value "IHPK" + ReServiceItemServiceCodeIHPK string = "IHPK" + + // ReServiceItemServiceCodeIHUPK captures enum value "IHUPK" + ReServiceItemServiceCodeIHUPK string = "IHUPK" + + // ReServiceItemServiceCodeINPK captures enum value "INPK" + ReServiceItemServiceCodeINPK string = "INPK" + + // ReServiceItemServiceCodeIOASIT captures enum value "IOASIT" + ReServiceItemServiceCodeIOASIT string = "IOASIT" + + // ReServiceItemServiceCodeIOFSIT captures enum value "IOFSIT" + ReServiceItemServiceCodeIOFSIT string = "IOFSIT" + + // ReServiceItemServiceCodeIOPSIT captures enum value "IOPSIT" + ReServiceItemServiceCodeIOPSIT string = "IOPSIT" + + // ReServiceItemServiceCodeIOSFSC captures enum value "IOSFSC" + ReServiceItemServiceCodeIOSFSC string = "IOSFSC" + + // ReServiceItemServiceCodeIOSHUT captures enum value "IOSHUT" + ReServiceItemServiceCodeIOSHUT string = "IOSHUT" + + // ReServiceItemServiceCodeISLH captures enum value "ISLH" + ReServiceItemServiceCodeISLH string = "ISLH" + + // ReServiceItemServiceCodeIUBPK captures enum value "IUBPK" + ReServiceItemServiceCodeIUBPK string = "IUBPK" + + // ReServiceItemServiceCodeIUBUPK captures enum value "IUBUPK" + ReServiceItemServiceCodeIUBUPK string = "IUBUPK" + + // ReServiceItemServiceCodeIUCRT captures enum value "IUCRT" + ReServiceItemServiceCodeIUCRT string = "IUCRT" + + // ReServiceItemServiceCodeMS captures enum value "MS" + ReServiceItemServiceCodeMS string = "MS" + + // ReServiceItemServiceCodePODFSC captures enum value "PODFSC" + ReServiceItemServiceCodePODFSC string = "PODFSC" + + // ReServiceItemServiceCodePOEFSC captures enum value "POEFSC" + ReServiceItemServiceCodePOEFSC string = "POEFSC" + + // ReServiceItemServiceCodeUBP captures enum value "UBP" + ReServiceItemServiceCodeUBP string = "UBP" +) + +// prop value enum +func (m *ReServiceItem) validateServiceCodeEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, reServiceItemTypeServiceCodePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *ReServiceItem) validateServiceCode(formats strfmt.Registry) error { + if swag.IsZero(m.ServiceCode) { // not required + return nil + } + + // value enum + if err := m.validateServiceCodeEnum("serviceCode", "body", m.ServiceCode); err != nil { + return err + } + + return nil +} + +var reServiceItemTypeShipmentTypePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["BOAT_HAUL_AWAY","BOAT_TOW_AWAY","HHG","HHG_INTO_NTS_DOMESTIC","HHG_OUTOF_NTS_DOMESTIC","MOBILE_HOME","PPM","UNACCOMPANIED_BAGGAGE"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + reServiceItemTypeShipmentTypePropEnum = append(reServiceItemTypeShipmentTypePropEnum, v) + } +} + +const ( + + // ReServiceItemShipmentTypeBOATHAULAWAY captures enum value "BOAT_HAUL_AWAY" + ReServiceItemShipmentTypeBOATHAULAWAY string = "BOAT_HAUL_AWAY" + + // ReServiceItemShipmentTypeBOATTOWAWAY captures enum value "BOAT_TOW_AWAY" + ReServiceItemShipmentTypeBOATTOWAWAY string = "BOAT_TOW_AWAY" + + // ReServiceItemShipmentTypeHHG captures enum value "HHG" + ReServiceItemShipmentTypeHHG string = "HHG" + + // ReServiceItemShipmentTypeHHGINTONTSDOMESTIC captures enum value "HHG_INTO_NTS_DOMESTIC" + ReServiceItemShipmentTypeHHGINTONTSDOMESTIC string = "HHG_INTO_NTS_DOMESTIC" + + // ReServiceItemShipmentTypeHHGOUTOFNTSDOMESTIC captures enum value "HHG_OUTOF_NTS_DOMESTIC" + ReServiceItemShipmentTypeHHGOUTOFNTSDOMESTIC string = "HHG_OUTOF_NTS_DOMESTIC" + + // ReServiceItemShipmentTypeMOBILEHOME captures enum value "MOBILE_HOME" + ReServiceItemShipmentTypeMOBILEHOME string = "MOBILE_HOME" + + // ReServiceItemShipmentTypePPM captures enum value "PPM" + ReServiceItemShipmentTypePPM string = "PPM" + + // ReServiceItemShipmentTypeUNACCOMPANIEDBAGGAGE captures enum value "UNACCOMPANIED_BAGGAGE" + ReServiceItemShipmentTypeUNACCOMPANIEDBAGGAGE string = "UNACCOMPANIED_BAGGAGE" +) + +// prop value enum +func (m *ReServiceItem) validateShipmentTypeEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, reServiceItemTypeShipmentTypePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *ReServiceItem) validateShipmentType(formats strfmt.Registry) error { + if swag.IsZero(m.ShipmentType) { // not required + return nil + } + + // value enum + if err := m.validateShipmentTypeEnum("shipmentType", "body", m.ShipmentType); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this re service item based on context it is used +func (m *ReServiceItem) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *ReServiceItem) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ReServiceItem) UnmarshalBinary(b []byte) error { + var res ReServiceItem + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/gen/ghcmessages/re_service_items.go b/pkg/gen/ghcmessages/re_service_items.go new file mode 100644 index 00000000000..b5c2f510811 --- /dev/null +++ b/pkg/gen/ghcmessages/re_service_items.go @@ -0,0 +1,78 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package ghcmessages + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// ReServiceItems re service items +// +// swagger:model ReServiceItems +type ReServiceItems []*ReServiceItem + +// Validate validates this re service items +func (m ReServiceItems) Validate(formats strfmt.Registry) error { + var res []error + + for i := 0; i < len(m); i++ { + if swag.IsZero(m[i]) { // not required + continue + } + + if m[i] != nil { + if err := m[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName(strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName(strconv.Itoa(i)) + } + return err + } + } + + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// ContextValidate validate this re service items based on the context it is used +func (m ReServiceItems) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + for i := 0; i < len(m); i++ { + + if m[i] != nil { + + if swag.IsZero(m[i]) { // not required + return nil + } + + if err := m[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName(strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName(strconv.Itoa(i)) + } + return err + } + } + + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/pkg/gen/ghcmessages/search_move.go b/pkg/gen/ghcmessages/search_move.go index c6690995151..f0f91015deb 100644 --- a/pkg/gen/ghcmessages/search_move.go +++ b/pkg/gen/ghcmessages/search_move.go @@ -22,13 +22,13 @@ type SearchMove struct { // branch Branch string `json:"branch,omitempty"` + // destination g b l o c + DestinationGBLOC GBLOC `json:"destinationGBLOC,omitempty"` + // ZIP // Example: 90210 // Pattern: ^(\d{5})$ - DestinationDutyLocationPostalCode string `json:"destinationDutyLocationPostalCode,omitempty"` - - // destination g b l o c - DestinationGBLOC GBLOC `json:"destinationGBLOC,omitempty"` + DestinationPostalCode string `json:"destinationPostalCode,omitempty"` // edipi // Example: 1234567890 @@ -94,11 +94,11 @@ type SearchMove struct { func (m *SearchMove) Validate(formats strfmt.Registry) error { var res []error - if err := m.validateDestinationDutyLocationPostalCode(formats); err != nil { + if err := m.validateDestinationGBLOC(formats); err != nil { res = append(res, err) } - if err := m.validateDestinationGBLOC(formats); err != nil { + if err := m.validateDestinationPostalCode(formats); err != nil { res = append(res, err) } @@ -140,29 +140,29 @@ func (m *SearchMove) Validate(formats strfmt.Registry) error { return nil } -func (m *SearchMove) validateDestinationDutyLocationPostalCode(formats strfmt.Registry) error { - if swag.IsZero(m.DestinationDutyLocationPostalCode) { // not required +func (m *SearchMove) validateDestinationGBLOC(formats strfmt.Registry) error { + if swag.IsZero(m.DestinationGBLOC) { // not required return nil } - if err := validate.Pattern("destinationDutyLocationPostalCode", "body", m.DestinationDutyLocationPostalCode, `^(\d{5})$`); err != nil { + if err := m.DestinationGBLOC.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("destinationGBLOC") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("destinationGBLOC") + } return err } return nil } -func (m *SearchMove) validateDestinationGBLOC(formats strfmt.Registry) error { - if swag.IsZero(m.DestinationGBLOC) { // not required +func (m *SearchMove) validateDestinationPostalCode(formats strfmt.Registry) error { + if swag.IsZero(m.DestinationPostalCode) { // not required return nil } - if err := m.DestinationGBLOC.Validate(formats); err != nil { - if ve, ok := err.(*errors.Validation); ok { - return ve.ValidateName("destinationGBLOC") - } else if ce, ok := err.(*errors.CompositeError); ok { - return ce.ValidateName("destinationGBLOC") - } + if err := validate.Pattern("destinationPostalCode", "body", m.DestinationPostalCode, `^(\d{5})$`); err != nil { return err } diff --git a/pkg/gen/ghcmessages/shipment_address_update.go b/pkg/gen/ghcmessages/shipment_address_update.go index 5465bcb9c22..d609419c05d 100644 --- a/pkg/gen/ghcmessages/shipment_address_update.go +++ b/pkg/gen/ghcmessages/shipment_address_update.go @@ -14,7 +14,7 @@ import ( "github.com/go-openapi/validate" ) -// ShipmentAddressUpdate This represents a destination address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO. +// ShipmentAddressUpdate This represents a delivery address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO. // // swagger:model ShipmentAddressUpdate type ShipmentAddressUpdate struct { @@ -38,7 +38,7 @@ type ShipmentAddressUpdate struct { // Required: true NewAddress *Address `json:"newAddress"` - // The distance between the original SIT address and requested new destination address of shipment + // The distance between the original SIT address and requested new delivery address of shipment // Example: 88 // Minimum: 0 NewSitDistanceBetween *int64 `json:"newSitDistanceBetween,omitempty"` @@ -49,7 +49,7 @@ type ShipmentAddressUpdate struct { // Example: This is an office remark OfficeRemarks *string `json:"officeRemarks,omitempty"` - // The distance between the original SIT address and the previous/old destination address of shipment + // The distance between the original SIT address and the previous/old delivery address of shipment // Example: 50 // Minimum: 0 OldSitDistanceBetween *int64 `json:"oldSitDistanceBetween,omitempty"` diff --git a/pkg/gen/ghcmessages/transportation_office_assignment.go b/pkg/gen/ghcmessages/transportation_office_assignment.go new file mode 100644 index 00000000000..c370c5902af --- /dev/null +++ b/pkg/gen/ghcmessages/transportation_office_assignment.go @@ -0,0 +1,241 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package ghcmessages + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// TransportationOfficeAssignment transportation office assignment +// +// swagger:model TransportationOfficeAssignment +type TransportationOfficeAssignment struct { + + // created at + // Read Only: true + // Format: date-time + CreatedAt strfmt.DateTime `json:"createdAt,omitempty"` + + // office user Id + // Example: c56a4780-65aa-42ec-a945-5fd87dec0538 + // Required: true + // Format: uuid + OfficeUserID *strfmt.UUID `json:"officeUserId"` + + // primary office + // Required: true + PrimaryOffice *bool `json:"primaryOffice"` + + // transportation office + TransportationOffice *TransportationOffice `json:"transportationOffice,omitempty"` + + // transportation office Id + // Example: d67a4780-65aa-42ec-a945-5fd87dec0549 + // Required: true + // Format: uuid + TransportationOfficeID *strfmt.UUID `json:"transportationOfficeId"` + + // updated at + // Read Only: true + // Format: date-time + UpdatedAt strfmt.DateTime `json:"updatedAt,omitempty"` +} + +// Validate validates this transportation office assignment +func (m *TransportationOfficeAssignment) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateCreatedAt(formats); err != nil { + res = append(res, err) + } + + if err := m.validateOfficeUserID(formats); err != nil { + res = append(res, err) + } + + if err := m.validatePrimaryOffice(formats); err != nil { + res = append(res, err) + } + + if err := m.validateTransportationOffice(formats); err != nil { + res = append(res, err) + } + + if err := m.validateTransportationOfficeID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateUpdatedAt(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *TransportationOfficeAssignment) validateCreatedAt(formats strfmt.Registry) error { + if swag.IsZero(m.CreatedAt) { // not required + return nil + } + + if err := validate.FormatOf("createdAt", "body", "date-time", m.CreatedAt.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *TransportationOfficeAssignment) validateOfficeUserID(formats strfmt.Registry) error { + + if err := validate.Required("officeUserId", "body", m.OfficeUserID); err != nil { + return err + } + + if err := validate.FormatOf("officeUserId", "body", "uuid", m.OfficeUserID.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *TransportationOfficeAssignment) validatePrimaryOffice(formats strfmt.Registry) error { + + if err := validate.Required("primaryOffice", "body", m.PrimaryOffice); err != nil { + return err + } + + return nil +} + +func (m *TransportationOfficeAssignment) validateTransportationOffice(formats strfmt.Registry) error { + if swag.IsZero(m.TransportationOffice) { // not required + return nil + } + + if m.TransportationOffice != nil { + if err := m.TransportationOffice.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("transportationOffice") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("transportationOffice") + } + return err + } + } + + return nil +} + +func (m *TransportationOfficeAssignment) validateTransportationOfficeID(formats strfmt.Registry) error { + + if err := validate.Required("transportationOfficeId", "body", m.TransportationOfficeID); err != nil { + return err + } + + if err := validate.FormatOf("transportationOfficeId", "body", "uuid", m.TransportationOfficeID.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *TransportationOfficeAssignment) validateUpdatedAt(formats strfmt.Registry) error { + if swag.IsZero(m.UpdatedAt) { // not required + return nil + } + + if err := validate.FormatOf("updatedAt", "body", "date-time", m.UpdatedAt.String(), formats); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this transportation office assignment based on the context it is used +func (m *TransportationOfficeAssignment) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateCreatedAt(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateTransportationOffice(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateUpdatedAt(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *TransportationOfficeAssignment) contextValidateCreatedAt(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "createdAt", "body", strfmt.DateTime(m.CreatedAt)); err != nil { + return err + } + + return nil +} + +func (m *TransportationOfficeAssignment) contextValidateTransportationOffice(ctx context.Context, formats strfmt.Registry) error { + + if m.TransportationOffice != nil { + + if swag.IsZero(m.TransportationOffice) { // not required + return nil + } + + if err := m.TransportationOffice.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("transportationOffice") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("transportationOffice") + } + return err + } + } + + return nil +} + +func (m *TransportationOfficeAssignment) contextValidateUpdatedAt(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "updatedAt", "body", strfmt.DateTime(m.UpdatedAt)); err != nil { + return err + } + + return nil +} + +// MarshalBinary interface implementation +func (m *TransportationOfficeAssignment) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *TransportationOfficeAssignment) UnmarshalBinary(b []byte) error { + var res TransportationOfficeAssignment + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/gen/ghcmessages/update_allowance_payload.go b/pkg/gen/ghcmessages/update_allowance_payload.go index aeb83c7caaa..ee0d677c0e7 100644 --- a/pkg/gen/ghcmessages/update_allowance_payload.go +++ b/pkg/gen/ghcmessages/update_allowance_payload.go @@ -19,12 +19,24 @@ import ( // swagger:model UpdateAllowancePayload type UpdateAllowancePayload struct { + // Indicates if the move entitlement allows dependents to travel to the new Permanent Duty Station (PDS). This is only present on OCONUS moves. + // Example: true + AccompaniedTour *bool `json:"accompaniedTour,omitempty"` + // agency Agency *Affiliation `json:"agency,omitempty"` // dependents authorized DependentsAuthorized *bool `json:"dependentsAuthorized,omitempty"` + // Indicates the number of dependents of the age twelve or older for a move. This is only present on OCONUS moves. + // Example: 3 + DependentsTwelveAndOver *int64 `json:"dependentsTwelveAndOver,omitempty"` + + // Indicates the number of dependents under the age of twelve for a move. This is only present on OCONUS moves. + // Example: 5 + DependentsUnderTwelve *int64 `json:"dependentsUnderTwelve,omitempty"` + // grade Grade *Grade `json:"grade,omitempty"` @@ -54,6 +66,10 @@ type UpdateAllowancePayload struct { // the number of storage in transit days that the customer is entitled to for a given shipment on their move // Minimum: 0 StorageInTransit *int64 `json:"storageInTransit,omitempty"` + + // ub allowance + // Example: 500 + UbAllowance *int64 `json:"ubAllowance,omitempty"` } // Validate validates this update allowance payload diff --git a/pkg/gen/ghcmessages/update_p_p_m_shipment.go b/pkg/gen/ghcmessages/update_p_p_m_shipment.go index 62bde658004..1684f99a4c9 100644 --- a/pkg/gen/ghcmessages/update_p_p_m_shipment.go +++ b/pkg/gen/ghcmessages/update_p_p_m_shipment.go @@ -50,6 +50,11 @@ type UpdatePPMShipment struct { // advance status AdvanceStatus *PPMAdvanceStatus `json:"advanceStatus,omitempty"` + // The allowable weight of the PPM shipment goods being moved. + // Example: 4300 + // Minimum: 0 + AllowableWeight *int64 `json:"allowableWeight,omitempty"` + // destination address DestinationAddress struct { PPMDestinationAddress @@ -165,6 +170,10 @@ func (m *UpdatePPMShipment) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateAllowableWeight(formats); err != nil { + res = append(res, err) + } + if err := m.validateDestinationAddress(formats); err != nil { res = append(res, err) } @@ -270,6 +279,18 @@ func (m *UpdatePPMShipment) validateAdvanceStatus(formats strfmt.Registry) error return nil } +func (m *UpdatePPMShipment) validateAllowableWeight(formats strfmt.Registry) error { + if swag.IsZero(m.AllowableWeight) { // not required + return nil + } + + if err := validate.MinimumInt("allowableWeight", "body", *m.AllowableWeight, 0, false); err != nil { + return err + } + + return nil +} + func (m *UpdatePPMShipment) validateDestinationAddress(formats strfmt.Registry) error { if swag.IsZero(m.DestinationAddress) { // not required return nil diff --git a/pkg/gen/ghcmessages/update_weight_ticket.go b/pkg/gen/ghcmessages/update_weight_ticket.go index edc62b26fca..b1e861e0f11 100644 --- a/pkg/gen/ghcmessages/update_weight_ticket.go +++ b/pkg/gen/ghcmessages/update_weight_ticket.go @@ -23,10 +23,6 @@ type UpdateWeightTicket struct { // Minimum: 0 AdjustedNetWeight *int64 `json:"adjustedNetWeight,omitempty"` - // Indicates the maximum reimbursable weight of the shipment - // Minimum: 0 - AllowableWeight *int64 `json:"allowableWeight,omitempty"` - // Weight of the vehicle when empty. // Minimum: 0 EmptyWeight *int64 `json:"emptyWeight,omitempty"` @@ -59,10 +55,6 @@ func (m *UpdateWeightTicket) Validate(formats strfmt.Registry) error { res = append(res, err) } - if err := m.validateAllowableWeight(formats); err != nil { - res = append(res, err) - } - if err := m.validateEmptyWeight(formats); err != nil { res = append(res, err) } @@ -93,18 +85,6 @@ func (m *UpdateWeightTicket) validateAdjustedNetWeight(formats strfmt.Registry) return nil } -func (m *UpdateWeightTicket) validateAllowableWeight(formats strfmt.Registry) error { - if swag.IsZero(m.AllowableWeight) { // not required - return nil - } - - if err := validate.MinimumInt("allowableWeight", "body", *m.AllowableWeight, 0, false); err != nil { - return err - } - - return nil -} - func (m *UpdateWeightTicket) validateEmptyWeight(formats strfmt.Registry) error { if swag.IsZero(m.EmptyWeight) { // not required return nil diff --git a/pkg/gen/ghcmessages/weight_ticket.go b/pkg/gen/ghcmessages/weight_ticket.go index 97569ed5e51..65435245820 100644 --- a/pkg/gen/ghcmessages/weight_ticket.go +++ b/pkg/gen/ghcmessages/weight_ticket.go @@ -23,10 +23,6 @@ type WeightTicket struct { // Minimum: 0 AdjustedNetWeight *int64 `json:"adjustedNetWeight"` - // Maximum reimbursable weight. - // Minimum: 0 - AllowableWeight *int64 `json:"allowableWeight"` - // created at // Required: true // Read Only: true @@ -143,10 +139,6 @@ func (m *WeightTicket) Validate(formats strfmt.Registry) error { res = append(res, err) } - if err := m.validateAllowableWeight(formats); err != nil { - res = append(res, err) - } - if err := m.validateCreatedAt(formats); err != nil { res = append(res, err) } @@ -229,18 +221,6 @@ func (m *WeightTicket) validateAdjustedNetWeight(formats strfmt.Registry) error return nil } -func (m *WeightTicket) validateAllowableWeight(formats strfmt.Registry) error { - if swag.IsZero(m.AllowableWeight) { // not required - return nil - } - - if err := validate.MinimumInt("allowableWeight", "body", *m.AllowableWeight, 0, false); err != nil { - return err - } - - return nil -} - func (m *WeightTicket) validateCreatedAt(formats strfmt.Registry) error { if err := validate.Required("createdAt", "body", strfmt.DateTime(m.CreatedAt)); err != nil { diff --git a/pkg/gen/internalapi/embedded_spec.go b/pkg/gen/internalapi/embedded_spec.go index 0ca76e4ffb7..cb6b70eda63 100644 --- a/pkg/gen/internalapi/embedded_spec.go +++ b/pkg/gen/internalapi/embedded_spec.go @@ -4122,6 +4122,12 @@ func init() { "new_duty_location_id" ], "properties": { + "accompanied_tour": { + "description": "Indicates if the move entitlement allows dependents to travel to the new Permanent Duty Station (PDS). This is only present on OCONUS moves.", + "type": "boolean", + "x-nullable": true, + "example": true + }, "counseling_office_id": { "type": "string", "format": "uuid", @@ -4131,6 +4137,18 @@ func init() { "department_indicator": { "$ref": "#/definitions/DeptIndicator" }, + "dependents_twelve_and_over": { + "description": "Indicates the number of dependents of the age twelve or older for a move. This is only present on OCONUS moves.", + "type": "integer", + "x-nullable": true, + "example": 3 + }, + "dependents_under_twelve": { + "description": "Indicates the number of dependents under the age of twelve for a move. This is only present on OCONUS moves.", + "type": "integer", + "x-nullable": true, + "example": 5 + }, "grade": { "$ref": "#/definitions/OrderPayGrade" }, @@ -4382,6 +4400,24 @@ func init() { "Entitlement": { "type": "object", "properties": { + "accompanied_tour": { + "description": "Indicates if the move entitlement allows dependents to travel to the new Permanent Duty Station (PDS). This is only present on OCONUS moves.", + "type": "boolean", + "x-nullable": true, + "example": true + }, + "dependents_twelve_and_over": { + "description": "Indicates the number of dependents of the age twelve or older for a move. This is only present on OCONUS moves.", + "type": "integer", + "x-nullable": true, + "example": 3 + }, + "dependents_under_twelve": { + "description": "Indicates the number of dependents under the age of twelve for a move. This is only present on OCONUS moves.", + "type": "integer", + "x-nullable": true, + "example": 5 + }, "proGear": { "description": "Pro-gear weight limit as set by an Office user, distinct from the service member's default weight allotment determined by pay grade\n", "type": "integer", @@ -4393,6 +4429,12 @@ func init() { "type": "integer", "x-nullable": true, "example": 500 + }, + "ub_allowance": { + "description": "The amount of weight in pounds that the move is entitled for shipment types of Unaccompanied Baggage.", + "type": "integer", + "x-nullable": true, + "example": 3 } } }, @@ -5314,14 +5356,7 @@ func init() { "example": "2018-04-25" }, "orders_type": { - "type": "string", - "title": "Move Type", - "enum": [ - "PCS - OCONUS", - "PCS - CONUS", - "PCS + TDY - OCONUS", - "PCS + TDY - CONUS" - ] + "$ref": "#/definitions/OrdersType" }, "origin_duty_location_name": { "type": "string", @@ -5661,6 +5696,12 @@ func init() { "transportation_office": { "$ref": "#/definitions/TransportationOffice" }, + "transportation_office_assignments": { + "type": "array", + "items": { + "$ref": "#/definitions/TransportationOfficeAssignment" + } + }, "updated_at": { "type": "string", "format": "date-time" @@ -5975,15 +6016,19 @@ func init() { "WOUNDED_WARRIOR", "BLUEBARK", "SAFETY", - "TEMPORARY_DUTY" + "TEMPORARY_DUTY", + "EARLY_RETURN_OF_DEPENDENTS", + "STUDENT_TRAVEL" ], "x-display-value": { "BLUEBARK": "BLUEBARK", + "EARLY_RETURN_OF_DEPENDENTS": "Early Return of Dependents", "LOCAL_MOVE": "Local Move", "PERMANENT_CHANGE_OF_STATION": "Permanent Change Of Station", "RETIREMENT": "Retirement", "SAFETY": "Safety", "SEPARATION": "Separation", + "STUDENT_TRAVEL": "Student Travel", "TEMPORARY_DUTY": "Temporary Duty (TDY)", "WOUNDED_WARRIOR": "Wounded Warrior" } @@ -6276,6 +6321,13 @@ func init() { "advanceStatus": { "$ref": "#/definitions/PPMAdvanceStatus" }, + "allowableWeight": { + "description": "The allowable weight of the PPM shipment goods being moved.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4300 + }, "approvedAt": { "description": "The timestamp of when the shipment was approved and the service member can begin their move.", "type": "string", @@ -7416,6 +7468,43 @@ func init() { } } }, + "TransportationOfficeAssignment": { + "type": "object", + "required": [ + "officeUserId", + "transportationOfficeId", + "primaryOffice" + ], + "properties": { + "createdAt": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "officeUserId": { + "type": "string", + "format": "uuid", + "example": "c56a4780-65aa-42ec-a945-5fd87dec0538" + }, + "primaryOffice": { + "type": "boolean", + "x-omitempty": false + }, + "transportationOffice": { + "$ref": "#/definitions/TransportationOffice" + }, + "transportationOfficeId": { + "type": "string", + "format": "uuid", + "example": "d67a4780-65aa-42ec-a945-5fd87dec0549" + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "readOnly": true + } + } + }, "TransportationOffices": { "type": "array", "items": { @@ -7859,10 +7948,6 @@ func init() { "description": "Indicates the adjusted net weight of the vehicle", "type": "integer" }, - "allowableWeight": { - "description": "Indicates the maximum reimbursable weight of the shipment", - "type": "integer" - }, "emptyWeight": { "description": "Weight of the vehicle when empty.", "type": "integer" @@ -8054,12 +8139,6 @@ func init() { "x-nullable": true, "x-omitempty": false }, - "allowableWeight": { - "description": "Maximum reimbursable weight.", - "type": "integer", - "x-nullable": true, - "x-omitempty": false - }, "createdAt": { "type": "string", "format": "date-time", @@ -12957,6 +13036,12 @@ func init() { "new_duty_location_id" ], "properties": { + "accompanied_tour": { + "description": "Indicates if the move entitlement allows dependents to travel to the new Permanent Duty Station (PDS). This is only present on OCONUS moves.", + "type": "boolean", + "x-nullable": true, + "example": true + }, "counseling_office_id": { "type": "string", "format": "uuid", @@ -12966,6 +13051,18 @@ func init() { "department_indicator": { "$ref": "#/definitions/DeptIndicator" }, + "dependents_twelve_and_over": { + "description": "Indicates the number of dependents of the age twelve or older for a move. This is only present on OCONUS moves.", + "type": "integer", + "x-nullable": true, + "example": 3 + }, + "dependents_under_twelve": { + "description": "Indicates the number of dependents under the age of twelve for a move. This is only present on OCONUS moves.", + "type": "integer", + "x-nullable": true, + "example": 5 + }, "grade": { "$ref": "#/definitions/OrderPayGrade" }, @@ -13219,6 +13316,24 @@ func init() { "Entitlement": { "type": "object", "properties": { + "accompanied_tour": { + "description": "Indicates if the move entitlement allows dependents to travel to the new Permanent Duty Station (PDS). This is only present on OCONUS moves.", + "type": "boolean", + "x-nullable": true, + "example": true + }, + "dependents_twelve_and_over": { + "description": "Indicates the number of dependents of the age twelve or older for a move. This is only present on OCONUS moves.", + "type": "integer", + "x-nullable": true, + "example": 3 + }, + "dependents_under_twelve": { + "description": "Indicates the number of dependents under the age of twelve for a move. This is only present on OCONUS moves.", + "type": "integer", + "x-nullable": true, + "example": 5 + }, "proGear": { "description": "Pro-gear weight limit as set by an Office user, distinct from the service member's default weight allotment determined by pay grade\n", "type": "integer", @@ -13230,6 +13345,12 @@ func init() { "type": "integer", "x-nullable": true, "example": 500 + }, + "ub_allowance": { + "description": "The amount of weight in pounds that the move is entitled for shipment types of Unaccompanied Baggage.", + "type": "integer", + "x-nullable": true, + "example": 3 } } }, @@ -14153,14 +14274,7 @@ func init() { "example": "2018-04-25" }, "orders_type": { - "type": "string", - "title": "Move Type", - "enum": [ - "PCS - OCONUS", - "PCS - CONUS", - "PCS + TDY - OCONUS", - "PCS + TDY - CONUS" - ] + "$ref": "#/definitions/OrdersType" }, "origin_duty_location_name": { "type": "string", @@ -14500,6 +14614,12 @@ func init() { "transportation_office": { "$ref": "#/definitions/TransportationOffice" }, + "transportation_office_assignments": { + "type": "array", + "items": { + "$ref": "#/definitions/TransportationOfficeAssignment" + } + }, "updated_at": { "type": "string", "format": "date-time" @@ -14814,15 +14934,19 @@ func init() { "WOUNDED_WARRIOR", "BLUEBARK", "SAFETY", - "TEMPORARY_DUTY" + "TEMPORARY_DUTY", + "EARLY_RETURN_OF_DEPENDENTS", + "STUDENT_TRAVEL" ], "x-display-value": { "BLUEBARK": "BLUEBARK", + "EARLY_RETURN_OF_DEPENDENTS": "Early Return of Dependents", "LOCAL_MOVE": "Local Move", "PERMANENT_CHANGE_OF_STATION": "Permanent Change Of Station", "RETIREMENT": "Retirement", "SAFETY": "Safety", "SEPARATION": "Separation", + "STUDENT_TRAVEL": "Student Travel", "TEMPORARY_DUTY": "Temporary Duty (TDY)", "WOUNDED_WARRIOR": "Wounded Warrior" } @@ -15115,6 +15239,14 @@ func init() { "advanceStatus": { "$ref": "#/definitions/PPMAdvanceStatus" }, + "allowableWeight": { + "description": "The allowable weight of the PPM shipment goods being moved.", + "type": "integer", + "minimum": 0, + "x-nullable": true, + "x-omitempty": false, + "example": 4300 + }, "approvedAt": { "description": "The timestamp of when the shipment was approved and the service member can begin their move.", "type": "string", @@ -16257,6 +16389,43 @@ func init() { } } }, + "TransportationOfficeAssignment": { + "type": "object", + "required": [ + "officeUserId", + "transportationOfficeId", + "primaryOffice" + ], + "properties": { + "createdAt": { + "type": "string", + "format": "date-time", + "readOnly": true + }, + "officeUserId": { + "type": "string", + "format": "uuid", + "example": "c56a4780-65aa-42ec-a945-5fd87dec0538" + }, + "primaryOffice": { + "type": "boolean", + "x-omitempty": false + }, + "transportationOffice": { + "$ref": "#/definitions/TransportationOffice" + }, + "transportationOfficeId": { + "type": "string", + "format": "uuid", + "example": "d67a4780-65aa-42ec-a945-5fd87dec0549" + }, + "updatedAt": { + "type": "string", + "format": "date-time", + "readOnly": true + } + } + }, "TransportationOffices": { "type": "array", "items": { @@ -16702,11 +16871,6 @@ func init() { "type": "integer", "minimum": 0 }, - "allowableWeight": { - "description": "Indicates the maximum reimbursable weight of the shipment", - "type": "integer", - "minimum": 0 - }, "emptyWeight": { "description": "Weight of the vehicle when empty.", "type": "integer", @@ -16904,13 +17068,6 @@ func init() { "x-nullable": true, "x-omitempty": false }, - "allowableWeight": { - "description": "Maximum reimbursable weight.", - "type": "integer", - "minimum": 0, - "x-nullable": true, - "x-omitempty": false - }, "createdAt": { "type": "string", "format": "date-time", diff --git a/pkg/gen/internalmessages/create_update_orders.go b/pkg/gen/internalmessages/create_update_orders.go index ad3cd16e743..7ccd66e1beb 100644 --- a/pkg/gen/internalmessages/create_update_orders.go +++ b/pkg/gen/internalmessages/create_update_orders.go @@ -19,6 +19,10 @@ import ( // swagger:model CreateUpdateOrders type CreateUpdateOrders struct { + // Indicates if the move entitlement allows dependents to travel to the new Permanent Duty Station (PDS). This is only present on OCONUS moves. + // Example: true + AccompaniedTour *bool `json:"accompanied_tour,omitempty"` + // counseling office id // Example: cf1addea-a4f9-4173-8506-2bb82a064cb7 // Format: uuid @@ -27,6 +31,14 @@ type CreateUpdateOrders struct { // department indicator DepartmentIndicator *DeptIndicator `json:"department_indicator,omitempty"` + // Indicates the number of dependents of the age twelve or older for a move. This is only present on OCONUS moves. + // Example: 3 + DependentsTwelveAndOver *int64 `json:"dependents_twelve_and_over,omitempty"` + + // Indicates the number of dependents under the age of twelve for a move. This is only present on OCONUS moves. + // Example: 5 + DependentsUnderTwelve *int64 `json:"dependents_under_twelve,omitempty"` + // grade Grade *OrderPayGrade `json:"grade,omitempty"` diff --git a/pkg/gen/internalmessages/entitlement.go b/pkg/gen/internalmessages/entitlement.go index 6692685e22d..703b403758f 100644 --- a/pkg/gen/internalmessages/entitlement.go +++ b/pkg/gen/internalmessages/entitlement.go @@ -17,6 +17,18 @@ import ( // swagger:model Entitlement type Entitlement struct { + // Indicates if the move entitlement allows dependents to travel to the new Permanent Duty Station (PDS). This is only present on OCONUS moves. + // Example: true + AccompaniedTour *bool `json:"accompanied_tour,omitempty"` + + // Indicates the number of dependents of the age twelve or older for a move. This is only present on OCONUS moves. + // Example: 3 + DependentsTwelveAndOver *int64 `json:"dependents_twelve_and_over,omitempty"` + + // Indicates the number of dependents under the age of twelve for a move. This is only present on OCONUS moves. + // Example: 5 + DependentsUnderTwelve *int64 `json:"dependents_under_twelve,omitempty"` + // Pro-gear weight limit as set by an Office user, distinct from the service member's default weight allotment determined by pay grade // // Example: 2000 @@ -26,6 +38,10 @@ type Entitlement struct { // // Example: 500 ProGearSpouse *int64 `json:"proGearSpouse,omitempty"` + + // The amount of weight in pounds that the move is entitled for shipment types of Unaccompanied Baggage. + // Example: 3 + UbAllowance *int64 `json:"ub_allowance,omitempty"` } // Validate validates this entitlement diff --git a/pkg/gen/internalmessages/move_queue_item.go b/pkg/gen/internalmessages/move_queue_item.go index b0ab98a5fe0..b274073ea6e 100644 --- a/pkg/gen/internalmessages/move_queue_item.go +++ b/pkg/gen/internalmessages/move_queue_item.go @@ -7,7 +7,6 @@ package internalmessages import ( "context" - "encoding/json" "github.com/go-openapi/errors" "github.com/go-openapi/strfmt" @@ -99,10 +98,9 @@ type MoveQueueItem struct { // Format: date MoveDate *strfmt.Date `json:"move_date,omitempty"` - // Move Type + // orders type // Required: true - // Enum: [PCS - OCONUS PCS - CONUS PCS + TDY - OCONUS PCS + TDY - CONUS] - OrdersType *string `json:"orders_type"` + OrdersType *OrdersType `json:"orders_type"` // Origin // Example: Dover AFB @@ -381,50 +379,25 @@ func (m *MoveQueueItem) validateMoveDate(formats strfmt.Registry) error { return nil } -var moveQueueItemTypeOrdersTypePropEnum []interface{} - -func init() { - var res []string - if err := json.Unmarshal([]byte(`["PCS - OCONUS","PCS - CONUS","PCS + TDY - OCONUS","PCS + TDY - CONUS"]`), &res); err != nil { - panic(err) - } - for _, v := range res { - moveQueueItemTypeOrdersTypePropEnum = append(moveQueueItemTypeOrdersTypePropEnum, v) - } -} - -const ( - - // MoveQueueItemOrdersTypePCSDashOCONUS captures enum value "PCS - OCONUS" - MoveQueueItemOrdersTypePCSDashOCONUS string = "PCS - OCONUS" - - // MoveQueueItemOrdersTypePCSDashCONUS captures enum value "PCS - CONUS" - MoveQueueItemOrdersTypePCSDashCONUS string = "PCS - CONUS" - - // MoveQueueItemOrdersTypePCSPlusTDYDashOCONUS captures enum value "PCS + TDY - OCONUS" - MoveQueueItemOrdersTypePCSPlusTDYDashOCONUS string = "PCS + TDY - OCONUS" - - // MoveQueueItemOrdersTypePCSPlusTDYDashCONUS captures enum value "PCS + TDY - CONUS" - MoveQueueItemOrdersTypePCSPlusTDYDashCONUS string = "PCS + TDY - CONUS" -) +func (m *MoveQueueItem) validateOrdersType(formats strfmt.Registry) error { -// prop value enum -func (m *MoveQueueItem) validateOrdersTypeEnum(path, location string, value string) error { - if err := validate.EnumCase(path, location, value, moveQueueItemTypeOrdersTypePropEnum, true); err != nil { + if err := validate.Required("orders_type", "body", m.OrdersType); err != nil { return err } - return nil -} - -func (m *MoveQueueItem) validateOrdersType(formats strfmt.Registry) error { if err := validate.Required("orders_type", "body", m.OrdersType); err != nil { return err } - // value enum - if err := m.validateOrdersTypeEnum("orders_type", "body", *m.OrdersType); err != nil { - return err + if m.OrdersType != nil { + if err := m.OrdersType.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("orders_type") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("orders_type") + } + return err + } } return nil @@ -502,6 +475,10 @@ func (m *MoveQueueItem) ContextValidate(ctx context.Context, formats strfmt.Regi res = append(res, err) } + if err := m.contextValidateOrdersType(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateWeightAllotment(ctx, formats); err != nil { res = append(res, err) } @@ -529,6 +506,23 @@ func (m *MoveQueueItem) contextValidateGrade(ctx context.Context, formats strfmt return nil } +func (m *MoveQueueItem) contextValidateOrdersType(ctx context.Context, formats strfmt.Registry) error { + + if m.OrdersType != nil { + + if err := m.OrdersType.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("orders_type") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("orders_type") + } + return err + } + } + + return nil +} + func (m *MoveQueueItem) contextValidateWeightAllotment(ctx context.Context, formats strfmt.Registry) error { if m.WeightAllotment != nil { diff --git a/pkg/gen/internalmessages/office_user.go b/pkg/gen/internalmessages/office_user.go index 10cb836c439..e548e7ec788 100644 --- a/pkg/gen/internalmessages/office_user.go +++ b/pkg/gen/internalmessages/office_user.go @@ -7,6 +7,7 @@ package internalmessages import ( "context" + "strconv" "github.com/go-openapi/errors" "github.com/go-openapi/strfmt" @@ -53,6 +54,9 @@ type OfficeUser struct { // transportation office TransportationOffice *TransportationOffice `json:"transportation_office,omitempty"` + // transportation office assignments + TransportationOfficeAssignments []*TransportationOfficeAssignment `json:"transportation_office_assignments"` + // updated at // Format: date-time UpdatedAt strfmt.DateTime `json:"updated_at,omitempty"` @@ -87,6 +91,10 @@ func (m *OfficeUser) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateTransportationOfficeAssignments(formats); err != nil { + res = append(res, err) + } + if err := m.validateUpdatedAt(formats); err != nil { res = append(res, err) } @@ -168,6 +176,32 @@ func (m *OfficeUser) validateTransportationOffice(formats strfmt.Registry) error return nil } +func (m *OfficeUser) validateTransportationOfficeAssignments(formats strfmt.Registry) error { + if swag.IsZero(m.TransportationOfficeAssignments) { // not required + return nil + } + + for i := 0; i < len(m.TransportationOfficeAssignments); i++ { + if swag.IsZero(m.TransportationOfficeAssignments[i]) { // not required + continue + } + + if m.TransportationOfficeAssignments[i] != nil { + if err := m.TransportationOfficeAssignments[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("transportation_office_assignments" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("transportation_office_assignments" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + func (m *OfficeUser) validateUpdatedAt(formats strfmt.Registry) error { if swag.IsZero(m.UpdatedAt) { // not required return nil @@ -200,6 +234,10 @@ func (m *OfficeUser) ContextValidate(ctx context.Context, formats strfmt.Registr res = append(res, err) } + if err := m.contextValidateTransportationOfficeAssignments(ctx, formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { return errors.CompositeValidationError(res...) } @@ -227,6 +265,31 @@ func (m *OfficeUser) contextValidateTransportationOffice(ctx context.Context, fo return nil } +func (m *OfficeUser) contextValidateTransportationOfficeAssignments(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.TransportationOfficeAssignments); i++ { + + if m.TransportationOfficeAssignments[i] != nil { + + if swag.IsZero(m.TransportationOfficeAssignments[i]) { // not required + return nil + } + + if err := m.TransportationOfficeAssignments[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("transportation_office_assignments" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("transportation_office_assignments" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + // MarshalBinary interface implementation func (m *OfficeUser) MarshalBinary() ([]byte, error) { if m == nil { diff --git a/pkg/gen/internalmessages/orders_type.go b/pkg/gen/internalmessages/orders_type.go index 043ebf02297..059e20ff623 100644 --- a/pkg/gen/internalmessages/orders_type.go +++ b/pkg/gen/internalmessages/orders_type.go @@ -53,6 +53,12 @@ const ( // OrdersTypeTEMPORARYDUTY captures enum value "TEMPORARY_DUTY" OrdersTypeTEMPORARYDUTY OrdersType = "TEMPORARY_DUTY" + + // OrdersTypeEARLYRETURNOFDEPENDENTS captures enum value "EARLY_RETURN_OF_DEPENDENTS" + OrdersTypeEARLYRETURNOFDEPENDENTS OrdersType = "EARLY_RETURN_OF_DEPENDENTS" + + // OrdersTypeSTUDENTTRAVEL captures enum value "STUDENT_TRAVEL" + OrdersTypeSTUDENTTRAVEL OrdersType = "STUDENT_TRAVEL" ) // for schema @@ -60,7 +66,7 @@ var ordersTypeEnum []interface{} func init() { var res []OrdersType - if err := json.Unmarshal([]byte(`["PERMANENT_CHANGE_OF_STATION","LOCAL_MOVE","RETIREMENT","SEPARATION","WOUNDED_WARRIOR","BLUEBARK","SAFETY","TEMPORARY_DUTY"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["PERMANENT_CHANGE_OF_STATION","LOCAL_MOVE","RETIREMENT","SEPARATION","WOUNDED_WARRIOR","BLUEBARK","SAFETY","TEMPORARY_DUTY","EARLY_RETURN_OF_DEPENDENTS","STUDENT_TRAVEL"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/internalmessages/p_p_m_shipment.go b/pkg/gen/internalmessages/p_p_m_shipment.go index 15ee628d1df..43181ef2904 100644 --- a/pkg/gen/internalmessages/p_p_m_shipment.go +++ b/pkg/gen/internalmessages/p_p_m_shipment.go @@ -51,6 +51,11 @@ type PPMShipment struct { // advance status AdvanceStatus *PPMAdvanceStatus `json:"advanceStatus,omitempty"` + // The allowable weight of the PPM shipment goods being moved. + // Example: 4300 + // Minimum: 0 + AllowableWeight *int64 `json:"allowableWeight"` + // The timestamp of when the shipment was approved and the service member can begin their move. // Format: date-time ApprovedAt *strfmt.DateTime `json:"approvedAt"` @@ -231,6 +236,10 @@ func (m *PPMShipment) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateAllowableWeight(formats); err != nil { + res = append(res, err) + } + if err := m.validateApprovedAt(formats); err != nil { res = append(res, err) } @@ -392,6 +401,18 @@ func (m *PPMShipment) validateAdvanceStatus(formats strfmt.Registry) error { return nil } +func (m *PPMShipment) validateAllowableWeight(formats strfmt.Registry) error { + if swag.IsZero(m.AllowableWeight) { // not required + return nil + } + + if err := validate.MinimumInt("allowableWeight", "body", *m.AllowableWeight, 0, false); err != nil { + return err + } + + return nil +} + func (m *PPMShipment) validateApprovedAt(formats strfmt.Registry) error { if swag.IsZero(m.ApprovedAt) { // not required return nil diff --git a/pkg/gen/internalmessages/transportation_office_assignment.go b/pkg/gen/internalmessages/transportation_office_assignment.go new file mode 100644 index 00000000000..133dca464db --- /dev/null +++ b/pkg/gen/internalmessages/transportation_office_assignment.go @@ -0,0 +1,241 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package internalmessages + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// TransportationOfficeAssignment transportation office assignment +// +// swagger:model TransportationOfficeAssignment +type TransportationOfficeAssignment struct { + + // created at + // Read Only: true + // Format: date-time + CreatedAt strfmt.DateTime `json:"createdAt,omitempty"` + + // office user Id + // Example: c56a4780-65aa-42ec-a945-5fd87dec0538 + // Required: true + // Format: uuid + OfficeUserID *strfmt.UUID `json:"officeUserId"` + + // primary office + // Required: true + PrimaryOffice *bool `json:"primaryOffice"` + + // transportation office + TransportationOffice *TransportationOffice `json:"transportationOffice,omitempty"` + + // transportation office Id + // Example: d67a4780-65aa-42ec-a945-5fd87dec0549 + // Required: true + // Format: uuid + TransportationOfficeID *strfmt.UUID `json:"transportationOfficeId"` + + // updated at + // Read Only: true + // Format: date-time + UpdatedAt strfmt.DateTime `json:"updatedAt,omitempty"` +} + +// Validate validates this transportation office assignment +func (m *TransportationOfficeAssignment) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateCreatedAt(formats); err != nil { + res = append(res, err) + } + + if err := m.validateOfficeUserID(formats); err != nil { + res = append(res, err) + } + + if err := m.validatePrimaryOffice(formats); err != nil { + res = append(res, err) + } + + if err := m.validateTransportationOffice(formats); err != nil { + res = append(res, err) + } + + if err := m.validateTransportationOfficeID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateUpdatedAt(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *TransportationOfficeAssignment) validateCreatedAt(formats strfmt.Registry) error { + if swag.IsZero(m.CreatedAt) { // not required + return nil + } + + if err := validate.FormatOf("createdAt", "body", "date-time", m.CreatedAt.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *TransportationOfficeAssignment) validateOfficeUserID(formats strfmt.Registry) error { + + if err := validate.Required("officeUserId", "body", m.OfficeUserID); err != nil { + return err + } + + if err := validate.FormatOf("officeUserId", "body", "uuid", m.OfficeUserID.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *TransportationOfficeAssignment) validatePrimaryOffice(formats strfmt.Registry) error { + + if err := validate.Required("primaryOffice", "body", m.PrimaryOffice); err != nil { + return err + } + + return nil +} + +func (m *TransportationOfficeAssignment) validateTransportationOffice(formats strfmt.Registry) error { + if swag.IsZero(m.TransportationOffice) { // not required + return nil + } + + if m.TransportationOffice != nil { + if err := m.TransportationOffice.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("transportationOffice") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("transportationOffice") + } + return err + } + } + + return nil +} + +func (m *TransportationOfficeAssignment) validateTransportationOfficeID(formats strfmt.Registry) error { + + if err := validate.Required("transportationOfficeId", "body", m.TransportationOfficeID); err != nil { + return err + } + + if err := validate.FormatOf("transportationOfficeId", "body", "uuid", m.TransportationOfficeID.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *TransportationOfficeAssignment) validateUpdatedAt(formats strfmt.Registry) error { + if swag.IsZero(m.UpdatedAt) { // not required + return nil + } + + if err := validate.FormatOf("updatedAt", "body", "date-time", m.UpdatedAt.String(), formats); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this transportation office assignment based on the context it is used +func (m *TransportationOfficeAssignment) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateCreatedAt(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateTransportationOffice(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateUpdatedAt(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *TransportationOfficeAssignment) contextValidateCreatedAt(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "createdAt", "body", strfmt.DateTime(m.CreatedAt)); err != nil { + return err + } + + return nil +} + +func (m *TransportationOfficeAssignment) contextValidateTransportationOffice(ctx context.Context, formats strfmt.Registry) error { + + if m.TransportationOffice != nil { + + if swag.IsZero(m.TransportationOffice) { // not required + return nil + } + + if err := m.TransportationOffice.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("transportationOffice") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("transportationOffice") + } + return err + } + } + + return nil +} + +func (m *TransportationOfficeAssignment) contextValidateUpdatedAt(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "updatedAt", "body", strfmt.DateTime(m.UpdatedAt)); err != nil { + return err + } + + return nil +} + +// MarshalBinary interface implementation +func (m *TransportationOfficeAssignment) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *TransportationOfficeAssignment) UnmarshalBinary(b []byte) error { + var res TransportationOfficeAssignment + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/gen/internalmessages/update_weight_ticket.go b/pkg/gen/internalmessages/update_weight_ticket.go index 4a4df88b70c..8b513988e59 100644 --- a/pkg/gen/internalmessages/update_weight_ticket.go +++ b/pkg/gen/internalmessages/update_weight_ticket.go @@ -23,10 +23,6 @@ type UpdateWeightTicket struct { // Minimum: 0 AdjustedNetWeight *int64 `json:"adjustedNetWeight,omitempty"` - // Indicates the maximum reimbursable weight of the shipment - // Minimum: 0 - AllowableWeight *int64 `json:"allowableWeight,omitempty"` - // Weight of the vehicle when empty. // Minimum: 0 EmptyWeight *int64 `json:"emptyWeight,omitempty"` @@ -62,10 +58,6 @@ func (m *UpdateWeightTicket) Validate(formats strfmt.Registry) error { res = append(res, err) } - if err := m.validateAllowableWeight(formats); err != nil { - res = append(res, err) - } - if err := m.validateEmptyWeight(formats); err != nil { res = append(res, err) } @@ -92,18 +84,6 @@ func (m *UpdateWeightTicket) validateAdjustedNetWeight(formats strfmt.Registry) return nil } -func (m *UpdateWeightTicket) validateAllowableWeight(formats strfmt.Registry) error { - if swag.IsZero(m.AllowableWeight) { // not required - return nil - } - - if err := validate.MinimumInt("allowableWeight", "body", *m.AllowableWeight, 0, false); err != nil { - return err - } - - return nil -} - func (m *UpdateWeightTicket) validateEmptyWeight(formats strfmt.Registry) error { if swag.IsZero(m.EmptyWeight) { // not required return nil diff --git a/pkg/gen/internalmessages/weight_ticket.go b/pkg/gen/internalmessages/weight_ticket.go index 9ce828fc722..0a3c5f9afdc 100644 --- a/pkg/gen/internalmessages/weight_ticket.go +++ b/pkg/gen/internalmessages/weight_ticket.go @@ -23,10 +23,6 @@ type WeightTicket struct { // Minimum: 0 AdjustedNetWeight *int64 `json:"adjustedNetWeight"` - // Maximum reimbursable weight. - // Minimum: 0 - AllowableWeight *int64 `json:"allowableWeight"` - // created at // Required: true // Read Only: true @@ -143,10 +139,6 @@ func (m *WeightTicket) Validate(formats strfmt.Registry) error { res = append(res, err) } - if err := m.validateAllowableWeight(formats); err != nil { - res = append(res, err) - } - if err := m.validateCreatedAt(formats); err != nil { res = append(res, err) } @@ -229,18 +221,6 @@ func (m *WeightTicket) validateAdjustedNetWeight(formats strfmt.Registry) error return nil } -func (m *WeightTicket) validateAllowableWeight(formats strfmt.Registry) error { - if swag.IsZero(m.AllowableWeight) { // not required - return nil - } - - if err := validate.MinimumInt("allowableWeight", "body", *m.AllowableWeight, 0, false); err != nil { - return err - } - - return nil -} - func (m *WeightTicket) validateCreatedAt(formats strfmt.Registry) error { if err := validate.Required("createdAt", "body", strfmt.DateTime(m.CreatedAt)); err != nil { diff --git a/pkg/gen/pptasapi/embedded_spec.go b/pkg/gen/pptasapi/embedded_spec.go index 0c8c13f5247..ae36cf8c2a4 100644 --- a/pkg/gen/pptasapi/embedded_spec.go +++ b/pkg/gen/pptasapi/embedded_spec.go @@ -426,7 +426,7 @@ func init() { "example": "G" }, "orderNumber": { - "description": "not to be confused with Orders Number", + "description": "LoaDocID in lines of accounting table. Not to be confused with Orders Number.", "type": "string", "x-nullable": true, "example": "030-00362" @@ -586,7 +586,7 @@ func init() { "x-nullable": true }, "bcn": { - "description": "LoaSbaltmtRcpntID in lines_of_accounting", + "description": "LoaAlltSnID in lines_of_accounting", "type": "string", "x-nullable": true }, @@ -681,7 +681,7 @@ func init() { "x-nullable": true }, "objClass": { - "description": "LoaAlltSnID in lines_of_accounting", + "description": "LoaObjClsID in lines_of_accounting", "type": "string", "x-nullable": true }, @@ -694,7 +694,7 @@ func init() { "x-nullable": true }, "paa": { - "description": "LoaDocID in lines_of_accounting", + "description": "LoaInstlAcntgActID in lines_of_accounting", "type": "string", "x-nullable": true }, @@ -839,12 +839,12 @@ func init() { "example": "Destination" }, "subAllotCD": { - "description": "LoaInstlAcntgActID in lines_of_accounting", + "description": "LoaSbaltmtRcpntID in lines_of_accounting", "type": "string", "x-nullable": true }, "subhead": { - "description": "LoaObjClsID in lines_of_accounting", + "description": "LoaTrsySfxTx in lines_of_accounting", "type": "string", "x-nullable": true }, @@ -1315,7 +1315,7 @@ func init() { "example": "G" }, "orderNumber": { - "description": "not to be confused with Orders Number", + "description": "LoaDocID in lines of accounting table. Not to be confused with Orders Number.", "type": "string", "x-nullable": true, "example": "030-00362" @@ -1475,7 +1475,7 @@ func init() { "x-nullable": true }, "bcn": { - "description": "LoaSbaltmtRcpntID in lines_of_accounting", + "description": "LoaAlltSnID in lines_of_accounting", "type": "string", "x-nullable": true }, @@ -1570,7 +1570,7 @@ func init() { "x-nullable": true }, "objClass": { - "description": "LoaAlltSnID in lines_of_accounting", + "description": "LoaObjClsID in lines_of_accounting", "type": "string", "x-nullable": true }, @@ -1583,7 +1583,7 @@ func init() { "x-nullable": true }, "paa": { - "description": "LoaDocID in lines_of_accounting", + "description": "LoaInstlAcntgActID in lines_of_accounting", "type": "string", "x-nullable": true }, @@ -1728,12 +1728,12 @@ func init() { "example": "Destination" }, "subAllotCD": { - "description": "LoaInstlAcntgActID in lines_of_accounting", + "description": "LoaSbaltmtRcpntID in lines_of_accounting", "type": "string", "x-nullable": true }, "subhead": { - "description": "LoaObjClsID in lines_of_accounting", + "description": "LoaTrsySfxTx in lines_of_accounting", "type": "string", "x-nullable": true }, diff --git a/pkg/gen/pptasmessages/p_p_t_a_s_report.go b/pkg/gen/pptasmessages/p_p_t_a_s_report.go index d36335b5882..04d2c1ce029 100644 --- a/pkg/gen/pptasmessages/p_p_t_a_s_report.go +++ b/pkg/gen/pptasmessages/p_p_t_a_s_report.go @@ -69,7 +69,7 @@ type PPTASReport struct { // Example: G MiddleInitial *string `json:"middleInitial,omitempty"` - // not to be confused with Orders Number + // LoaDocID in lines of accounting table. Not to be confused with Orders Number. // Example: 030-00362 OrderNumber *string `json:"orderNumber,omitempty"` diff --git a/pkg/gen/pptasmessages/p_p_t_a_s_shipment.go b/pkg/gen/pptasmessages/p_p_t_a_s_shipment.go index c59cfc5eb70..51425c727df 100644 --- a/pkg/gen/pptasmessages/p_p_t_a_s_shipment.go +++ b/pkg/gen/pptasmessages/p_p_t_a_s_shipment.go @@ -29,7 +29,7 @@ type PPTASShipment struct { // Appropriation Appro *string `json:"appro,omitempty"` - // LoaSbaltmtRcpntID in lines_of_accounting + // LoaAlltSnID in lines_of_accounting Bcn *string `json:"bcn,omitempty"` // LoaPgmElmntID in lines_of_accounting @@ -92,7 +92,7 @@ type PPTASShipment struct { // net weight NetWeight *int64 `json:"netWeight,omitempty"` - // LoaAlltSnID in lines_of_accounting + // LoaObjClsID in lines_of_accounting ObjClass *string `json:"objClass,omitempty"` // origin address @@ -101,7 +101,7 @@ type PPTASShipment struct { // origin price OriginPrice *float64 `json:"originPrice,omitempty"` - // LoaDocID in lines_of_accounting + // LoaInstlAcntgActID in lines_of_accounting Paa *string `json:"paa,omitempty"` // packing price @@ -188,10 +188,10 @@ type PPTASShipment struct { // Example: Destination SitType *string `json:"sitType,omitempty"` - // LoaInstlAcntgActID in lines_of_accounting + // LoaSbaltmtRcpntID in lines_of_accounting SubAllotCD *string `json:"subAllotCD,omitempty"` - // LoaObjClsID in lines_of_accounting + // LoaTrsySfxTx in lines_of_accounting Subhead *string `json:"subhead,omitempty"` // travel advance diff --git a/pkg/gen/primeapi/embedded_spec.go b/pkg/gen/primeapi/embedded_spec.go index e1f1d0c1eb2..2a84c9cd753 100644 --- a/pkg/gen/primeapi/embedded_spec.go +++ b/pkg/gen/primeapi/embedded_spec.go @@ -355,7 +355,7 @@ func init() { }, "/mto-service-items/{mtoServiceItemID}": { "patch": { - "description": "Updates MTOServiceItems after creation. Not all service items or fields may be updated, please see details below.\n\nThis endpoint supports different body definitions. In the modelType field below, select the modelType corresponding\n to the service item you wish to update and the documentation will update with the new definition.\n\n* Addresses: To update a destination service item's SIT destination final address, update the shipment destination address.\nFor approved shipments, please use [updateShipmentDestinationAddress](#mtoShipment/updateShipmentDestinationAddress).\nFor shipments not yet approved, please use [updateMTOShipmentAddress](#mtoShipment/updateMTOShipmentAddress).\n\n* SIT Service Items: Take note that when updating ` + "`" + `sitCustomerContacted` + "`" + `, ` + "`" + `sitDepartureDate` + "`" + `, or ` + "`" + `sitRequestedDelivery` + "`" + `, we want\nthose to be updated on ` + "`" + `DOASIT` + "`" + ` (for origin SIT) and ` + "`" + `DDASIT` + "`" + ` (for destination SIT). If updating those values in other service\nitems, the office users will not have as much attention to those values.\n\nTo create a service item, please use [createMTOServiceItem](#mtoServiceItem/createMTOServiceItem)) endpoint.\n\n* Resubmitting rejected SIT service items: This endpoint will handle the logic of changing the status of rejected SIT service items from\nREJECTED to SUBMITTED. Please provide the ` + "`" + `requestedApprovalsRequestedStatus: true` + "`" + ` when resubmitting as this will give attention to the TOO to\nreview the resubmitted SIT service item. Another note, ` + "`" + `updateReason` + "`" + ` must have a different value than the current ` + "`" + `reason` + "`" + ` value on the service item.\nIf this value is not updated, then an error will be sent back.\n\nThe following SIT service items can be resubmitted following a rejection:\n- DDASIT\n- DDDSIT\n- DDFSIT\n- DOASIT\n- DOPSIT\n- DOFSIT\n- DDSFSC\n- DOSFSC\n\nAt a MINIMUM, the payload for resubmitting a rejected SIT service item must look like this:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"reServiceCode\": \"DDFSIT\",\n \"updateReason\": \"A reason that differs from the previous reason\",\n \"modelType\": \"UpdateMTOServiceItemSIT\",\n \"requestApprovalsRequestedStatus\": true\n}\n` + "`" + `` + "`" + `` + "`" + `\n", + "description": "Updates MTOServiceItems after creation. Not all service items or fields may be updated, please see details below.\n\nThis endpoint supports different body definitions. In the modelType field below, select the modelType corresponding\n to the service item you wish to update and the documentation will update with the new definition.\n\n* Addresses: To update a destination service item's SIT destination final address, update the shipment delivery address.\nFor approved shipments, please use [updateShipmentDestinationAddress](#mtoShipment/updateShipmentDestinationAddress).\nFor shipments not yet approved, please use [updateMTOShipmentAddress](#mtoShipment/updateMTOShipmentAddress).\n\n* SIT Service Items: Take note that when updating ` + "`" + `sitCustomerContacted` + "`" + `, ` + "`" + `sitDepartureDate` + "`" + `, or ` + "`" + `sitRequestedDelivery` + "`" + `, we want\nthose to be updated on ` + "`" + `DOASIT` + "`" + ` (for origin SIT) and ` + "`" + `DDASIT` + "`" + ` (for destination SIT). If updating those values in other service\nitems, the office users will not have as much attention to those values.\n\nTo create a service item, please use [createMTOServiceItem](#mtoServiceItem/createMTOServiceItem)) endpoint.\n\n* Resubmitting rejected SIT service items: This endpoint will handle the logic of changing the status of rejected SIT service items from\nREJECTED to SUBMITTED. Please provide the ` + "`" + `requestedApprovalsRequestedStatus: true` + "`" + ` when resubmitting as this will give attention to the TOO to\nreview the resubmitted SIT service item. Another note, ` + "`" + `updateReason` + "`" + ` must have a different value than the current ` + "`" + `reason` + "`" + ` value on the service item.\nIf this value is not updated, then an error will be sent back.\n\nThe following SIT service items can be resubmitted following a rejection:\n- DDASIT\n- DDDSIT\n- DDFSIT\n- DOASIT\n- DOPSIT\n- DOFSIT\n- DDSFSC\n- DOSFSC\n\nAt a MINIMUM, the payload for resubmitting a rejected SIT service item must look like this:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"reServiceCode\": \"DDFSIT\",\n \"updateReason\": \"A reason that differs from the previous reason\",\n \"modelType\": \"UpdateMTOServiceItemSIT\",\n \"requestApprovalsRequestedStatus\": true\n}\n` + "`" + `` + "`" + `` + "`" + `\n", "consumes": [ "application/json" ], @@ -567,7 +567,7 @@ func init() { }, "/mto-shipments/{mtoShipmentID}/addresses/{addressID}": { "put": { - "description": "### Functionality\nThis endpoint is used to **update** the pickup, secondary, and destination addresses on an MTO Shipment. mto-shipments/{mtoShipmentID}/shipment-address-updates is for updating a delivery address. The address details completely replace the original, except for the UUID.\nTherefore a complete address should be sent in the request.\nWhen a destination address on a shipment is updated, the destination SIT service items address ID will also be updated so that shipment and service item final destinations match.\n\nThis endpoint **cannot create** an address.\nTo create an address on an MTO shipment, the caller must use [updateMTOShipment](#operation/updateMTOShipment) as the parent shipment has to be updated with the appropriate link to the address.\n\n### Errors\nThe address must be associated with the mtoShipment passed in the url.\nIn other words, it should be listed as pickupAddress, destinationAddress, secondaryPickupAddress or secondaryDeliveryAddress on the mtoShipment provided.\nIf it is not, caller will receive a **Conflict** Error.\n\nThe mtoShipment should be associated with an MTO that is available to prime.\nIf the caller requests an update to an address, and the shipment is not on an available MTO, the caller will receive a **NotFound** Error.\n", + "description": "### Functionality\nThis endpoint is used to **update** the pickup, secondary, and delivery addresses on an MTO Shipment. mto-shipments/{mtoShipmentID}/shipment-address-updates is for updating a delivery address. The address details completely replace the original, except for the UUID.\nTherefore a complete address should be sent in the request.\nWhen a delivery address on a shipment is updated, the destination SIT service items address ID will also be updated so that shipment and service item final destinations match.\n\nThis endpoint **cannot create** an address.\nTo create an address on an MTO shipment, the caller must use [updateMTOShipment](#operation/updateMTOShipment) as the parent shipment has to be updated with the appropriate link to the address.\n\n### Errors\nThe address must be associated with the mtoShipment passed in the url.\nIn other words, it should be listed as pickupAddress, destinationAddress, secondaryPickupAddress or secondaryDeliveryAddress on the mtoShipment provided.\nIf it is not, caller will receive a **Conflict** Error.\n\nThe mtoShipment should be associated with an MTO that is available to prime.\nIf the caller requests an update to an address, and the shipment is not on an available MTO, the caller will receive a **NotFound** Error.\n", "consumes": [ "application/json" ], @@ -858,7 +858,7 @@ func init() { }, "/mto-shipments/{mtoShipmentID}/shipment-address-updates": { "post": { - "description": "### Functionality\nThis endpoint is used so the Prime can request an **update** for the destination address on an MTO Shipment,\nafter the destination address has already been approved.\n\nThis endpoint and operation only supports the following shipment types:\n- HHG\n- NTSR\n\nFor HHG shipments, if automatically approved or TOO approves, this will update the final destination address values for destination SIT service items to be the same as the changed destination address that was approved.\n\nAddress updates will be automatically approved unless they change:\n - The service area\n - Mileage bracket for direct delivery\n - the address and the distance between the old and new address is \u003e 50\n - Domestic Short Haul to Domestic Line Haul or vice versa\n - Shipments that start and end in one ZIP3 use Short Haul pricing\n - Shipments that start and end in different ZIP3s use Line Haul pricing\n\nFor those, changes will require TOO approval.\n", + "description": "### Functionality\nThis endpoint is used so the Prime can request an **update** for the delivery address on an MTO Shipment,\nafter the delivery address has already been approved.\n\nThis endpoint and operation only supports the following shipment types:\n- HHG\n- NTSR\n\nFor HHG shipments, if automatically approved or TOO approves, this will update the final delivery address values for destination SIT service items to be the same as the changed delivery address that was approved.\n\nAddress updates will be automatically approved unless they change:\n - The service area\n - Mileage bracket for direct delivery\n - the address and the distance between the old and new address is \u003e 50\n - Domestic Short Haul to Domestic Line Haul or vice versa\n - Shipments that start and end in one ZIP3 use Short Haul pricing\n - Shipments that start and end in different ZIP3s use Line Haul pricing\n\nFor those, changes will require TOO approval.\n", "consumes": [ "application/json" ], @@ -1790,6 +1790,12 @@ func init() { "type": "integer", "x-formatting": "weight", "example": 500 + }, + "unaccompaniedBaggageAllowance": { + "description": "The amount of weight in pounds that the move is entitled for shipment types of Unaccompanied Baggage.", + "type": "integer", + "x-nullable": true, + "example": 3 } } }, @@ -1874,6 +1880,16 @@ func init() { "format": "date-time", "readOnly": true }, + "destinationGBLOC": { + "type": "string", + "readOnly": true, + "example": "JFK" + }, + "destinationPostalCode": { + "type": "string", + "readOnly": true, + "example": "90210" + }, "eTag": { "type": "string", "readOnly": true @@ -2810,6 +2826,16 @@ func init() { "format": "date-time", "readOnly": true }, + "destinationGBLOC": { + "type": "string", + "readOnly": true, + "example": "KKFA" + }, + "destinationPostalCode": { + "type": "string", + "readOnly": true, + "example": "90210" + }, "eTag": { "type": "string", "readOnly": true @@ -2964,15 +2990,19 @@ func init() { "WOUNDED_WARRIOR", "BLUEBARK", "SAFETY", - "TEMPORARY_DUTY" + "TEMPORARY_DUTY", + "EARLY_RETURN_OF_DEPENDENTS", + "STUDENT_TRAVEL" ], "x-display-value": { "BLUEBARK": "BLUEBARK", + "EARLY_RETURN_OF_DEPENDENTS": "Early Return of Dependents", "LOCAL_MOVE": "Local Move", "PERMANENT_CHANGE_OF_STATION": "Permanent Change Of Station", "RETIREMENT": "Retirement", "SAFETY": "Safety", "SEPARATION": "Separation", + "STUDENT_TRAVEL": "Student Travel", "TEMPORARY_DUTY": "Temporary Duty (TDY)", "WOUNDED_WARRIOR": "Wounded Warrior" } @@ -3754,7 +3784,7 @@ func init() { } }, "ShipmentAddressUpdate": { - "description": "This represents a destination address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO.\n", + "description": "This represents a delivery address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO.\n", "type": "object", "required": [ "id", @@ -3782,7 +3812,7 @@ func init() { "$ref": "#/definitions/Address" }, "newSitDistanceBetween": { - "description": "The distance between the original SIT address and requested new destination address of shipment", + "description": "The distance between the original SIT address and requested new delivery address of shipment", "type": "integer", "example": 88 }, @@ -3794,7 +3824,7 @@ func init() { "example": "This is an office remark" }, "oldSitDistanceBetween": { - "description": "The distance between the original SIT address and the previous/old destination address of shipment", + "description": "The distance between the original SIT address and the previous/old delivery address of shipment", "type": "integer", "example": 50 }, @@ -4283,7 +4313,7 @@ func init() { } }, "UpdateShipmentDestinationAddress": { - "description": "UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the destination address on an MTO Shipment.", + "description": "UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the delivery address on an MTO Shipment.", "type": "object", "required": [ "contractorRemarks", @@ -4904,7 +4934,7 @@ func init() { }, "/mto-service-items/{mtoServiceItemID}": { "patch": { - "description": "Updates MTOServiceItems after creation. Not all service items or fields may be updated, please see details below.\n\nThis endpoint supports different body definitions. In the modelType field below, select the modelType corresponding\n to the service item you wish to update and the documentation will update with the new definition.\n\n* Addresses: To update a destination service item's SIT destination final address, update the shipment destination address.\nFor approved shipments, please use [updateShipmentDestinationAddress](#mtoShipment/updateShipmentDestinationAddress).\nFor shipments not yet approved, please use [updateMTOShipmentAddress](#mtoShipment/updateMTOShipmentAddress).\n\n* SIT Service Items: Take note that when updating ` + "`" + `sitCustomerContacted` + "`" + `, ` + "`" + `sitDepartureDate` + "`" + `, or ` + "`" + `sitRequestedDelivery` + "`" + `, we want\nthose to be updated on ` + "`" + `DOASIT` + "`" + ` (for origin SIT) and ` + "`" + `DDASIT` + "`" + ` (for destination SIT). If updating those values in other service\nitems, the office users will not have as much attention to those values.\n\nTo create a service item, please use [createMTOServiceItem](#mtoServiceItem/createMTOServiceItem)) endpoint.\n\n* Resubmitting rejected SIT service items: This endpoint will handle the logic of changing the status of rejected SIT service items from\nREJECTED to SUBMITTED. Please provide the ` + "`" + `requestedApprovalsRequestedStatus: true` + "`" + ` when resubmitting as this will give attention to the TOO to\nreview the resubmitted SIT service item. Another note, ` + "`" + `updateReason` + "`" + ` must have a different value than the current ` + "`" + `reason` + "`" + ` value on the service item.\nIf this value is not updated, then an error will be sent back.\n\nThe following SIT service items can be resubmitted following a rejection:\n- DDASIT\n- DDDSIT\n- DDFSIT\n- DOASIT\n- DOPSIT\n- DOFSIT\n- DDSFSC\n- DOSFSC\n\nAt a MINIMUM, the payload for resubmitting a rejected SIT service item must look like this:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"reServiceCode\": \"DDFSIT\",\n \"updateReason\": \"A reason that differs from the previous reason\",\n \"modelType\": \"UpdateMTOServiceItemSIT\",\n \"requestApprovalsRequestedStatus\": true\n}\n` + "`" + `` + "`" + `` + "`" + `\n", + "description": "Updates MTOServiceItems after creation. Not all service items or fields may be updated, please see details below.\n\nThis endpoint supports different body definitions. In the modelType field below, select the modelType corresponding\n to the service item you wish to update and the documentation will update with the new definition.\n\n* Addresses: To update a destination service item's SIT destination final address, update the shipment delivery address.\nFor approved shipments, please use [updateShipmentDestinationAddress](#mtoShipment/updateShipmentDestinationAddress).\nFor shipments not yet approved, please use [updateMTOShipmentAddress](#mtoShipment/updateMTOShipmentAddress).\n\n* SIT Service Items: Take note that when updating ` + "`" + `sitCustomerContacted` + "`" + `, ` + "`" + `sitDepartureDate` + "`" + `, or ` + "`" + `sitRequestedDelivery` + "`" + `, we want\nthose to be updated on ` + "`" + `DOASIT` + "`" + ` (for origin SIT) and ` + "`" + `DDASIT` + "`" + ` (for destination SIT). If updating those values in other service\nitems, the office users will not have as much attention to those values.\n\nTo create a service item, please use [createMTOServiceItem](#mtoServiceItem/createMTOServiceItem)) endpoint.\n\n* Resubmitting rejected SIT service items: This endpoint will handle the logic of changing the status of rejected SIT service items from\nREJECTED to SUBMITTED. Please provide the ` + "`" + `requestedApprovalsRequestedStatus: true` + "`" + ` when resubmitting as this will give attention to the TOO to\nreview the resubmitted SIT service item. Another note, ` + "`" + `updateReason` + "`" + ` must have a different value than the current ` + "`" + `reason` + "`" + ` value on the service item.\nIf this value is not updated, then an error will be sent back.\n\nThe following SIT service items can be resubmitted following a rejection:\n- DDASIT\n- DDDSIT\n- DDFSIT\n- DOASIT\n- DOPSIT\n- DOFSIT\n- DDSFSC\n- DOSFSC\n\nAt a MINIMUM, the payload for resubmitting a rejected SIT service item must look like this:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"reServiceCode\": \"DDFSIT\",\n \"updateReason\": \"A reason that differs from the previous reason\",\n \"modelType\": \"UpdateMTOServiceItemSIT\",\n \"requestApprovalsRequestedStatus\": true\n}\n` + "`" + `` + "`" + `` + "`" + `\n", "consumes": [ "application/json" ], @@ -5180,7 +5210,7 @@ func init() { }, "/mto-shipments/{mtoShipmentID}/addresses/{addressID}": { "put": { - "description": "### Functionality\nThis endpoint is used to **update** the pickup, secondary, and destination addresses on an MTO Shipment. mto-shipments/{mtoShipmentID}/shipment-address-updates is for updating a delivery address. The address details completely replace the original, except for the UUID.\nTherefore a complete address should be sent in the request.\nWhen a destination address on a shipment is updated, the destination SIT service items address ID will also be updated so that shipment and service item final destinations match.\n\nThis endpoint **cannot create** an address.\nTo create an address on an MTO shipment, the caller must use [updateMTOShipment](#operation/updateMTOShipment) as the parent shipment has to be updated with the appropriate link to the address.\n\n### Errors\nThe address must be associated with the mtoShipment passed in the url.\nIn other words, it should be listed as pickupAddress, destinationAddress, secondaryPickupAddress or secondaryDeliveryAddress on the mtoShipment provided.\nIf it is not, caller will receive a **Conflict** Error.\n\nThe mtoShipment should be associated with an MTO that is available to prime.\nIf the caller requests an update to an address, and the shipment is not on an available MTO, the caller will receive a **NotFound** Error.\n", + "description": "### Functionality\nThis endpoint is used to **update** the pickup, secondary, and delivery addresses on an MTO Shipment. mto-shipments/{mtoShipmentID}/shipment-address-updates is for updating a delivery address. The address details completely replace the original, except for the UUID.\nTherefore a complete address should be sent in the request.\nWhen a delivery address on a shipment is updated, the destination SIT service items address ID will also be updated so that shipment and service item final destinations match.\n\nThis endpoint **cannot create** an address.\nTo create an address on an MTO shipment, the caller must use [updateMTOShipment](#operation/updateMTOShipment) as the parent shipment has to be updated with the appropriate link to the address.\n\n### Errors\nThe address must be associated with the mtoShipment passed in the url.\nIn other words, it should be listed as pickupAddress, destinationAddress, secondaryPickupAddress or secondaryDeliveryAddress on the mtoShipment provided.\nIf it is not, caller will receive a **Conflict** Error.\n\nThe mtoShipment should be associated with an MTO that is available to prime.\nIf the caller requests an update to an address, and the shipment is not on an available MTO, the caller will receive a **NotFound** Error.\n", "consumes": [ "application/json" ], @@ -5573,7 +5603,7 @@ func init() { }, "/mto-shipments/{mtoShipmentID}/shipment-address-updates": { "post": { - "description": "### Functionality\nThis endpoint is used so the Prime can request an **update** for the destination address on an MTO Shipment,\nafter the destination address has already been approved.\n\nThis endpoint and operation only supports the following shipment types:\n- HHG\n- NTSR\n\nFor HHG shipments, if automatically approved or TOO approves, this will update the final destination address values for destination SIT service items to be the same as the changed destination address that was approved.\n\nAddress updates will be automatically approved unless they change:\n - The service area\n - Mileage bracket for direct delivery\n - the address and the distance between the old and new address is \u003e 50\n - Domestic Short Haul to Domestic Line Haul or vice versa\n - Shipments that start and end in one ZIP3 use Short Haul pricing\n - Shipments that start and end in different ZIP3s use Line Haul pricing\n\nFor those, changes will require TOO approval.\n", + "description": "### Functionality\nThis endpoint is used so the Prime can request an **update** for the delivery address on an MTO Shipment,\nafter the delivery address has already been approved.\n\nThis endpoint and operation only supports the following shipment types:\n- HHG\n- NTSR\n\nFor HHG shipments, if automatically approved or TOO approves, this will update the final delivery address values for destination SIT service items to be the same as the changed delivery address that was approved.\n\nAddress updates will be automatically approved unless they change:\n - The service area\n - Mileage bracket for direct delivery\n - the address and the distance between the old and new address is \u003e 50\n - Domestic Short Haul to Domestic Line Haul or vice versa\n - Shipments that start and end in one ZIP3 use Short Haul pricing\n - Shipments that start and end in different ZIP3s use Line Haul pricing\n\nFor those, changes will require TOO approval.\n", "consumes": [ "application/json" ], @@ -6614,6 +6644,12 @@ func init() { "type": "integer", "x-formatting": "weight", "example": 500 + }, + "unaccompaniedBaggageAllowance": { + "description": "The amount of weight in pounds that the move is entitled for shipment types of Unaccompanied Baggage.", + "type": "integer", + "x-nullable": true, + "example": 3 } } }, @@ -6698,6 +6734,16 @@ func init() { "format": "date-time", "readOnly": true }, + "destinationGBLOC": { + "type": "string", + "readOnly": true, + "example": "JFK" + }, + "destinationPostalCode": { + "type": "string", + "readOnly": true, + "example": "90210" + }, "eTag": { "type": "string", "readOnly": true @@ -7634,6 +7680,16 @@ func init() { "format": "date-time", "readOnly": true }, + "destinationGBLOC": { + "type": "string", + "readOnly": true, + "example": "KKFA" + }, + "destinationPostalCode": { + "type": "string", + "readOnly": true, + "example": "90210" + }, "eTag": { "type": "string", "readOnly": true @@ -7788,15 +7844,19 @@ func init() { "WOUNDED_WARRIOR", "BLUEBARK", "SAFETY", - "TEMPORARY_DUTY" + "TEMPORARY_DUTY", + "EARLY_RETURN_OF_DEPENDENTS", + "STUDENT_TRAVEL" ], "x-display-value": { "BLUEBARK": "BLUEBARK", + "EARLY_RETURN_OF_DEPENDENTS": "Early Return of Dependents", "LOCAL_MOVE": "Local Move", "PERMANENT_CHANGE_OF_STATION": "Permanent Change Of Station", "RETIREMENT": "Retirement", "SAFETY": "Safety", "SEPARATION": "Separation", + "STUDENT_TRAVEL": "Student Travel", "TEMPORARY_DUTY": "Temporary Duty (TDY)", "WOUNDED_WARRIOR": "Wounded Warrior" } @@ -8581,7 +8641,7 @@ func init() { } }, "ShipmentAddressUpdate": { - "description": "This represents a destination address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO.\n", + "description": "This represents a delivery address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO.\n", "type": "object", "required": [ "id", @@ -8609,7 +8669,7 @@ func init() { "$ref": "#/definitions/Address" }, "newSitDistanceBetween": { - "description": "The distance between the original SIT address and requested new destination address of shipment", + "description": "The distance between the original SIT address and requested new delivery address of shipment", "type": "integer", "minimum": 0, "example": 88 @@ -8622,7 +8682,7 @@ func init() { "example": "This is an office remark" }, "oldSitDistanceBetween": { - "description": "The distance between the original SIT address and the previous/old destination address of shipment", + "description": "The distance between the original SIT address and the previous/old delivery address of shipment", "type": "integer", "minimum": 0, "example": 50 @@ -9112,7 +9172,7 @@ func init() { } }, "UpdateShipmentDestinationAddress": { - "description": "UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the destination address on an MTO Shipment.", + "description": "UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the delivery address on an MTO Shipment.", "type": "object", "required": [ "contractorRemarks", diff --git a/pkg/gen/primeapi/primeoperations/mto_service_item/update_m_t_o_service_item.go b/pkg/gen/primeapi/primeoperations/mto_service_item/update_m_t_o_service_item.go index f8b307b79f0..f7f4c334317 100644 --- a/pkg/gen/primeapi/primeoperations/mto_service_item/update_m_t_o_service_item.go +++ b/pkg/gen/primeapi/primeoperations/mto_service_item/update_m_t_o_service_item.go @@ -40,7 +40,7 @@ This endpoint supports different body definitions. In the modelType field below, to the service item you wish to update and the documentation will update with the new definition. -* Addresses: To update a destination service item's SIT destination final address, update the shipment destination address. +* Addresses: To update a destination service item's SIT destination final address, update the shipment delivery address. For approved shipments, please use [updateShipmentDestinationAddress](#mtoShipment/updateShipmentDestinationAddress). For shipments not yet approved, please use [updateMTOShipmentAddress](#mtoShipment/updateMTOShipmentAddress). diff --git a/pkg/gen/primeapi/primeoperations/mto_shipment/update_m_t_o_shipment_address.go b/pkg/gen/primeapi/primeoperations/mto_shipment/update_m_t_o_shipment_address.go index d0c377c5163..b5d1e738c34 100644 --- a/pkg/gen/primeapi/primeoperations/mto_shipment/update_m_t_o_shipment_address.go +++ b/pkg/gen/primeapi/primeoperations/mto_shipment/update_m_t_o_shipment_address.go @@ -35,9 +35,9 @@ func NewUpdateMTOShipmentAddress(ctx *middleware.Context, handler UpdateMTOShipm updateMTOShipmentAddress ### Functionality -This endpoint is used to **update** the pickup, secondary, and destination addresses on an MTO Shipment. mto-shipments/{mtoShipmentID}/shipment-address-updates is for updating a delivery address. The address details completely replace the original, except for the UUID. +This endpoint is used to **update** the pickup, secondary, and delivery addresses on an MTO Shipment. mto-shipments/{mtoShipmentID}/shipment-address-updates is for updating a delivery address. The address details completely replace the original, except for the UUID. Therefore a complete address should be sent in the request. -When a destination address on a shipment is updated, the destination SIT service items address ID will also be updated so that shipment and service item final destinations match. +When a delivery address on a shipment is updated, the destination SIT service items address ID will also be updated so that shipment and service item final destinations match. This endpoint **cannot create** an address. To create an address on an MTO shipment, the caller must use [updateMTOShipment](#operation/updateMTOShipment) as the parent shipment has to be updated with the appropriate link to the address. diff --git a/pkg/gen/primeapi/primeoperations/mto_shipment/update_shipment_destination_address.go b/pkg/gen/primeapi/primeoperations/mto_shipment/update_shipment_destination_address.go index 7f6b0337bbf..05d4da3e964 100644 --- a/pkg/gen/primeapi/primeoperations/mto_shipment/update_shipment_destination_address.go +++ b/pkg/gen/primeapi/primeoperations/mto_shipment/update_shipment_destination_address.go @@ -35,14 +35,14 @@ func NewUpdateShipmentDestinationAddress(ctx *middleware.Context, handler Update updateShipmentDestinationAddress ### Functionality -This endpoint is used so the Prime can request an **update** for the destination address on an MTO Shipment, -after the destination address has already been approved. +This endpoint is used so the Prime can request an **update** for the delivery address on an MTO Shipment, +after the delivery address has already been approved. This endpoint and operation only supports the following shipment types: - HHG - NTSR -For HHG shipments, if automatically approved or TOO approves, this will update the final destination address values for destination SIT service items to be the same as the changed destination address that was approved. +For HHG shipments, if automatically approved or TOO approves, this will update the final delivery address values for destination SIT service items to be the same as the changed delivery address that was approved. Address updates will be automatically approved unless they change: - The service area diff --git a/pkg/gen/primeclient/mto_service_item/mto_service_item_client.go b/pkg/gen/primeclient/mto_service_item/mto_service_item_client.go index 97e446ab844..76bd5b73e25 100644 --- a/pkg/gen/primeclient/mto_service_item/mto_service_item_client.go +++ b/pkg/gen/primeclient/mto_service_item/mto_service_item_client.go @@ -223,7 +223,7 @@ This endpoint supports different body definitions. In the modelType field below, to the service item you wish to update and the documentation will update with the new definition. -* Addresses: To update a destination service item's SIT destination final address, update the shipment destination address. +* Addresses: To update a destination service item's SIT destination final address, update the shipment delivery address. For approved shipments, please use [updateShipmentDestinationAddress](#mtoShipment/updateShipmentDestinationAddress). For shipments not yet approved, please use [updateMTOShipmentAddress](#mtoShipment/updateMTOShipmentAddress). diff --git a/pkg/gen/primeclient/mto_shipment/mto_shipment_client.go b/pkg/gen/primeclient/mto_shipment/mto_shipment_client.go index 47d681c22e7..2051172f1d1 100644 --- a/pkg/gen/primeclient/mto_shipment/mto_shipment_client.go +++ b/pkg/gen/primeclient/mto_shipment/mto_shipment_client.go @@ -319,9 +319,9 @@ func (a *Client) UpdateMTOShipment(params *UpdateMTOShipmentParams, opts ...Clie ### Functionality -This endpoint is used to **update** the pickup, secondary, and destination addresses on an MTO Shipment. mto-shipments/{mtoShipmentID}/shipment-address-updates is for updating a delivery address. The address details completely replace the original, except for the UUID. +This endpoint is used to **update** the pickup, secondary, and delivery addresses on an MTO Shipment. mto-shipments/{mtoShipmentID}/shipment-address-updates is for updating a delivery address. The address details completely replace the original, except for the UUID. Therefore a complete address should be sent in the request. -When a destination address on a shipment is updated, the destination SIT service items address ID will also be updated so that shipment and service item final destinations match. +When a delivery address on a shipment is updated, the destination SIT service items address ID will also be updated so that shipment and service item final destinations match. This endpoint **cannot create** an address. To create an address on an MTO shipment, the caller must use [updateMTOShipment](#operation/updateMTOShipment) as the parent shipment has to be updated with the appropriate link to the address. @@ -464,14 +464,14 @@ func (a *Client) UpdateReweigh(params *UpdateReweighParams, opts ...ClientOption ### Functionality -This endpoint is used so the Prime can request an **update** for the destination address on an MTO Shipment, -after the destination address has already been approved. +This endpoint is used so the Prime can request an **update** for the delivery address on an MTO Shipment, +after the delivery address has already been approved. This endpoint and operation only supports the following shipment types: - HHG - NTSR -For HHG shipments, if automatically approved or TOO approves, this will update the final destination address values for destination SIT service items to be the same as the changed destination address that was approved. +For HHG shipments, if automatically approved or TOO approves, this will update the final delivery address values for destination SIT service items to be the same as the changed delivery address that was approved. Address updates will be automatically approved unless they change: - The service area diff --git a/pkg/gen/primemessages/entitlements.go b/pkg/gen/primemessages/entitlements.go index 7c498cd20a0..c51ada24273 100644 --- a/pkg/gen/primemessages/entitlements.go +++ b/pkg/gen/primemessages/entitlements.go @@ -75,6 +75,10 @@ type Entitlements struct { // total weight // Example: 500 TotalWeight int64 `json:"totalWeight,omitempty"` + + // The amount of weight in pounds that the move is entitled for shipment types of Unaccompanied Baggage. + // Example: 3 + UnaccompaniedBaggageAllowance *int64 `json:"unaccompaniedBaggageAllowance,omitempty"` } // Validate validates this entitlements diff --git a/pkg/gen/primemessages/list_move.go b/pkg/gen/primemessages/list_move.go index 4e9af4f1e52..27440ca263b 100644 --- a/pkg/gen/primemessages/list_move.go +++ b/pkg/gen/primemessages/list_move.go @@ -38,6 +38,16 @@ type ListMove struct { // Format: date-time CreatedAt strfmt.DateTime `json:"createdAt,omitempty"` + // destination g b l o c + // Example: JFK + // Read Only: true + DestinationGBLOC string `json:"destinationGBLOC,omitempty"` + + // destination postal code + // Example: 90210 + // Read Only: true + DestinationPostalCode string `json:"destinationPostalCode,omitempty"` + // e tag // Read Only: true ETag string `json:"eTag,omitempty"` @@ -266,6 +276,14 @@ func (m *ListMove) ContextValidate(ctx context.Context, formats strfmt.Registry) res = append(res, err) } + if err := m.contextValidateDestinationGBLOC(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateDestinationPostalCode(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateETag(ctx, formats); err != nil { res = append(res, err) } @@ -332,6 +350,24 @@ func (m *ListMove) contextValidateCreatedAt(ctx context.Context, formats strfmt. return nil } +func (m *ListMove) contextValidateDestinationGBLOC(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "destinationGBLOC", "body", string(m.DestinationGBLOC)); err != nil { + return err + } + + return nil +} + +func (m *ListMove) contextValidateDestinationPostalCode(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "destinationPostalCode", "body", string(m.DestinationPostalCode)); err != nil { + return err + } + + return nil +} + func (m *ListMove) contextValidateETag(ctx context.Context, formats strfmt.Registry) error { if err := validate.ReadOnly(ctx, "eTag", "body", string(m.ETag)); err != nil { diff --git a/pkg/gen/primemessages/move_task_order.go b/pkg/gen/primemessages/move_task_order.go index 3d5f5854487..befdf8fe3d0 100644 --- a/pkg/gen/primemessages/move_task_order.go +++ b/pkg/gen/primemessages/move_task_order.go @@ -39,6 +39,16 @@ type MoveTaskOrder struct { // Format: date-time CreatedAt strfmt.DateTime `json:"createdAt,omitempty"` + // destination g b l o c + // Example: KKFA + // Read Only: true + DestinationGBLOC string `json:"destinationGBLOC,omitempty"` + + // destination postal code + // Example: 90210 + // Read Only: true + DestinationPostalCode string `json:"destinationPostalCode,omitempty"` + // e tag // Read Only: true ETag string `json:"eTag,omitempty"` @@ -127,6 +137,10 @@ func (m *MoveTaskOrder) UnmarshalJSON(raw []byte) error { CreatedAt strfmt.DateTime `json:"createdAt,omitempty"` + DestinationGBLOC string `json:"destinationGBLOC,omitempty"` + + DestinationPostalCode string `json:"destinationPostalCode,omitempty"` + ETag string `json:"eTag,omitempty"` ExcessWeightAcknowledgedAt *strfmt.DateTime `json:"excessWeightAcknowledgedAt"` @@ -183,6 +197,12 @@ func (m *MoveTaskOrder) UnmarshalJSON(raw []byte) error { // createdAt result.CreatedAt = data.CreatedAt + // destinationGBLOC + result.DestinationGBLOC = data.DestinationGBLOC + + // destinationPostalCode + result.DestinationPostalCode = data.DestinationPostalCode + // eTag result.ETag = data.ETag @@ -247,6 +267,10 @@ func (m MoveTaskOrder) MarshalJSON() ([]byte, error) { CreatedAt strfmt.DateTime `json:"createdAt,omitempty"` + DestinationGBLOC string `json:"destinationGBLOC,omitempty"` + + DestinationPostalCode string `json:"destinationPostalCode,omitempty"` + ETag string `json:"eTag,omitempty"` ExcessWeightAcknowledgedAt *strfmt.DateTime `json:"excessWeightAcknowledgedAt"` @@ -284,6 +308,10 @@ func (m MoveTaskOrder) MarshalJSON() ([]byte, error) { CreatedAt: m.CreatedAt, + DestinationGBLOC: m.DestinationGBLOC, + + DestinationPostalCode: m.DestinationPostalCode, + ETag: m.ETag, ExcessWeightAcknowledgedAt: m.ExcessWeightAcknowledgedAt, @@ -655,6 +683,14 @@ func (m *MoveTaskOrder) ContextValidate(ctx context.Context, formats strfmt.Regi res = append(res, err) } + if err := m.contextValidateDestinationGBLOC(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateDestinationPostalCode(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateETag(ctx, formats); err != nil { res = append(res, err) } @@ -732,6 +768,24 @@ func (m *MoveTaskOrder) contextValidateCreatedAt(ctx context.Context, formats st return nil } +func (m *MoveTaskOrder) contextValidateDestinationGBLOC(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "destinationGBLOC", "body", string(m.DestinationGBLOC)); err != nil { + return err + } + + return nil +} + +func (m *MoveTaskOrder) contextValidateDestinationPostalCode(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "destinationPostalCode", "body", string(m.DestinationPostalCode)); err != nil { + return err + } + + return nil +} + func (m *MoveTaskOrder) contextValidateETag(ctx context.Context, formats strfmt.Registry) error { if err := validate.ReadOnly(ctx, "eTag", "body", string(m.ETag)); err != nil { diff --git a/pkg/gen/primemessages/orders_type.go b/pkg/gen/primemessages/orders_type.go index 8128616b85b..84f9bf262bc 100644 --- a/pkg/gen/primemessages/orders_type.go +++ b/pkg/gen/primemessages/orders_type.go @@ -53,6 +53,12 @@ const ( // OrdersTypeTEMPORARYDUTY captures enum value "TEMPORARY_DUTY" OrdersTypeTEMPORARYDUTY OrdersType = "TEMPORARY_DUTY" + + // OrdersTypeEARLYRETURNOFDEPENDENTS captures enum value "EARLY_RETURN_OF_DEPENDENTS" + OrdersTypeEARLYRETURNOFDEPENDENTS OrdersType = "EARLY_RETURN_OF_DEPENDENTS" + + // OrdersTypeSTUDENTTRAVEL captures enum value "STUDENT_TRAVEL" + OrdersTypeSTUDENTTRAVEL OrdersType = "STUDENT_TRAVEL" ) // for schema @@ -60,7 +66,7 @@ var ordersTypeEnum []interface{} func init() { var res []OrdersType - if err := json.Unmarshal([]byte(`["PERMANENT_CHANGE_OF_STATION","LOCAL_MOVE","RETIREMENT","SEPARATION","WOUNDED_WARRIOR","BLUEBARK","SAFETY","TEMPORARY_DUTY"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["PERMANENT_CHANGE_OF_STATION","LOCAL_MOVE","RETIREMENT","SEPARATION","WOUNDED_WARRIOR","BLUEBARK","SAFETY","TEMPORARY_DUTY","EARLY_RETURN_OF_DEPENDENTS","STUDENT_TRAVEL"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/primemessages/shipment_address_update.go b/pkg/gen/primemessages/shipment_address_update.go index 647192fe679..0e5b4258565 100644 --- a/pkg/gen/primemessages/shipment_address_update.go +++ b/pkg/gen/primemessages/shipment_address_update.go @@ -14,7 +14,7 @@ import ( "github.com/go-openapi/validate" ) -// ShipmentAddressUpdate This represents a destination address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO. +// ShipmentAddressUpdate This represents a delivery address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO. // // swagger:model ShipmentAddressUpdate type ShipmentAddressUpdate struct { @@ -38,7 +38,7 @@ type ShipmentAddressUpdate struct { // Required: true NewAddress *Address `json:"newAddress"` - // The distance between the original SIT address and requested new destination address of shipment + // The distance between the original SIT address and requested new delivery address of shipment // Example: 88 // Minimum: 0 NewSitDistanceBetween *int64 `json:"newSitDistanceBetween,omitempty"` @@ -49,7 +49,7 @@ type ShipmentAddressUpdate struct { // Example: This is an office remark OfficeRemarks *string `json:"officeRemarks,omitempty"` - // The distance between the original SIT address and the previous/old destination address of shipment + // The distance between the original SIT address and the previous/old delivery address of shipment // Example: 50 // Minimum: 0 OldSitDistanceBetween *int64 `json:"oldSitDistanceBetween,omitempty"` diff --git a/pkg/gen/primemessages/update_shipment_destination_address.go b/pkg/gen/primemessages/update_shipment_destination_address.go index c6e82f5275e..6089fccc289 100644 --- a/pkg/gen/primemessages/update_shipment_destination_address.go +++ b/pkg/gen/primemessages/update_shipment_destination_address.go @@ -14,7 +14,7 @@ import ( "github.com/go-openapi/validate" ) -// UpdateShipmentDestinationAddress UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the destination address on an MTO Shipment. +// UpdateShipmentDestinationAddress UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the delivery address on an MTO Shipment. // // swagger:model UpdateShipmentDestinationAddress type UpdateShipmentDestinationAddress struct { diff --git a/pkg/gen/primev2api/embedded_spec.go b/pkg/gen/primev2api/embedded_spec.go index 97502ab2dbc..eea553b11ee 100644 --- a/pkg/gen/primev2api/embedded_spec.go +++ b/pkg/gen/primev2api/embedded_spec.go @@ -1026,6 +1026,12 @@ func init() { "type": "integer", "x-formatting": "weight", "example": 500 + }, + "unaccompaniedBaggageAllowance": { + "description": "The amount of weight in pounds that the move is entitled for shipment types of Unaccompanied Baggage.", + "type": "integer", + "x-nullable": true, + "example": 3 } } }, @@ -1945,6 +1951,16 @@ func init() { "format": "date-time", "readOnly": true }, + "destinationGBLOC": { + "type": "string", + "readOnly": true, + "example": "KKFA" + }, + "destinationPostalCode": { + "type": "string", + "readOnly": true, + "example": "90210" + }, "eTag": { "type": "string", "readOnly": true @@ -2115,15 +2131,19 @@ func init() { "WOUNDED_WARRIOR", "BLUEBARK", "SAFETY", - "TEMPORARY_DUTY" + "TEMPORARY_DUTY", + "EARLY_RETURN_OF_DEPENDENTS", + "STUDENT_TRAVEL" ], "x-display-value": { "BLUEBARK": "BLUEBARK", + "EARLY_RETURN_OF_DEPENDENTS": "Early Return of Dependents", "LOCAL_MOVE": "Local Move", "PERMANENT_CHANGE_OF_STATION": "Permanent Change Of Station", "RETIREMENT": "Retirement", "SAFETY": "Safety", "SEPARATION": "Separation", + "STUDENT_TRAVEL": "Student Travel", "TEMPORARY_DUTY": "Temporary Duty (TDY)", "WOUNDED_WARRIOR": "Wounded Warrior" } @@ -2874,7 +2894,7 @@ func init() { } }, "ShipmentAddressUpdate": { - "description": "This represents a destination address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO.\n", + "description": "This represents a delivery address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO.\n", "type": "object", "required": [ "id", @@ -2902,7 +2922,7 @@ func init() { "$ref": "#/definitions/Address" }, "newSitDistanceBetween": { - "description": "The distance between the original SIT address and requested new destination address of shipment", + "description": "The distance between the original SIT address and requested new delivery address of shipment", "type": "integer", "example": 88 }, @@ -2914,7 +2934,7 @@ func init() { "example": "This is an office remark" }, "oldSitDistanceBetween": { - "description": "The distance between the original SIT address and the previous/old destination address of shipment", + "description": "The distance between the original SIT address and the previous/old delivery address of shipment", "type": "integer", "example": 50 }, @@ -3395,7 +3415,7 @@ func init() { } }, "UpdateShipmentDestinationAddress": { - "description": "UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the destination address on an MTO Shipment.", + "description": "UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the delivery address on an MTO Shipment.", "type": "object", "required": [ "contractorRemarks", @@ -4617,6 +4637,12 @@ func init() { "type": "integer", "x-formatting": "weight", "example": 500 + }, + "unaccompaniedBaggageAllowance": { + "description": "The amount of weight in pounds that the move is entitled for shipment types of Unaccompanied Baggage.", + "type": "integer", + "x-nullable": true, + "example": 3 } } }, @@ -5536,6 +5562,16 @@ func init() { "format": "date-time", "readOnly": true }, + "destinationGBLOC": { + "type": "string", + "readOnly": true, + "example": "KKFA" + }, + "destinationPostalCode": { + "type": "string", + "readOnly": true, + "example": "90210" + }, "eTag": { "type": "string", "readOnly": true @@ -5706,15 +5742,19 @@ func init() { "WOUNDED_WARRIOR", "BLUEBARK", "SAFETY", - "TEMPORARY_DUTY" + "TEMPORARY_DUTY", + "EARLY_RETURN_OF_DEPENDENTS", + "STUDENT_TRAVEL" ], "x-display-value": { "BLUEBARK": "BLUEBARK", + "EARLY_RETURN_OF_DEPENDENTS": "Early Return of Dependents", "LOCAL_MOVE": "Local Move", "PERMANENT_CHANGE_OF_STATION": "Permanent Change Of Station", "RETIREMENT": "Retirement", "SAFETY": "Safety", "SEPARATION": "Separation", + "STUDENT_TRAVEL": "Student Travel", "TEMPORARY_DUTY": "Temporary Duty (TDY)", "WOUNDED_WARRIOR": "Wounded Warrior" } @@ -6465,7 +6505,7 @@ func init() { } }, "ShipmentAddressUpdate": { - "description": "This represents a destination address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO.\n", + "description": "This represents a delivery address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO.\n", "type": "object", "required": [ "id", @@ -6493,7 +6533,7 @@ func init() { "$ref": "#/definitions/Address" }, "newSitDistanceBetween": { - "description": "The distance between the original SIT address and requested new destination address of shipment", + "description": "The distance between the original SIT address and requested new delivery address of shipment", "type": "integer", "minimum": 0, "example": 88 @@ -6506,7 +6546,7 @@ func init() { "example": "This is an office remark" }, "oldSitDistanceBetween": { - "description": "The distance between the original SIT address and the previous/old destination address of shipment", + "description": "The distance between the original SIT address and the previous/old delivery address of shipment", "type": "integer", "minimum": 0, "example": 50 @@ -6988,7 +7028,7 @@ func init() { } }, "UpdateShipmentDestinationAddress": { - "description": "UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the destination address on an MTO Shipment.", + "description": "UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the delivery address on an MTO Shipment.", "type": "object", "required": [ "contractorRemarks", diff --git a/pkg/gen/primev2messages/entitlements.go b/pkg/gen/primev2messages/entitlements.go index f4390a92a35..e29d3f733e3 100644 --- a/pkg/gen/primev2messages/entitlements.go +++ b/pkg/gen/primev2messages/entitlements.go @@ -75,6 +75,10 @@ type Entitlements struct { // total weight // Example: 500 TotalWeight int64 `json:"totalWeight,omitempty"` + + // The amount of weight in pounds that the move is entitled for shipment types of Unaccompanied Baggage. + // Example: 3 + UnaccompaniedBaggageAllowance *int64 `json:"unaccompaniedBaggageAllowance,omitempty"` } // Validate validates this entitlements diff --git a/pkg/gen/primev2messages/move_task_order.go b/pkg/gen/primev2messages/move_task_order.go index af1390e30c7..c5e6cf21146 100644 --- a/pkg/gen/primev2messages/move_task_order.go +++ b/pkg/gen/primev2messages/move_task_order.go @@ -43,6 +43,16 @@ type MoveTaskOrder struct { // Format: date-time CreatedAt strfmt.DateTime `json:"createdAt,omitempty"` + // destination g b l o c + // Example: KKFA + // Read Only: true + DestinationGBLOC string `json:"destinationGBLOC,omitempty"` + + // destination postal code + // Example: 90210 + // Read Only: true + DestinationPostalCode string `json:"destinationPostalCode,omitempty"` + // e tag // Read Only: true ETag string `json:"eTag,omitempty"` @@ -133,6 +143,10 @@ func (m *MoveTaskOrder) UnmarshalJSON(raw []byte) error { CreatedAt strfmt.DateTime `json:"createdAt,omitempty"` + DestinationGBLOC string `json:"destinationGBLOC,omitempty"` + + DestinationPostalCode string `json:"destinationPostalCode,omitempty"` + ETag string `json:"eTag,omitempty"` ExcessWeightAcknowledgedAt *strfmt.DateTime `json:"excessWeightAcknowledgedAt"` @@ -192,6 +206,12 @@ func (m *MoveTaskOrder) UnmarshalJSON(raw []byte) error { // createdAt result.CreatedAt = data.CreatedAt + // destinationGBLOC + result.DestinationGBLOC = data.DestinationGBLOC + + // destinationPostalCode + result.DestinationPostalCode = data.DestinationPostalCode + // eTag result.ETag = data.ETag @@ -258,6 +278,10 @@ func (m MoveTaskOrder) MarshalJSON() ([]byte, error) { CreatedAt strfmt.DateTime `json:"createdAt,omitempty"` + DestinationGBLOC string `json:"destinationGBLOC,omitempty"` + + DestinationPostalCode string `json:"destinationPostalCode,omitempty"` + ETag string `json:"eTag,omitempty"` ExcessWeightAcknowledgedAt *strfmt.DateTime `json:"excessWeightAcknowledgedAt"` @@ -297,6 +321,10 @@ func (m MoveTaskOrder) MarshalJSON() ([]byte, error) { CreatedAt: m.CreatedAt, + DestinationGBLOC: m.DestinationGBLOC, + + DestinationPostalCode: m.DestinationPostalCode, + ETag: m.ETag, ExcessWeightAcknowledgedAt: m.ExcessWeightAcknowledgedAt, @@ -672,6 +700,14 @@ func (m *MoveTaskOrder) ContextValidate(ctx context.Context, formats strfmt.Regi res = append(res, err) } + if err := m.contextValidateDestinationGBLOC(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateDestinationPostalCode(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateETag(ctx, formats); err != nil { res = append(res, err) } @@ -758,6 +794,24 @@ func (m *MoveTaskOrder) contextValidateCreatedAt(ctx context.Context, formats st return nil } +func (m *MoveTaskOrder) contextValidateDestinationGBLOC(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "destinationGBLOC", "body", string(m.DestinationGBLOC)); err != nil { + return err + } + + return nil +} + +func (m *MoveTaskOrder) contextValidateDestinationPostalCode(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "destinationPostalCode", "body", string(m.DestinationPostalCode)); err != nil { + return err + } + + return nil +} + func (m *MoveTaskOrder) contextValidateETag(ctx context.Context, formats strfmt.Registry) error { if err := validate.ReadOnly(ctx, "eTag", "body", string(m.ETag)); err != nil { diff --git a/pkg/gen/primev2messages/orders_type.go b/pkg/gen/primev2messages/orders_type.go index a4b2330c915..a5f02303750 100644 --- a/pkg/gen/primev2messages/orders_type.go +++ b/pkg/gen/primev2messages/orders_type.go @@ -53,6 +53,12 @@ const ( // OrdersTypeTEMPORARYDUTY captures enum value "TEMPORARY_DUTY" OrdersTypeTEMPORARYDUTY OrdersType = "TEMPORARY_DUTY" + + // OrdersTypeEARLYRETURNOFDEPENDENTS captures enum value "EARLY_RETURN_OF_DEPENDENTS" + OrdersTypeEARLYRETURNOFDEPENDENTS OrdersType = "EARLY_RETURN_OF_DEPENDENTS" + + // OrdersTypeSTUDENTTRAVEL captures enum value "STUDENT_TRAVEL" + OrdersTypeSTUDENTTRAVEL OrdersType = "STUDENT_TRAVEL" ) // for schema @@ -60,7 +66,7 @@ var ordersTypeEnum []interface{} func init() { var res []OrdersType - if err := json.Unmarshal([]byte(`["PERMANENT_CHANGE_OF_STATION","LOCAL_MOVE","RETIREMENT","SEPARATION","WOUNDED_WARRIOR","BLUEBARK","SAFETY","TEMPORARY_DUTY"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["PERMANENT_CHANGE_OF_STATION","LOCAL_MOVE","RETIREMENT","SEPARATION","WOUNDED_WARRIOR","BLUEBARK","SAFETY","TEMPORARY_DUTY","EARLY_RETURN_OF_DEPENDENTS","STUDENT_TRAVEL"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/primev2messages/shipment_address_update.go b/pkg/gen/primev2messages/shipment_address_update.go index 9c42f92d5ca..af7df83363b 100644 --- a/pkg/gen/primev2messages/shipment_address_update.go +++ b/pkg/gen/primev2messages/shipment_address_update.go @@ -14,7 +14,7 @@ import ( "github.com/go-openapi/validate" ) -// ShipmentAddressUpdate This represents a destination address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO. +// ShipmentAddressUpdate This represents a delivery address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO. // // swagger:model ShipmentAddressUpdate type ShipmentAddressUpdate struct { @@ -38,7 +38,7 @@ type ShipmentAddressUpdate struct { // Required: true NewAddress *Address `json:"newAddress"` - // The distance between the original SIT address and requested new destination address of shipment + // The distance between the original SIT address and requested new delivery address of shipment // Example: 88 // Minimum: 0 NewSitDistanceBetween *int64 `json:"newSitDistanceBetween,omitempty"` @@ -49,7 +49,7 @@ type ShipmentAddressUpdate struct { // Example: This is an office remark OfficeRemarks *string `json:"officeRemarks,omitempty"` - // The distance between the original SIT address and the previous/old destination address of shipment + // The distance between the original SIT address and the previous/old delivery address of shipment // Example: 50 // Minimum: 0 OldSitDistanceBetween *int64 `json:"oldSitDistanceBetween,omitempty"` diff --git a/pkg/gen/primev2messages/update_shipment_destination_address.go b/pkg/gen/primev2messages/update_shipment_destination_address.go index 5c4e9f085d2..aa41a7747d8 100644 --- a/pkg/gen/primev2messages/update_shipment_destination_address.go +++ b/pkg/gen/primev2messages/update_shipment_destination_address.go @@ -14,7 +14,7 @@ import ( "github.com/go-openapi/validate" ) -// UpdateShipmentDestinationAddress UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the destination address on an MTO Shipment. +// UpdateShipmentDestinationAddress UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the delivery address on an MTO Shipment. // // swagger:model UpdateShipmentDestinationAddress type UpdateShipmentDestinationAddress struct { diff --git a/pkg/gen/primev3api/embedded_spec.go b/pkg/gen/primev3api/embedded_spec.go index bb65ac5f892..14a06694328 100644 --- a/pkg/gen/primev3api/embedded_spec.go +++ b/pkg/gen/primev3api/embedded_spec.go @@ -739,7 +739,7 @@ func init() { "example": "handle with care" }, "destinationAddress": { - "description": "Where the movers should deliver this shipment.", + "description": "primary location the movers should deliver this shipment.", "allOf": [ { "$ref": "#/definitions/Address" @@ -773,7 +773,7 @@ func init() { } }, "pickupAddress": { - "description": "The address where the movers should pick up this shipment.", + "description": "The primary address where the movers should pick up this shipment.", "allOf": [ { "$ref": "#/definitions/Address" @@ -801,7 +801,7 @@ func init() { "x-nullable": true }, "secondaryDestinationAddress": { - "description": "The second address where the movers should deliver this shipment.", + "description": "second location where the movers should deliver this shipment.", "allOf": [ { "$ref": "#/definitions/Address" @@ -820,7 +820,7 @@ func init() { "$ref": "#/definitions/MTOShipmentType" }, "tertiaryDestinationAddress": { - "description": "The third address where the movers should deliver this shipment.", + "description": "third location where the movers should deliver this shipment.", "allOf": [ { "$ref": "#/definitions/Address" @@ -938,7 +938,7 @@ func init() { ] }, "secondaryPickupAddress": { - "description": "An optional secondary pickup location address near the origin where additional goods exist.", + "description": "An optional secondary Pickup Address address near the origin where additional goods exist.", "allOf": [ { "$ref": "#/definitions/Address" @@ -991,7 +991,7 @@ func init() { ] }, "tertiaryPickupAddress": { - "description": "An optional tertiary pickup location address near the origin where additional goods exist.", + "description": "An optional tertiary Pickup Address address near the origin where additional goods exist.", "allOf": [ { "$ref": "#/definitions/Address" @@ -1188,6 +1188,12 @@ func init() { "type": "integer", "x-formatting": "weight", "example": 500 + }, + "unaccompaniedBaggageAllowance": { + "description": "The amount of weight in pounds that the move is entitled for shipment types of Unaccompanied Baggage.", + "type": "integer", + "x-nullable": true, + "example": 3 } } }, @@ -1877,6 +1883,9 @@ func init() { } ] }, + "destinationRateArea": { + "$ref": "#/definitions/RateArea" + }, "destinationSitAuthEndDate": { "description": "The SIT authorized end date for destination SIT.", "type": "string", @@ -1941,6 +1950,9 @@ func init() { "x-nullable": true, "example": 4500 }, + "originRateArea": { + "$ref": "#/definitions/RateArea" + }, "originSitAuthEndDate": { "description": "The SIT authorized end date for origin SIT.", "type": "string", @@ -2167,6 +2179,16 @@ func init() { "format": "date-time", "readOnly": true }, + "destinationGBLOC": { + "type": "string", + "readOnly": true, + "example": "KKFA" + }, + "destinationPostalCode": { + "type": "string", + "readOnly": true, + "example": "90210" + }, "eTag": { "type": "string", "readOnly": true @@ -2337,15 +2359,19 @@ func init() { "WOUNDED_WARRIOR", "BLUEBARK", "SAFETY", - "TEMPORARY_DUTY" + "TEMPORARY_DUTY", + "EARLY_RETURN_OF_DEPENDENTS", + "STUDENT_TRAVEL" ], "x-display-value": { "BLUEBARK": "BLUEBARK", + "EARLY_RETURN_OF_DEPENDENTS": "Early Return of Dependents", "LOCAL_MOVE": "Local Move", "PERMANENT_CHANGE_OF_STATION": "Permanent Change Of Station", "RETIREMENT": "Retirement", "SAFETY": "Safety", "SEPARATION": "Separation", + "STUDENT_TRAVEL": "Student Travel", "TEMPORARY_DUTY": "Temporary Duty (TDY)", "WOUNDED_WARRIOR": "Wounded Warrior" } @@ -2594,6 +2620,9 @@ func init() { "destinationAddress": { "$ref": "#/definitions/PPMDestinationAddress" }, + "destinationRateArea": { + "$ref": "#/definitions/RateArea" + }, "eTag": { "description": "A hash unique to this shipment that should be used as the \"If-Match\" header for any updates.", "type": "string", @@ -2677,6 +2706,9 @@ func init() { "x-nullable": true, "x-omitempty": false }, + "originRateArea": { + "$ref": "#/definitions/RateArea" + }, "pickupAddress": { "$ref": "#/definitions/Address" }, @@ -2987,6 +3019,32 @@ func init() { "$ref": "#/definitions/ProofOfServiceDoc" } }, + "RateArea": { + "description": "Rate area info for OCONUS postal code", + "type": "object", + "required": [ + "id", + "rateAreaId", + "rateAreaName" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid", + "example": "1f2270c7-7166-40ae-981e-b200ebdf3054" + }, + "rateAreaId": { + "description": "Rate area code", + "type": "string", + "example": "US8101000" + }, + "rateAreaName": { + "description": "Rate area name", + "type": "string", + "example": "Alaska (Zone) I" + } + } + }, "ReServiceCode": { "description": "This is the full list of service items that can be found on a shipment. Not all service items\nmay be requested by the Prime, but may be returned in a response.\n\nDocumentation of all the service items will be provided.\n", "type": "string", @@ -3309,7 +3367,7 @@ func init() { } }, "ShipmentAddressUpdate": { - "description": "This represents a destination address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO.\n", + "description": "This represents a delivery address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO.\n", "type": "object", "required": [ "id", @@ -3337,7 +3395,7 @@ func init() { "$ref": "#/definitions/Address" }, "newSitDistanceBetween": { - "description": "The distance between the original SIT address and requested new destination address of shipment", + "description": "The distance between the original SIT address and requested new delivery address of shipment", "type": "integer", "example": 88 }, @@ -3349,7 +3407,7 @@ func init() { "example": "This is an office remark" }, "oldSitDistanceBetween": { - "description": "The distance between the original SIT address and the previous/old destination address of shipment", + "description": "The distance between the original SIT address and the previous/old delivery address of shipment", "type": "integer", "example": 50 }, @@ -3830,7 +3888,7 @@ func init() { ] }, "secondaryPickupAddress": { - "description": "An optional secondary pickup location near the origin where additional goods exist.\n", + "description": "An optional secondary Pickup Address near the origin where additional goods exist.\n", "allOf": [ { "$ref": "#/definitions/Address" @@ -3884,7 +3942,7 @@ func init() { ] }, "tertiaryPickupAddress": { - "description": "An optional third pickup location near the origin where additional goods exist.\n", + "description": "An optional third Pickup Address near the origin where additional goods exist.\n", "allOf": [ { "$ref": "#/definitions/Address" @@ -3916,7 +3974,7 @@ func init() { } }, "UpdateShipmentDestinationAddress": { - "description": "UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the destination address on an MTO Shipment.", + "description": "UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the delivery address on an MTO Shipment.", "type": "object", "required": [ "contractorRemarks", @@ -4851,7 +4909,7 @@ func init() { "example": "handle with care" }, "destinationAddress": { - "description": "Where the movers should deliver this shipment.", + "description": "primary location the movers should deliver this shipment.", "allOf": [ { "$ref": "#/definitions/Address" @@ -4885,7 +4943,7 @@ func init() { } }, "pickupAddress": { - "description": "The address where the movers should pick up this shipment.", + "description": "The primary address where the movers should pick up this shipment.", "allOf": [ { "$ref": "#/definitions/Address" @@ -4913,7 +4971,7 @@ func init() { "x-nullable": true }, "secondaryDestinationAddress": { - "description": "The second address where the movers should deliver this shipment.", + "description": "second location where the movers should deliver this shipment.", "allOf": [ { "$ref": "#/definitions/Address" @@ -4932,7 +4990,7 @@ func init() { "$ref": "#/definitions/MTOShipmentType" }, "tertiaryDestinationAddress": { - "description": "The third address where the movers should deliver this shipment.", + "description": "third location where the movers should deliver this shipment.", "allOf": [ { "$ref": "#/definitions/Address" @@ -5050,7 +5108,7 @@ func init() { ] }, "secondaryPickupAddress": { - "description": "An optional secondary pickup location address near the origin where additional goods exist.", + "description": "An optional secondary Pickup Address address near the origin where additional goods exist.", "allOf": [ { "$ref": "#/definitions/Address" @@ -5103,7 +5161,7 @@ func init() { ] }, "tertiaryPickupAddress": { - "description": "An optional tertiary pickup location address near the origin where additional goods exist.", + "description": "An optional tertiary Pickup Address address near the origin where additional goods exist.", "allOf": [ { "$ref": "#/definitions/Address" @@ -5300,6 +5358,12 @@ func init() { "type": "integer", "x-formatting": "weight", "example": 500 + }, + "unaccompaniedBaggageAllowance": { + "description": "The amount of weight in pounds that the move is entitled for shipment types of Unaccompanied Baggage.", + "type": "integer", + "x-nullable": true, + "example": 3 } } }, @@ -5989,6 +6053,9 @@ func init() { } ] }, + "destinationRateArea": { + "$ref": "#/definitions/RateArea" + }, "destinationSitAuthEndDate": { "description": "The SIT authorized end date for destination SIT.", "type": "string", @@ -6053,6 +6120,9 @@ func init() { "x-nullable": true, "example": 4500 }, + "originRateArea": { + "$ref": "#/definitions/RateArea" + }, "originSitAuthEndDate": { "description": "The SIT authorized end date for origin SIT.", "type": "string", @@ -6279,6 +6349,16 @@ func init() { "format": "date-time", "readOnly": true }, + "destinationGBLOC": { + "type": "string", + "readOnly": true, + "example": "KKFA" + }, + "destinationPostalCode": { + "type": "string", + "readOnly": true, + "example": "90210" + }, "eTag": { "type": "string", "readOnly": true @@ -6449,15 +6529,19 @@ func init() { "WOUNDED_WARRIOR", "BLUEBARK", "SAFETY", - "TEMPORARY_DUTY" + "TEMPORARY_DUTY", + "EARLY_RETURN_OF_DEPENDENTS", + "STUDENT_TRAVEL" ], "x-display-value": { "BLUEBARK": "BLUEBARK", + "EARLY_RETURN_OF_DEPENDENTS": "Early Return of Dependents", "LOCAL_MOVE": "Local Move", "PERMANENT_CHANGE_OF_STATION": "Permanent Change Of Station", "RETIREMENT": "Retirement", "SAFETY": "Safety", "SEPARATION": "Separation", + "STUDENT_TRAVEL": "Student Travel", "TEMPORARY_DUTY": "Temporary Duty (TDY)", "WOUNDED_WARRIOR": "Wounded Warrior" } @@ -6706,6 +6790,9 @@ func init() { "destinationAddress": { "$ref": "#/definitions/PPMDestinationAddress" }, + "destinationRateArea": { + "$ref": "#/definitions/RateArea" + }, "eTag": { "description": "A hash unique to this shipment that should be used as the \"If-Match\" header for any updates.", "type": "string", @@ -6789,6 +6876,9 @@ func init() { "x-nullable": true, "x-omitempty": false }, + "originRateArea": { + "$ref": "#/definitions/RateArea" + }, "pickupAddress": { "$ref": "#/definitions/Address" }, @@ -7099,6 +7189,32 @@ func init() { "$ref": "#/definitions/ProofOfServiceDoc" } }, + "RateArea": { + "description": "Rate area info for OCONUS postal code", + "type": "object", + "required": [ + "id", + "rateAreaId", + "rateAreaName" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid", + "example": "1f2270c7-7166-40ae-981e-b200ebdf3054" + }, + "rateAreaId": { + "description": "Rate area code", + "type": "string", + "example": "US8101000" + }, + "rateAreaName": { + "description": "Rate area name", + "type": "string", + "example": "Alaska (Zone) I" + } + } + }, "ReServiceCode": { "description": "This is the full list of service items that can be found on a shipment. Not all service items\nmay be requested by the Prime, but may be returned in a response.\n\nDocumentation of all the service items will be provided.\n", "type": "string", @@ -7421,7 +7537,7 @@ func init() { } }, "ShipmentAddressUpdate": { - "description": "This represents a destination address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO.\n", + "description": "This represents a delivery address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO.\n", "type": "object", "required": [ "id", @@ -7449,7 +7565,7 @@ func init() { "$ref": "#/definitions/Address" }, "newSitDistanceBetween": { - "description": "The distance between the original SIT address and requested new destination address of shipment", + "description": "The distance between the original SIT address and requested new delivery address of shipment", "type": "integer", "minimum": 0, "example": 88 @@ -7462,7 +7578,7 @@ func init() { "example": "This is an office remark" }, "oldSitDistanceBetween": { - "description": "The distance between the original SIT address and the previous/old destination address of shipment", + "description": "The distance between the original SIT address and the previous/old delivery address of shipment", "type": "integer", "minimum": 0, "example": 50 @@ -7944,7 +8060,7 @@ func init() { ] }, "secondaryPickupAddress": { - "description": "An optional secondary pickup location near the origin where additional goods exist.\n", + "description": "An optional secondary Pickup Address near the origin where additional goods exist.\n", "allOf": [ { "$ref": "#/definitions/Address" @@ -7998,7 +8114,7 @@ func init() { ] }, "tertiaryPickupAddress": { - "description": "An optional third pickup location near the origin where additional goods exist.\n", + "description": "An optional third Pickup Address near the origin where additional goods exist.\n", "allOf": [ { "$ref": "#/definitions/Address" @@ -8030,7 +8146,7 @@ func init() { } }, "UpdateShipmentDestinationAddress": { - "description": "UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the destination address on an MTO Shipment.", + "description": "UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the delivery address on an MTO Shipment.", "type": "object", "required": [ "contractorRemarks", diff --git a/pkg/gen/primev3messages/create_m_t_o_shipment.go b/pkg/gen/primev3messages/create_m_t_o_shipment.go index d3be412344b..c8496c4f60e 100644 --- a/pkg/gen/primev3messages/create_m_t_o_shipment.go +++ b/pkg/gen/primev3messages/create_m_t_o_shipment.go @@ -45,7 +45,7 @@ type CreateMTOShipment struct { // Example: handle with care CustomerRemarks *string `json:"customerRemarks,omitempty"` - // Where the movers should deliver this shipment. + // primary location the movers should deliver this shipment. DestinationAddress struct { Address } `json:"destinationAddress,omitempty"` @@ -71,7 +71,7 @@ type CreateMTOShipment struct { mtoServiceItemsField []MTOServiceItem - // The address where the movers should pick up this shipment. + // The primary address where the movers should pick up this shipment. PickupAddress struct { Address } `json:"pickupAddress,omitempty"` @@ -94,7 +94,7 @@ type CreateMTOShipment struct { // Format: date RequestedPickupDate *strfmt.Date `json:"requestedPickupDate,omitempty"` - // The second address where the movers should deliver this shipment. + // second location where the movers should deliver this shipment. SecondaryDestinationAddress struct { Address } `json:"secondaryDestinationAddress,omitempty"` @@ -108,7 +108,7 @@ type CreateMTOShipment struct { // Required: true ShipmentType *MTOShipmentType `json:"shipmentType"` - // The third address where the movers should deliver this shipment. + // third location where the movers should deliver this shipment. TertiaryDestinationAddress struct { Address } `json:"tertiaryDestinationAddress,omitempty"` diff --git a/pkg/gen/primev3messages/create_p_p_m_shipment.go b/pkg/gen/primev3messages/create_p_p_m_shipment.go index bc0b3c5c83a..61a5a3ed5ac 100644 --- a/pkg/gen/primev3messages/create_p_p_m_shipment.go +++ b/pkg/gen/primev3messages/create_p_p_m_shipment.go @@ -59,7 +59,7 @@ type CreatePPMShipment struct { Address } `json:"secondaryDestinationAddress,omitempty"` - // An optional secondary pickup location address near the origin where additional goods exist. + // An optional secondary Pickup Address address near the origin where additional goods exist. SecondaryPickupAddress struct { Address } `json:"secondaryPickupAddress,omitempty"` @@ -94,7 +94,7 @@ type CreatePPMShipment struct { Address } `json:"tertiaryDestinationAddress,omitempty"` - // An optional tertiary pickup location address near the origin where additional goods exist. + // An optional tertiary Pickup Address address near the origin where additional goods exist. TertiaryPickupAddress struct { Address } `json:"tertiaryPickupAddress,omitempty"` diff --git a/pkg/gen/primev3messages/entitlements.go b/pkg/gen/primev3messages/entitlements.go index 84abf8674a0..1e228c6350f 100644 --- a/pkg/gen/primev3messages/entitlements.go +++ b/pkg/gen/primev3messages/entitlements.go @@ -75,6 +75,10 @@ type Entitlements struct { // total weight // Example: 500 TotalWeight int64 `json:"totalWeight,omitempty"` + + // The amount of weight in pounds that the move is entitled for shipment types of Unaccompanied Baggage. + // Example: 3 + UnaccompaniedBaggageAllowance *int64 `json:"unaccompaniedBaggageAllowance,omitempty"` } // Validate validates this entitlements diff --git a/pkg/gen/primev3messages/m_t_o_shipment_without_service_items.go b/pkg/gen/primev3messages/m_t_o_shipment_without_service_items.go index 72d878b9b30..753b69b0304 100644 --- a/pkg/gen/primev3messages/m_t_o_shipment_without_service_items.go +++ b/pkg/gen/primev3messages/m_t_o_shipment_without_service_items.go @@ -89,6 +89,9 @@ type MTOShipmentWithoutServiceItems struct { Address } `json:"destinationAddress,omitempty"` + // destination rate area + DestinationRateArea *RateArea `json:"destinationRateArea,omitempty"` + // The SIT authorized end date for destination SIT. // Format: date DestinationSitAuthEndDate *strfmt.Date `json:"destinationSitAuthEndDate,omitempty"` @@ -138,6 +141,9 @@ type MTOShipmentWithoutServiceItems struct { // Example: 4500 NtsRecordedWeight *int64 `json:"ntsRecordedWeight,omitempty"` + // origin rate area + OriginRateArea *RateArea `json:"originRateArea,omitempty"` + // The SIT authorized end date for origin SIT. // Format: date OriginSitAuthEndDate *strfmt.Date `json:"originSitAuthEndDate,omitempty"` @@ -268,6 +274,10 @@ func (m *MTOShipmentWithoutServiceItems) Validate(formats strfmt.Registry) error res = append(res, err) } + if err := m.validateDestinationRateArea(formats); err != nil { + res = append(res, err) + } + if err := m.validateDestinationSitAuthEndDate(formats); err != nil { res = append(res, err) } @@ -296,6 +306,10 @@ func (m *MTOShipmentWithoutServiceItems) Validate(formats strfmt.Registry) error res = append(res, err) } + if err := m.validateOriginRateArea(formats); err != nil { + res = append(res, err) + } + if err := m.validateOriginSitAuthEndDate(formats); err != nil { res = append(res, err) } @@ -497,6 +511,25 @@ func (m *MTOShipmentWithoutServiceItems) validateDestinationAddress(formats strf return nil } +func (m *MTOShipmentWithoutServiceItems) validateDestinationRateArea(formats strfmt.Registry) error { + if swag.IsZero(m.DestinationRateArea) { // not required + return nil + } + + if m.DestinationRateArea != nil { + if err := m.DestinationRateArea.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("destinationRateArea") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("destinationRateArea") + } + return err + } + } + + return nil +} + func (m *MTOShipmentWithoutServiceItems) validateDestinationSitAuthEndDate(formats strfmt.Registry) error { if swag.IsZero(m.DestinationSitAuthEndDate) { // not required return nil @@ -625,6 +658,25 @@ func (m *MTOShipmentWithoutServiceItems) validateMoveTaskOrderID(formats strfmt. return nil } +func (m *MTOShipmentWithoutServiceItems) validateOriginRateArea(formats strfmt.Registry) error { + if swag.IsZero(m.OriginRateArea) { // not required + return nil + } + + if m.OriginRateArea != nil { + if err := m.OriginRateArea.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("originRateArea") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("originRateArea") + } + return err + } + } + + return nil +} + func (m *MTOShipmentWithoutServiceItems) validateOriginSitAuthEndDate(formats strfmt.Registry) error { if swag.IsZero(m.OriginSitAuthEndDate) { // not required return nil @@ -1010,6 +1062,10 @@ func (m *MTOShipmentWithoutServiceItems) ContextValidate(ctx context.Context, fo res = append(res, err) } + if err := m.contextValidateDestinationRateArea(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateDestinationType(ctx, formats); err != nil { res = append(res, err) } @@ -1034,6 +1090,10 @@ func (m *MTOShipmentWithoutServiceItems) ContextValidate(ctx context.Context, fo res = append(res, err) } + if err := m.contextValidateOriginRateArea(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidatePickupAddress(ctx, formats); err != nil { res = append(res, err) } @@ -1201,6 +1261,27 @@ func (m *MTOShipmentWithoutServiceItems) contextValidateDestinationAddress(ctx c return nil } +func (m *MTOShipmentWithoutServiceItems) contextValidateDestinationRateArea(ctx context.Context, formats strfmt.Registry) error { + + if m.DestinationRateArea != nil { + + if swag.IsZero(m.DestinationRateArea) { // not required + return nil + } + + if err := m.DestinationRateArea.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("destinationRateArea") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("destinationRateArea") + } + return err + } + } + + return nil +} + func (m *MTOShipmentWithoutServiceItems) contextValidateDestinationType(ctx context.Context, formats strfmt.Registry) error { if m.DestinationType != nil { @@ -1279,6 +1360,27 @@ func (m *MTOShipmentWithoutServiceItems) contextValidateMoveTaskOrderID(ctx cont return nil } +func (m *MTOShipmentWithoutServiceItems) contextValidateOriginRateArea(ctx context.Context, formats strfmt.Registry) error { + + if m.OriginRateArea != nil { + + if swag.IsZero(m.OriginRateArea) { // not required + return nil + } + + if err := m.OriginRateArea.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("originRateArea") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("originRateArea") + } + return err + } + } + + return nil +} + func (m *MTOShipmentWithoutServiceItems) contextValidatePickupAddress(ctx context.Context, formats strfmt.Registry) error { return nil diff --git a/pkg/gen/primev3messages/move_task_order.go b/pkg/gen/primev3messages/move_task_order.go index 4d295abc872..02b991a0a16 100644 --- a/pkg/gen/primev3messages/move_task_order.go +++ b/pkg/gen/primev3messages/move_task_order.go @@ -43,6 +43,16 @@ type MoveTaskOrder struct { // Format: date-time CreatedAt strfmt.DateTime `json:"createdAt,omitempty"` + // destination g b l o c + // Example: KKFA + // Read Only: true + DestinationGBLOC string `json:"destinationGBLOC,omitempty"` + + // destination postal code + // Example: 90210 + // Read Only: true + DestinationPostalCode string `json:"destinationPostalCode,omitempty"` + // e tag // Read Only: true ETag string `json:"eTag,omitempty"` @@ -133,6 +143,10 @@ func (m *MoveTaskOrder) UnmarshalJSON(raw []byte) error { CreatedAt strfmt.DateTime `json:"createdAt,omitempty"` + DestinationGBLOC string `json:"destinationGBLOC,omitempty"` + + DestinationPostalCode string `json:"destinationPostalCode,omitempty"` + ETag string `json:"eTag,omitempty"` ExcessWeightAcknowledgedAt *strfmt.DateTime `json:"excessWeightAcknowledgedAt"` @@ -192,6 +206,12 @@ func (m *MoveTaskOrder) UnmarshalJSON(raw []byte) error { // createdAt result.CreatedAt = data.CreatedAt + // destinationGBLOC + result.DestinationGBLOC = data.DestinationGBLOC + + // destinationPostalCode + result.DestinationPostalCode = data.DestinationPostalCode + // eTag result.ETag = data.ETag @@ -258,6 +278,10 @@ func (m MoveTaskOrder) MarshalJSON() ([]byte, error) { CreatedAt strfmt.DateTime `json:"createdAt,omitempty"` + DestinationGBLOC string `json:"destinationGBLOC,omitempty"` + + DestinationPostalCode string `json:"destinationPostalCode,omitempty"` + ETag string `json:"eTag,omitempty"` ExcessWeightAcknowledgedAt *strfmt.DateTime `json:"excessWeightAcknowledgedAt"` @@ -297,6 +321,10 @@ func (m MoveTaskOrder) MarshalJSON() ([]byte, error) { CreatedAt: m.CreatedAt, + DestinationGBLOC: m.DestinationGBLOC, + + DestinationPostalCode: m.DestinationPostalCode, + ETag: m.ETag, ExcessWeightAcknowledgedAt: m.ExcessWeightAcknowledgedAt, @@ -672,6 +700,14 @@ func (m *MoveTaskOrder) ContextValidate(ctx context.Context, formats strfmt.Regi res = append(res, err) } + if err := m.contextValidateDestinationGBLOC(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateDestinationPostalCode(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateETag(ctx, formats); err != nil { res = append(res, err) } @@ -758,6 +794,24 @@ func (m *MoveTaskOrder) contextValidateCreatedAt(ctx context.Context, formats st return nil } +func (m *MoveTaskOrder) contextValidateDestinationGBLOC(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "destinationGBLOC", "body", string(m.DestinationGBLOC)); err != nil { + return err + } + + return nil +} + +func (m *MoveTaskOrder) contextValidateDestinationPostalCode(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "destinationPostalCode", "body", string(m.DestinationPostalCode)); err != nil { + return err + } + + return nil +} + func (m *MoveTaskOrder) contextValidateETag(ctx context.Context, formats strfmt.Registry) error { if err := validate.ReadOnly(ctx, "eTag", "body", string(m.ETag)); err != nil { diff --git a/pkg/gen/primev3messages/orders_type.go b/pkg/gen/primev3messages/orders_type.go index dfe1b6a549e..ee3a1d78344 100644 --- a/pkg/gen/primev3messages/orders_type.go +++ b/pkg/gen/primev3messages/orders_type.go @@ -53,6 +53,12 @@ const ( // OrdersTypeTEMPORARYDUTY captures enum value "TEMPORARY_DUTY" OrdersTypeTEMPORARYDUTY OrdersType = "TEMPORARY_DUTY" + + // OrdersTypeEARLYRETURNOFDEPENDENTS captures enum value "EARLY_RETURN_OF_DEPENDENTS" + OrdersTypeEARLYRETURNOFDEPENDENTS OrdersType = "EARLY_RETURN_OF_DEPENDENTS" + + // OrdersTypeSTUDENTTRAVEL captures enum value "STUDENT_TRAVEL" + OrdersTypeSTUDENTTRAVEL OrdersType = "STUDENT_TRAVEL" ) // for schema @@ -60,7 +66,7 @@ var ordersTypeEnum []interface{} func init() { var res []OrdersType - if err := json.Unmarshal([]byte(`["PERMANENT_CHANGE_OF_STATION","LOCAL_MOVE","RETIREMENT","SEPARATION","WOUNDED_WARRIOR","BLUEBARK","SAFETY","TEMPORARY_DUTY"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["PERMANENT_CHANGE_OF_STATION","LOCAL_MOVE","RETIREMENT","SEPARATION","WOUNDED_WARRIOR","BLUEBARK","SAFETY","TEMPORARY_DUTY","EARLY_RETURN_OF_DEPENDENTS","STUDENT_TRAVEL"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/primev3messages/p_p_m_shipment.go b/pkg/gen/primev3messages/p_p_m_shipment.go index 89d6cd51c5f..a765ef630c1 100644 --- a/pkg/gen/primev3messages/p_p_m_shipment.go +++ b/pkg/gen/primev3messages/p_p_m_shipment.go @@ -61,6 +61,9 @@ type PPMShipment struct { // Required: true DestinationAddress *PPMDestinationAddress `json:"destinationAddress"` + // destination rate area + DestinationRateArea *RateArea `json:"destinationRateArea,omitempty"` + // A hash unique to this shipment that should be used as the "If-Match" header for any updates. // Required: true // Read Only: true @@ -117,6 +120,9 @@ type PPMShipment struct { // The max amount the government will pay the service member to move their belongings based on the moving date, locations, and shipment weight. MaxIncentive *int64 `json:"maxIncentive"` + // origin rate area + OriginRateArea *RateArea `json:"originRateArea,omitempty"` + // pickup address // Required: true PickupAddress *Address `json:"pickupAddress"` @@ -217,6 +223,10 @@ func (m *PPMShipment) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateDestinationRateArea(formats); err != nil { + res = append(res, err) + } + if err := m.validateETag(formats); err != nil { res = append(res, err) } @@ -229,6 +239,10 @@ func (m *PPMShipment) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateOriginRateArea(formats); err != nil { + res = append(res, err) + } + if err := m.validatePickupAddress(formats); err != nil { res = append(res, err) } @@ -372,6 +386,25 @@ func (m *PPMShipment) validateDestinationAddress(formats strfmt.Registry) error return nil } +func (m *PPMShipment) validateDestinationRateArea(formats strfmt.Registry) error { + if swag.IsZero(m.DestinationRateArea) { // not required + return nil + } + + if m.DestinationRateArea != nil { + if err := m.DestinationRateArea.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("destinationRateArea") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("destinationRateArea") + } + return err + } + } + + return nil +} + func (m *PPMShipment) validateETag(formats strfmt.Registry) error { if err := validate.RequiredString("eTag", "body", m.ETag); err != nil { @@ -407,6 +440,25 @@ func (m *PPMShipment) validateID(formats strfmt.Registry) error { return nil } +func (m *PPMShipment) validateOriginRateArea(formats strfmt.Registry) error { + if swag.IsZero(m.OriginRateArea) { // not required + return nil + } + + if m.OriginRateArea != nil { + if err := m.OriginRateArea.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("originRateArea") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("originRateArea") + } + return err + } + } + + return nil +} + func (m *PPMShipment) validatePickupAddress(formats strfmt.Registry) error { if err := validate.Required("pickupAddress", "body", m.PickupAddress); err != nil { @@ -634,6 +686,10 @@ func (m *PPMShipment) ContextValidate(ctx context.Context, formats strfmt.Regist res = append(res, err) } + if err := m.contextValidateDestinationRateArea(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateETag(ctx, formats); err != nil { res = append(res, err) } @@ -642,6 +698,10 @@ func (m *PPMShipment) ContextValidate(ctx context.Context, formats strfmt.Regist res = append(res, err) } + if err := m.contextValidateOriginRateArea(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidatePickupAddress(ctx, formats); err != nil { res = append(res, err) } @@ -710,6 +770,27 @@ func (m *PPMShipment) contextValidateDestinationAddress(ctx context.Context, for return nil } +func (m *PPMShipment) contextValidateDestinationRateArea(ctx context.Context, formats strfmt.Registry) error { + + if m.DestinationRateArea != nil { + + if swag.IsZero(m.DestinationRateArea) { // not required + return nil + } + + if err := m.DestinationRateArea.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("destinationRateArea") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("destinationRateArea") + } + return err + } + } + + return nil +} + func (m *PPMShipment) contextValidateETag(ctx context.Context, formats strfmt.Registry) error { if err := validate.ReadOnly(ctx, "eTag", "body", string(m.ETag)); err != nil { @@ -728,6 +809,27 @@ func (m *PPMShipment) contextValidateID(ctx context.Context, formats strfmt.Regi return nil } +func (m *PPMShipment) contextValidateOriginRateArea(ctx context.Context, formats strfmt.Registry) error { + + if m.OriginRateArea != nil { + + if swag.IsZero(m.OriginRateArea) { // not required + return nil + } + + if err := m.OriginRateArea.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("originRateArea") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("originRateArea") + } + return err + } + } + + return nil +} + func (m *PPMShipment) contextValidatePickupAddress(ctx context.Context, formats strfmt.Registry) error { if m.PickupAddress != nil { diff --git a/pkg/gen/primev3messages/rate_area.go b/pkg/gen/primev3messages/rate_area.go new file mode 100644 index 00000000000..2d98c400ebb --- /dev/null +++ b/pkg/gen/primev3messages/rate_area.go @@ -0,0 +1,113 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package primev3messages + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// RateArea Rate area info for OCONUS postal code +// +// swagger:model RateArea +type RateArea struct { + + // id + // Example: 1f2270c7-7166-40ae-981e-b200ebdf3054 + // Required: true + // Format: uuid + ID *strfmt.UUID `json:"id"` + + // Rate area code + // Example: US8101000 + // Required: true + RateAreaID *string `json:"rateAreaId"` + + // Rate area name + // Example: Alaska (Zone) I + // Required: true + RateAreaName *string `json:"rateAreaName"` +} + +// Validate validates this rate area +func (m *RateArea) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateRateAreaID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateRateAreaName(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *RateArea) validateID(formats strfmt.Registry) error { + + if err := validate.Required("id", "body", m.ID); err != nil { + return err + } + + if err := validate.FormatOf("id", "body", "uuid", m.ID.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *RateArea) validateRateAreaID(formats strfmt.Registry) error { + + if err := validate.Required("rateAreaId", "body", m.RateAreaID); err != nil { + return err + } + + return nil +} + +func (m *RateArea) validateRateAreaName(formats strfmt.Registry) error { + + if err := validate.Required("rateAreaName", "body", m.RateAreaName); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this rate area based on context it is used +func (m *RateArea) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *RateArea) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *RateArea) UnmarshalBinary(b []byte) error { + var res RateArea + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/gen/primev3messages/shipment_address_update.go b/pkg/gen/primev3messages/shipment_address_update.go index 0ffe7bdd055..f18dd49e3ee 100644 --- a/pkg/gen/primev3messages/shipment_address_update.go +++ b/pkg/gen/primev3messages/shipment_address_update.go @@ -14,7 +14,7 @@ import ( "github.com/go-openapi/validate" ) -// ShipmentAddressUpdate This represents a destination address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO. +// ShipmentAddressUpdate This represents a delivery address change request made by the Prime that is either auto-approved or requires review if the pricing criteria has changed. If criteria has changed, then it must be approved or rejected by a TOO. // // swagger:model ShipmentAddressUpdate type ShipmentAddressUpdate struct { @@ -38,7 +38,7 @@ type ShipmentAddressUpdate struct { // Required: true NewAddress *Address `json:"newAddress"` - // The distance between the original SIT address and requested new destination address of shipment + // The distance between the original SIT address and requested new delivery address of shipment // Example: 88 // Minimum: 0 NewSitDistanceBetween *int64 `json:"newSitDistanceBetween,omitempty"` @@ -49,7 +49,7 @@ type ShipmentAddressUpdate struct { // Example: This is an office remark OfficeRemarks *string `json:"officeRemarks,omitempty"` - // The distance between the original SIT address and the previous/old destination address of shipment + // The distance between the original SIT address and the previous/old delivery address of shipment // Example: 50 // Minimum: 0 OldSitDistanceBetween *int64 `json:"oldSitDistanceBetween,omitempty"` diff --git a/pkg/gen/primev3messages/update_p_p_m_shipment.go b/pkg/gen/primev3messages/update_p_p_m_shipment.go index ae462620c83..097575b0240 100644 --- a/pkg/gen/primev3messages/update_p_p_m_shipment.go +++ b/pkg/gen/primev3messages/update_p_p_m_shipment.go @@ -69,7 +69,7 @@ type UpdatePPMShipment struct { Address } `json:"secondaryDestinationAddress,omitempty"` - // An optional secondary pickup location near the origin where additional goods exist. + // An optional secondary Pickup Address near the origin where additional goods exist. // SecondaryPickupAddress struct { Address @@ -105,7 +105,7 @@ type UpdatePPMShipment struct { Address } `json:"tertiaryDestinationAddress,omitempty"` - // An optional third pickup location near the origin where additional goods exist. + // An optional third Pickup Address near the origin where additional goods exist. // TertiaryPickupAddress struct { Address diff --git a/pkg/gen/primev3messages/update_shipment_destination_address.go b/pkg/gen/primev3messages/update_shipment_destination_address.go index 97415bc99c9..c4d7dd58866 100644 --- a/pkg/gen/primev3messages/update_shipment_destination_address.go +++ b/pkg/gen/primev3messages/update_shipment_destination_address.go @@ -14,7 +14,7 @@ import ( "github.com/go-openapi/validate" ) -// UpdateShipmentDestinationAddress UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the destination address on an MTO Shipment. +// UpdateShipmentDestinationAddress UpdateShipmentDestinationAddress contains the fields required for the prime to request an update for the delivery address on an MTO Shipment. // // swagger:model UpdateShipmentDestinationAddress type UpdateShipmentDestinationAddress struct { diff --git a/pkg/gen/supportapi/embedded_spec.go b/pkg/gen/supportapi/embedded_spec.go index 73ec4c2a19f..31eea1917ec 100644 --- a/pkg/gen/supportapi/embedded_spec.go +++ b/pkg/gen/supportapi/embedded_spec.go @@ -1177,6 +1177,12 @@ func init() { "type": "integer", "x-formatting": "weight", "example": 500 + }, + "unaccompaniedBaggageAllowance": { + "description": "The amount of weight in pounds that the move is entitled for shipment types of Unaccompanied Baggage.", + "type": "integer", + "x-nullable": true, + "example": 3 } } }, @@ -2068,16 +2074,20 @@ func init() { "NTS", "WOUNDED_WARRIOR", "BLUEBARK", - "TEMPORARY_DUTY" + "TEMPORARY_DUTY", + "EARLY_RETURN_OF_DEPENDENTS", + "STUDENT_TRAVEL" ], "x-display-value": { "BLUEBARK": "BLUEBARK", + "EARLY_RETURN_OF_DEPENDENTS": "Early Return of Dependents", "GHC": "GHC", "LOCAL_MOVE": "Local Move", "NTS": "NTS", "PERMANENT_CHANGE_OF_STATION": "Permanent Change Of Station (PCS)", "RETIREMENT": "Retirement", "SEPARATION": "Separation", + "STUDENT_TRAVEL": "Student Travel", "TEMPORARY_DUTY": "Temporary Duty (TDY)", "WOUNDED_WARRIOR": "Wounded Warrior" } @@ -4034,6 +4044,12 @@ func init() { "type": "integer", "x-formatting": "weight", "example": 500 + }, + "unaccompaniedBaggageAllowance": { + "description": "The amount of weight in pounds that the move is entitled for shipment types of Unaccompanied Baggage.", + "type": "integer", + "x-nullable": true, + "example": 3 } } }, @@ -4925,16 +4941,20 @@ func init() { "NTS", "WOUNDED_WARRIOR", "BLUEBARK", - "TEMPORARY_DUTY" + "TEMPORARY_DUTY", + "EARLY_RETURN_OF_DEPENDENTS", + "STUDENT_TRAVEL" ], "x-display-value": { "BLUEBARK": "BLUEBARK", + "EARLY_RETURN_OF_DEPENDENTS": "Early Return of Dependents", "GHC": "GHC", "LOCAL_MOVE": "Local Move", "NTS": "NTS", "PERMANENT_CHANGE_OF_STATION": "Permanent Change Of Station (PCS)", "RETIREMENT": "Retirement", "SEPARATION": "Separation", + "STUDENT_TRAVEL": "Student Travel", "TEMPORARY_DUTY": "Temporary Duty (TDY)", "WOUNDED_WARRIOR": "Wounded Warrior" } diff --git a/pkg/gen/supportmessages/entitlement.go b/pkg/gen/supportmessages/entitlement.go index ead88e4d83f..434ad8aeed3 100644 --- a/pkg/gen/supportmessages/entitlement.go +++ b/pkg/gen/supportmessages/entitlement.go @@ -77,6 +77,10 @@ type Entitlement struct { // total weight // Example: 500 TotalWeight int64 `json:"totalWeight,omitempty"` + + // The amount of weight in pounds that the move is entitled for shipment types of Unaccompanied Baggage. + // Example: 3 + UnaccompaniedBaggageAllowance *int64 `json:"unaccompaniedBaggageAllowance,omitempty"` } // Validate validates this entitlement diff --git a/pkg/gen/supportmessages/orders_type.go b/pkg/gen/supportmessages/orders_type.go index 24fb04b2d53..39dedfe3064 100644 --- a/pkg/gen/supportmessages/orders_type.go +++ b/pkg/gen/supportmessages/orders_type.go @@ -56,6 +56,12 @@ const ( // OrdersTypeTEMPORARYDUTY captures enum value "TEMPORARY_DUTY" OrdersTypeTEMPORARYDUTY OrdersType = "TEMPORARY_DUTY" + + // OrdersTypeEARLYRETURNOFDEPENDENTS captures enum value "EARLY_RETURN_OF_DEPENDENTS" + OrdersTypeEARLYRETURNOFDEPENDENTS OrdersType = "EARLY_RETURN_OF_DEPENDENTS" + + // OrdersTypeSTUDENTTRAVEL captures enum value "STUDENT_TRAVEL" + OrdersTypeSTUDENTTRAVEL OrdersType = "STUDENT_TRAVEL" ) // for schema @@ -63,7 +69,7 @@ var ordersTypeEnum []interface{} func init() { var res []OrdersType - if err := json.Unmarshal([]byte(`["PERMANENT_CHANGE_OF_STATION","LOCAL_MOVE","RETIREMENT","SEPARATION","GHC","NTS","WOUNDED_WARRIOR","BLUEBARK","TEMPORARY_DUTY"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["PERMANENT_CHANGE_OF_STATION","LOCAL_MOVE","RETIREMENT","SEPARATION","GHC","NTS","WOUNDED_WARRIOR","BLUEBARK","TEMPORARY_DUTY","EARLY_RETURN_OF_DEPENDENTS","STUDENT_TRAVEL"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/handlers/adminapi/api.go b/pkg/handlers/adminapi/api.go index 231ded61fe7..89399c145c4 100644 --- a/pkg/handlers/adminapi/api.go +++ b/pkg/handlers/adminapi/api.go @@ -25,6 +25,8 @@ import ( requestedofficeusers "github.com/transcom/mymove/pkg/services/requested_office_users" "github.com/transcom/mymove/pkg/services/roles" signedcertification "github.com/transcom/mymove/pkg/services/signed_certification" + transportationoffice "github.com/transcom/mymove/pkg/services/transportation_office" + transportationofficeassignments "github.com/transcom/mymove/pkg/services/transportation_office_assignments" "github.com/transcom/mymove/pkg/services/upload" user "github.com/transcom/mymove/pkg/services/user" usersprivileges "github.com/transcom/mymove/pkg/services/users_privileges" @@ -81,11 +83,12 @@ func NewAdminAPI(handlerConfig handlers.HandlerConfig) *adminops.MymoveAPI { adminAPI.OfficeUsersGetOfficeUserHandler = GetOfficeUserHandler{ handlerConfig, - officeuser.NewOfficeUserFetcher(queryBuilder), + officeuser.NewOfficeUserFetcherPop(), query.NewQueryFilter, } userPrivilegesCreator := usersprivileges.NewUsersPrivilegesCreator() + transportaionOfficeAssignmentUpdater := transportationofficeassignments.NewTransportaionOfficeAssignmentUpdater() adminAPI.OfficeUsersCreateOfficeUserHandler = CreateOfficeUserHandler{ handlerConfig, officeuser.NewOfficeUserCreator(queryBuilder, handlerConfig.NotificationSender()), @@ -93,6 +96,7 @@ func NewAdminAPI(handlerConfig handlers.HandlerConfig) *adminops.MymoveAPI { userRolesCreator, newRolesFetcher, userPrivilegesCreator, + transportaionOfficeAssignmentUpdater, } adminAPI.OfficeUsersUpdateOfficeUserHandler = UpdateOfficeUserHandler{ @@ -102,6 +106,7 @@ func NewAdminAPI(handlerConfig handlers.HandlerConfig) *adminops.MymoveAPI { userRolesCreator, userPrivilegesCreator, user.NewUserSessionRevocation(queryBuilder), + transportaionOfficeAssignmentUpdater, } adminAPI.TransportationOfficesIndexOfficesHandler = IndexOfficesHandler{ @@ -111,6 +116,13 @@ func NewAdminAPI(handlerConfig handlers.HandlerConfig) *adminops.MymoveAPI { pagination.NewPagination, } + transportationOfficeFetcher := transportationoffice.NewTransportationOfficesFetcher() + adminAPI.TransportationOfficesGetOfficeByIDHandler = GetOfficeByIdHandler{ + handlerConfig, + transportationOfficeFetcher, + query.NewQueryFilter, + } + adminAPI.OrganizationsIndexOrganizationsHandler = IndexOrganizationsHandler{ handlerConfig, organization.NewOrganizationListFetcher(queryBuilder), diff --git a/pkg/handlers/adminapi/office_users.go b/pkg/handlers/adminapi/office_users.go index ed1de4ab02c..5983e514804 100644 --- a/pkg/handlers/adminapi/office_users.go +++ b/pkg/handlers/adminapi/office_users.go @@ -32,13 +32,23 @@ func payloadForRole(r roles.Role) *adminmessages.Role { } } -func payloadForPrivilege(r models.Privilege) *adminmessages.Privilege { +func payloadForPrivilege(p models.Privilege) *adminmessages.Privilege { return &adminmessages.Privilege{ - ID: *handlers.FmtUUID(r.ID), - PrivilegeType: *handlers.FmtString(string(r.PrivilegeType)), - PrivilegeName: *handlers.FmtString(string(r.PrivilegeName)), - CreatedAt: *handlers.FmtDateTime(r.CreatedAt), - UpdatedAt: *handlers.FmtDateTime(r.UpdatedAt), + ID: *handlers.FmtUUID(p.ID), + PrivilegeType: *handlers.FmtString(string(p.PrivilegeType)), + PrivilegeName: *handlers.FmtString(string(p.PrivilegeName)), + CreatedAt: *handlers.FmtDateTime(p.CreatedAt), + UpdatedAt: *handlers.FmtDateTime(p.UpdatedAt), + } +} + +func payloadForTransportationOfficeAssignment(toa models.TransportationOfficeAssignment) *adminmessages.TransportationOfficeAssignment { + return &adminmessages.TransportationOfficeAssignment{ + OfficeUserID: *handlers.FmtUUID(toa.ID), + TransportationOfficeID: *handlers.FmtUUID(toa.TransportationOfficeID), + PrimaryOffice: *handlers.FmtBool(*toa.PrimaryOffice), + CreatedAt: *handlers.FmtDateTime(toa.CreatedAt), + UpdatedAt: *handlers.FmtDateTime(toa.UpdatedAt), } } @@ -89,6 +99,9 @@ func payloadForOfficeUserModel(o models.OfficeUser) *adminmessages.OfficeUser { for _, privilege := range user.Privileges { payload.Privileges = append(payload.Privileges, payloadForPrivilege(privilege)) } + for _, transportationAssignment := range o.TransportationOfficeAssignments { + payload.TransportationOfficeAssignments = append(payload.TransportationOfficeAssignments, payloadForTransportationOfficeAssignment(transportationAssignment)) + } return payload } @@ -155,7 +168,7 @@ func (h IndexOfficeUsersHandler) Handle(params officeuserop.IndexOfficeUsersPara // GetOfficeUserHandler retrieves office user handler type GetOfficeUserHandler struct { handlers.HandlerConfig - services.OfficeUserFetcher + services.OfficeUserFetcherPop services.NewQueryFilter } @@ -164,18 +177,17 @@ func (h GetOfficeUserHandler) Handle(params officeuserop.GetOfficeUserParams) mi return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, func(appCtx appcontext.AppContext) (middleware.Responder, error) { - officeUserID := params.OfficeUserID - - queryFilters := []services.QueryFilter{query.NewQueryFilter("id", "=", officeUserID)} - - officeUser, err := h.OfficeUserFetcher.FetchOfficeUser(appCtx, queryFilters) + officeUserID := uuid.FromStringOrNil(params.OfficeUserID.String()) + officeUser, err := h.OfficeUserFetcherPop.FetchOfficeUserByID(appCtx, officeUserID) if err != nil { return handlers.ResponseForError(appCtx.Logger(), err), err } + userError := appCtx.DB().Load(&officeUser, "User") if userError != nil { return handlers.ResponseForError(appCtx.Logger(), userError), userError } + //todo: we want to move this query out of the handler and into querybuilder, if possible roleError := appCtx.DB().Q().Join("users_roles", "users_roles.role_id = roles.id"). Where("users_roles.deleted_at IS NULL AND users_roles.user_id = ?", (officeUser.User.ID)). @@ -183,12 +195,22 @@ func (h GetOfficeUserHandler) Handle(params officeuserop.GetOfficeUserParams) mi if roleError != nil { return handlers.ResponseForError(appCtx.Logger(), roleError), roleError } + privilegeError := appCtx.DB().Q().Join("users_privileges", "users_privileges.privilege_id = privileges.id"). Where("users_privileges.deleted_at IS NULL AND users_privileges.user_id = ?", (officeUser.User.ID)). All(&officeUser.User.Privileges) if privilegeError != nil { return handlers.ResponseForError(appCtx.Logger(), privilegeError), privilegeError } + + transportationOfficeAssignmentError := appCtx.DB().Q().EagerPreload("TransportationOffice"). + Join("transportation_offices", "transportation_office_assignments.transportation_office_id = transportation_offices.id"). + Where("transportation_office_assignments.id = ?", (officeUser.ID)). + All(&officeUser.TransportationOfficeAssignments) + if transportationOfficeAssignmentError != nil { + return handlers.ResponseForError(appCtx.Logger(), transportationOfficeAssignmentError), transportationOfficeAssignmentError + } + payload := payloadForOfficeUserModel(officeUser) return officeuserop.NewGetOfficeUserOK().WithPayload(payload), nil @@ -203,6 +225,7 @@ type CreateOfficeUserHandler struct { services.UserRoleAssociator services.RoleAssociater services.UserPrivilegeAssociator + services.TransportaionOfficeAssignmentUpdater } // Handle creates an office user @@ -211,9 +234,17 @@ func (h CreateOfficeUserHandler) Handle(params officeuserop.CreateOfficeUserPara return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, func(appCtx appcontext.AppContext) (middleware.Responder, error) { - transportationOfficeID, err := uuid.FromString(payload.TransportationOfficeID.String()) + if len(payload.TransportationOfficeAssignments) == 0 { + err := apperror.NewBadDataError("At least one transportation office is required") + appCtx.Logger().Error(err.Error()) + return officeuserop.NewCreateOfficeUserUnprocessableEntity(), err + } + + primaryTransportationOfficeID, err := getPrimaryTransportationOfficeIDFromPayload(payload.TransportationOfficeAssignments) + if err != nil { - appCtx.Logger().Error(fmt.Sprintf("UUID Parsing for %s", payload.TransportationOfficeID.String()), zap.Error(err)) + appCtx.Logger().Error("Error identifying primary transportation office", zap.Error(err)) + appCtx.Logger().Error(err.Error()) return officeuserop.NewCreateOfficeUserUnprocessableEntity(), err } @@ -238,16 +269,16 @@ func (h CreateOfficeUserHandler) Handle(params officeuserop.CreateOfficeUserPara FirstName: payload.FirstName, Telephone: payload.Telephone, Email: payload.Email, - TransportationOfficeID: transportationOfficeID, + TransportationOfficeID: primaryTransportationOfficeID, Active: true, Status: &officeUserStatus, } - transportationIDFilter := []services.QueryFilter{ - h.NewQueryFilter("id", "=", transportationOfficeID), + primaryTransportationIDFilter := []services.QueryFilter{ + h.NewQueryFilter("id", "=", primaryTransportationOfficeID), } - createdOfficeUser, verrs, err := h.OfficeUserCreator.CreateOfficeUser(appCtx, &officeUser, transportationIDFilter) + createdOfficeUser, verrs, err := h.OfficeUserCreator.CreateOfficeUser(appCtx, &officeUser, primaryTransportationIDFilter) if verrs != nil { validationError := &adminmessages.ValidationError{ InvalidFields: handlers.NewValidationErrorsResponse(verrs).Errors, @@ -297,6 +328,21 @@ func (h CreateOfficeUserHandler) Handle(params officeuserop.CreateOfficeUserPara return officeuserop.NewUpdateOfficeUserInternalServerError(), err } + updatedTransportationOfficeAssignments, err := transportationOfficeAssignmentsPayloadToModel(payload.TransportationOfficeAssignments) + if err != nil { + appCtx.Logger().Error("UUID parsing error for transportation office assignments", zap.Error(err)) + return officeuserop.NewCreateOfficeUserUnprocessableEntity(), err + } + + transportationOfficeAssignments, err := + h.TransportaionOfficeAssignmentUpdater.UpdateTransportaionOfficeAssignments(appCtx, createdOfficeUser.ID, updatedTransportationOfficeAssignments) + if err != nil { + appCtx.Logger().Error("Error updating office user's transportation office assignments", zap.Error(err)) + return officeuserop.NewCreateOfficeUserUnprocessableEntity(), err + } + + createdOfficeUser.TransportationOfficeAssignments = transportationOfficeAssignments + _, err = audit.Capture(appCtx, createdOfficeUser, nil, params.HTTPRequest) if err != nil { appCtx.Logger().Error("Error capturing audit record", zap.Error(err)) @@ -315,6 +361,7 @@ type UpdateOfficeUserHandler struct { services.UserRoleAssociator services.UserPrivilegeAssociator services.UserSessionRevocation + services.TransportaionOfficeAssignmentUpdater } // Handle updates an office user @@ -327,12 +374,23 @@ func (h UpdateOfficeUserHandler) Handle(params officeuserop.UpdateOfficeUserPara appCtx.Logger().Error(fmt.Sprintf("UUID Parsing for %s", params.OfficeUserID.String()), zap.Error(err)) } - updatedOfficeUser, verrs, err := h.OfficeUserUpdater.UpdateOfficeUser(appCtx, officeUserID, payload) + var primaryTransportationOfficeID uuid.UUID + if len(payload.TransportationOfficeAssignments) > 0 { + primaryTransportationOfficeID, err = getPrimaryTransportationOfficeIDFromPayload(payload.TransportationOfficeAssignments) + + if err != nil { + appCtx.Logger().Error("Error identifying primary transportation office", zap.Error(err)) + return officeuserop.NewCreateOfficeUserUnprocessableEntity(), err + } + } + + updatedOfficeUser, verrs, err := h.OfficeUserUpdater.UpdateOfficeUser(appCtx, officeUserID, payload, primaryTransportationOfficeID) if err != nil || verrs != nil { appCtx.Logger().Error("Error saving user", zap.Error(err), zap.Error(verrs)) return officeuserop.NewUpdateOfficeUserInternalServerError(), err } + if updatedOfficeUser.UserID != nil && payload.Roles != nil { updatedRoles := rolesPayloadToModel(payload.Roles) _, verrs, err = h.UserRoleAssociator.UpdateUserRoles(appCtx, *updatedOfficeUser.UserID, updatedRoles) @@ -410,6 +468,48 @@ func (h UpdateOfficeUserHandler) Handle(params officeuserop.UpdateOfficeUserPara } } + if len(payload.TransportationOfficeAssignments) > 0 { + + transportationOfficeAssignmentsFromPayload, err := transportationOfficeAssignmentsPayloadToModel(payload.TransportationOfficeAssignments) + if err != nil { + appCtx.Logger().Error("UUID parsing error for transportation office assignments", zap.Error(err)) + return officeuserop.NewCreateOfficeUserUnprocessableEntity(), err + } + + updatedTransportationOfficeAssignments, err := + h.TransportaionOfficeAssignmentUpdater.UpdateTransportaionOfficeAssignments(appCtx, updatedOfficeUser.ID, transportationOfficeAssignmentsFromPayload) + if err != nil { + appCtx.Logger().Error("Error updating office user's transportation office assignments", zap.Error(err)) + return officeuserop.NewCreateOfficeUserUnprocessableEntity(), err + } + + updatedOfficeUser.TransportationOfficeAssignments = updatedTransportationOfficeAssignments + + boolean := true + revokeOfficeSessionPayload := adminmessages.UserUpdate{ + RevokeOfficeSession: &boolean, + } + + _, validationErrors, revokeErr := h.UserSessionRevocation.RevokeUserSession( + appCtx, + *updatedOfficeUser.UserID, + &revokeOfficeSessionPayload, + h.SessionManagers(), + ) + + if revokeErr != nil { + err = apperror.NewInternalServerError("Error revoking user session") + appCtx.Logger().Error(err.Error(), zap.Error(revokeErr)) + return userop.NewUpdateUserInternalServerError(), revokeErr + } + + if validationErrors != nil { + err = apperror.NewInternalServerError("Error revoking user session") + appCtx.Logger().Error(err.Error(), zap.Error(verrs)) + return userop.NewUpdateUserInternalServerError(), validationErrors + } + } + // Log if the account was enabled or disabled (POAM requirement) if payload.Active != nil { _, err = audit.CaptureAccountStatus(appCtx, updatedOfficeUser, *payload.Active, params.HTTPRequest) @@ -448,3 +548,41 @@ func privilegesPayloadToModel(payload []*adminmessages.OfficeUserPrivilege) []mo } return rt } + +func transportationOfficeAssignmentsPayloadToModel(payload []*adminmessages.OfficeUserTransportationOfficeAssignment) (models.TransportationOfficeAssignments, error) { + var toas models.TransportationOfficeAssignments + for _, toa := range payload { + transportationOfficeID, err := uuid.FromString(toa.TransportationOfficeID.String()) + + if err != nil { + return models.TransportationOfficeAssignments{}, err + } + + model := &models.TransportationOfficeAssignment{ + TransportationOfficeID: transportationOfficeID, + PrimaryOffice: toa.PrimaryOffice, + } + + toas = append(toas, *model) + } + return toas, nil +} + +func getPrimaryTransportationOfficeIDFromPayload(payload []*adminmessages.OfficeUserTransportationOfficeAssignment) (uuid.UUID, error) { + var transportationOfficeID uuid.UUID + var err error + + if len(payload) == 1 { + transportationOfficeID, err = uuid.FromString(payload[0].TransportationOfficeID.String()) + return transportationOfficeID, err + } + + for _, toa := range payload { + if toa.PrimaryOffice != nil && *toa.PrimaryOffice { + transportationOfficeID, err = uuid.FromString(toa.TransportationOfficeID.String()) + return transportationOfficeID, err + } + } + + return transportationOfficeID, apperror.NewBadDataError("Could not identify primary transportaion office from list of assignments") +} diff --git a/pkg/handlers/adminapi/office_users_test.go b/pkg/handlers/adminapi/office_users_test.go index 1e6b479fe48..390a403d51a 100644 --- a/pkg/handlers/adminapi/office_users_test.go +++ b/pkg/handlers/adminapi/office_users_test.go @@ -22,6 +22,7 @@ import ( "github.com/transcom/mymove/pkg/services/pagination" "github.com/transcom/mymove/pkg/services/query" rolesservice "github.com/transcom/mymove/pkg/services/roles" + transportaionofficeassignments "github.com/transcom/mymove/pkg/services/transportation_office_assignments" usersprivileges "github.com/transcom/mymove/pkg/services/users_privileges" usersroles "github.com/transcom/mymove/pkg/services/users_roles" ) @@ -140,10 +141,10 @@ func (suite *HandlerSuite) TestGetOfficeUserHandler() { OfficeUserID: strfmt.UUID(officeUser.ID.String()), } - queryBuilder := query.NewQueryBuilder() + // queryBuilder := query.NewQueryBuilder() handler := GetOfficeUserHandler{ suite.HandlerConfig(), - officeuser.NewOfficeUserFetcher(queryBuilder), + officeuser.NewOfficeUserFetcherPop(), query.NewQueryFilter, } @@ -164,10 +165,10 @@ func (suite *HandlerSuite) TestGetOfficeUserHandler() { OfficeUserID: strfmt.UUID(fakeID), } - queryBuilder := query.NewQueryBuilder() + // queryBuilder := query.NewQueryBuilder() handler := GetOfficeUserHandler{ suite.HandlerConfig(), - officeuser.NewOfficeUserFetcher(queryBuilder), + officeuser.NewOfficeUserFetcherPop(), query.NewQueryFilter, } @@ -183,8 +184,8 @@ func (suite *HandlerSuite) TestCreateOfficeUserHandler() { tooRoleName := "Task Ordering Officer" tooRoleType := string(roles.RoleTypeTOO) - tioRoleName := "Task Invoicing Officer" - tioRoleType := string(roles.RoleTypeTIO) + scRoleName := "Services Counselor" + scRoleType := string(roles.RoleTypeServicesCounselor) supervisorPrivilegeName := "Supervisor" supervisorPrivilegeType := string(models.PrivilegeTypeSupervisor) @@ -194,6 +195,7 @@ func (suite *HandlerSuite) TestCreateOfficeUserHandler() { // Set up: Create a new Office User, save new user to the DB // Expected Outcome: The office user is created and we get a 200 OK. transportationOfficeID := factory.BuildDefaultTransportationOffice(suite.DB()).ID + primaryOffice := true params := officeuserop.CreateOfficeUserParams{ HTTPRequest: suite.setupAuthenticatedRequest("POST", "/office_users"), @@ -208,8 +210,8 @@ func (suite *HandlerSuite) TestCreateOfficeUserHandler() { RoleType: &tooRoleType, }, { - Name: &tioRoleName, - RoleType: &tioRoleType, + Name: &scRoleName, + RoleType: &scRoleType, }, }, Privileges: []*adminmessages.OfficeUserPrivilege{ @@ -218,7 +220,12 @@ func (suite *HandlerSuite) TestCreateOfficeUserHandler() { PrivilegeType: &supervisorPrivilegeType, }, }, - TransportationOfficeID: strfmt.UUID(transportationOfficeID.String()), + TransportationOfficeAssignments: []*adminmessages.OfficeUserTransportationOfficeAssignment{ + { + TransportationOfficeID: strfmt.UUID(transportationOfficeID.String()), + PrimaryOffice: &primaryOffice, + }, + }, }, } queryBuilder := query.NewQueryBuilder() @@ -229,6 +236,7 @@ func (suite *HandlerSuite) TestCreateOfficeUserHandler() { usersroles.NewUsersRolesCreator(), rolesservice.NewRolesFetcher(), usersprivileges.NewUsersPrivilegesCreator(), + transportaionofficeassignments.NewTransportaionOfficeAssignmentUpdater(), } suite.NoError(params.OfficeUser.Validate(strfmt.Default)) response := handler.Handle(params) @@ -240,6 +248,7 @@ func (suite *HandlerSuite) TestCreateOfficeUserHandler() { // Set up: Add new Office User to the DB // Expected Outcome: The office user is not created and we get a 500 internal server error. fakeTransportationOfficeID := "3b9c2975-4e54-40ea-a781-bab7d6e4a502" + primaryOffice := true officeUser := factory.BuildOfficeUser(suite.DB(), nil, []factory.Trait{ factory.GetTraitOfficeUserWithID, }) @@ -256,8 +265,8 @@ func (suite *HandlerSuite) TestCreateOfficeUserHandler() { RoleType: &tooRoleType, }, { - Name: &tioRoleName, - RoleType: &tioRoleType, + Name: &scRoleName, + RoleType: &scRoleName, }, }, Privileges: []*adminmessages.OfficeUserPrivilege{ @@ -266,7 +275,12 @@ func (suite *HandlerSuite) TestCreateOfficeUserHandler() { PrivilegeType: &supervisorPrivilegeType, }, }, - TransportationOfficeID: strfmt.UUID(fakeTransportationOfficeID), + TransportationOfficeAssignments: []*adminmessages.OfficeUserTransportationOfficeAssignment{ + { + TransportationOfficeID: strfmt.UUID(fakeTransportationOfficeID), + PrimaryOffice: &primaryOffice, + }, + }, }, } @@ -278,6 +292,7 @@ func (suite *HandlerSuite) TestCreateOfficeUserHandler() { usersroles.NewUsersRolesCreator(), rolesservice.NewRolesFetcher(), usersprivileges.NewUsersPrivilegesCreator(), + transportaionofficeassignments.NewTransportaionOfficeAssignmentUpdater(), } response := handler.Handle(params) @@ -295,6 +310,7 @@ func (suite *HandlerSuite) TestUpdateOfficeUserHandler() { usersroles.NewUsersRolesCreator(), // a special can of worms, TODO mocked tests usersprivileges.NewUsersPrivilegesCreator(), revoker, + transportaionofficeassignments.NewTransportaionOfficeAssignmentUpdater(), } } @@ -311,15 +327,21 @@ func (suite *HandlerSuite) TestUpdateOfficeUserHandler() { suite.Run("Office user is successfully updated", func() { officeUser := setupTestData() transportationOffice := factory.BuildTransportationOffice(nil, nil, nil) + primaryOffice := true firstName := "Riley" middleInitials := "RB" telephone := "865-555-5309" officeUserUpdates := &adminmessages.OfficeUserUpdate{ - FirstName: &firstName, - MiddleInitials: &middleInitials, - Telephone: &telephone, - TransportationOfficeID: strfmt.UUID(transportationOffice.ID.String()), + FirstName: &firstName, + MiddleInitials: &middleInitials, + Telephone: &telephone, + TransportationOfficeAssignments: []*adminmessages.OfficeUserTransportationOfficeAssignment{ + { + TransportationOfficeID: strfmt.UUID(transportationOffice.ID.String()), + PrimaryOffice: &primaryOffice, + }, + }, } params := officeuserop.UpdateOfficeUserParams{ @@ -338,9 +360,18 @@ func (suite *HandlerSuite) TestUpdateOfficeUserHandler() { expectedOfficeUser.TransportationOfficeID = transportationOffice.ID mockUpdater := mocks.OfficeUserUpdater{} - mockUpdater.On("UpdateOfficeUser", mock.AnythingOfType("*appcontext.appContext"), officeUser.ID, &expectedInput).Return(&expectedOfficeUser, nil, nil) + mockUpdater.On("UpdateOfficeUser", mock.AnythingOfType("*appcontext.appContext"), officeUser.ID, &expectedInput, transportationOffice.ID).Return(&expectedOfficeUser, nil, nil) + + expectedSessionUpdate := &adminmessages.UserUpdate{ + RevokeOfficeSession: models.BoolPointer(true), + } + mockRevoker := mocks.UserSessionRevocation{} + mockRevoker. + On("RevokeUserSession", mock.AnythingOfType("*appcontext.appContext"), *officeUser.UserID, expectedSessionUpdate, mock.Anything). + Return(nil, nil, nil). + Once() - response := setupHandler(&mockUpdater, &mocks.UserSessionRevocation{}).Handle(params) + response := setupHandler(&mockUpdater, &mockRevoker).Handle(params) suite.IsType(&officeuserop.UpdateOfficeUserOK{}, response) okResponse := response.(*officeuserop.UpdateOfficeUserOK) @@ -349,14 +380,21 @@ func (suite *HandlerSuite) TestUpdateOfficeUserHandler() { suite.Equal(middleInitials, *okResponse.Payload.MiddleInitials) suite.Equal(telephone, *okResponse.Payload.Telephone) suite.Equal(transportationOffice.ID.String(), okResponse.Payload.TransportationOfficeID.String()) + suite.Equal(transportationOffice.ID.String(), okResponse.Payload.TransportationOfficeAssignments[0].TransportationOfficeID.String()) suite.Equal(officeUser.LastName, *okResponse.Payload.LastName) // should not have been updated suite.Equal(officeUser.Email, *okResponse.Payload.Email) // should not have been updated }) suite.Run("Update fails due to bad Transportation Office", func() { officeUser := setupTestData() + primaryOffice := true officeUserUpdates := &adminmessages.OfficeUserUpdate{ - TransportationOfficeID: strfmt.UUID(uuid.Must(uuid.NewV4()).String()), + TransportationOfficeAssignments: []*adminmessages.OfficeUserTransportationOfficeAssignment{ + { + TransportationOfficeID: strfmt.UUID(uuid.Must(uuid.NewV4()).String()), + PrimaryOffice: &primaryOffice, + }, + }, } params := officeuserop.UpdateOfficeUserParams{ @@ -368,9 +406,23 @@ func (suite *HandlerSuite) TestUpdateOfficeUserHandler() { expectedInput := *officeUserUpdates mockUpdater := mocks.OfficeUserUpdater{} - mockUpdater.On("UpdateOfficeUser", mock.AnythingOfType("*appcontext.appContext"), officeUser.ID, &expectedInput).Return(nil, nil, sql.ErrNoRows) + mockUpdater.On("UpdateOfficeUser", + mock.AnythingOfType("*appcontext.appContext"), + officeUser.ID, + &expectedInput, + uuid.FromStringOrNil(officeUserUpdates.TransportationOfficeAssignments[0].TransportationOfficeID.String()), + ).Return(nil, nil, sql.ErrNoRows) + + expectedSessionUpdate := &adminmessages.UserUpdate{ + RevokeOfficeSession: models.BoolPointer(true), + } + mockRevoker := mocks.UserSessionRevocation{} + mockRevoker. + On("RevokeUserSession", mock.AnythingOfType("*appcontext.appContext"), *officeUser.UserID, expectedSessionUpdate, mock.Anything). + Return(nil, nil, nil). + Once() - response := setupHandler(&mockUpdater, &mocks.UserSessionRevocation{}).Handle(params) + response := setupHandler(&mockUpdater, &mockRevoker).Handle(params) suite.IsType(&officeuserop.UpdateOfficeUserInternalServerError{}, response) }) @@ -391,7 +443,7 @@ func (suite *HandlerSuite) TestUpdateOfficeUserHandler() { mockUpdater := mocks.OfficeUserUpdater{} mockUpdater. - On("UpdateOfficeUser", mock.AnythingOfType("*appcontext.appContext"), officeUser.ID, officeUserUpdates). + On("UpdateOfficeUser", mock.AnythingOfType("*appcontext.appContext"), officeUser.ID, officeUserUpdates, uuid.Nil). Return(&officeUser, nil, nil) expectedSessionUpdate := &adminmessages.UserUpdate{ diff --git a/pkg/handlers/adminapi/offices.go b/pkg/handlers/adminapi/offices.go index 5ae5e3a6755..fed05ff1b4d 100644 --- a/pkg/handlers/adminapi/offices.go +++ b/pkg/handlers/adminapi/offices.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/go-openapi/runtime/middleware" + "github.com/gofrs/uuid" "github.com/transcom/mymove/pkg/appcontext" transportation_officesop "github.com/transcom/mymove/pkg/gen/adminapi/adminoperations/transportation_offices" @@ -71,3 +72,25 @@ func (h IndexOfficesHandler) Handle(params transportation_officesop.IndexOffices return transportation_officesop.NewIndexOfficesOK().WithContentRange(fmt.Sprintf("offices %d-%d/%d", pagination.Offset(), pagination.Offset()+queriedOfficesCount, totalOfficesCount)).WithPayload(payload), nil }) } + +// GetOfficeByIdHandler returns a single of office via GET /office_users +type GetOfficeByIdHandler struct { + handlers.HandlerConfig + services.TransportationOfficesFetcher + services.NewQueryFilter +} + +// Handle retrieves a list of office users +func (h GetOfficeByIdHandler) Handle(params transportation_officesop.GetOfficeByIDParams) middleware.Responder { + return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, + func(appCtx appcontext.AppContext) (middleware.Responder, error) { + office, err := h.TransportationOfficesFetcher.GetTransportationOffice(appCtx, uuid.FromStringOrNil(params.OfficeID.String()), false) + if err != nil { + return handlers.ResponseForError(appCtx.Logger(), err), err + } + + payload := payloadForOfficeModel(*office) + + return transportation_officesop.NewGetOfficeByIDOK().WithPayload(payload), nil + }) +} diff --git a/pkg/handlers/adminapi/requested_office_users.go b/pkg/handlers/adminapi/requested_office_users.go index 6ec573f207a..ece06237578 100644 --- a/pkg/handlers/adminapi/requested_office_users.go +++ b/pkg/handlers/adminapi/requested_office_users.go @@ -225,8 +225,8 @@ func (h GetRequestedOfficeUserHandler) Handle(params requested_office_users.GetR requestedOfficeUserID := params.OfficeUserID queryFilters := []services.QueryFilter{query.NewQueryFilter("id", "=", requestedOfficeUserID)} - requestedOfficeUser, err := h.RequestedOfficeUserFetcher.FetchRequestedOfficeUser(appCtx, queryFilters) + if err != nil { return handlers.ResponseForError(appCtx.Logger(), err), err } diff --git a/pkg/handlers/adminapi/requested_office_users_test.go b/pkg/handlers/adminapi/requested_office_users_test.go index f0def8d0d89..d84c87ad69c 100644 --- a/pkg/handlers/adminapi/requested_office_users_test.go +++ b/pkg/handlers/adminapi/requested_office_users_test.go @@ -99,11 +99,13 @@ func (suite *HandlerSuite) TestGetRequestedOfficeUserHandler() { HTTPRequest: suite.setupAuthenticatedRequest("GET", fmt.Sprintf("/requested_office_users/%s", requestedOfficeUser.ID)), OfficeUserID: strfmt.UUID(requestedOfficeUser.ID.String()), } + requestedOfficeUserFetcher := &mocks.RequestedOfficeUserFetcher{} requestedOfficeUserFetcher.On("FetchRequestedOfficeUser", mock.AnythingOfType("*appcontext.appContext"), mock.Anything, ).Return(requestedOfficeUser, nil).Once() + mockRoleAssociator := &mocks.RoleAssociater{} mockRoles := roles.Roles{ roles.Role{ @@ -140,6 +142,7 @@ func (suite *HandlerSuite) TestGetRequestedOfficeUserHandler() { HTTPRequest: suite.setupAuthenticatedRequest("GET", fmt.Sprintf("/requested_office_users/%s", requestedOfficeUser.ID)), OfficeUserID: strfmt.UUID(requestedOfficeUser.ID.String()), } + expectedError := models.ErrFetchNotFound requestedOfficeUserFetcher := &mocks.RequestedOfficeUserFetcher{} requestedOfficeUserFetcher.On("FetchRequestedOfficeUser", diff --git a/pkg/handlers/ghcapi/api.go b/pkg/handlers/ghcapi/api.go index 6c6a50aa10e..c0ed5fa3cec 100644 --- a/pkg/handlers/ghcapi/api.go +++ b/pkg/handlers/ghcapi/api.go @@ -40,6 +40,7 @@ import ( "github.com/transcom/mymove/pkg/services/query" reportviolation "github.com/transcom/mymove/pkg/services/report_violation" "github.com/transcom/mymove/pkg/services/roles" + serviceitem "github.com/transcom/mymove/pkg/services/service_item" shipmentaddressupdate "github.com/transcom/mymove/pkg/services/shipment_address_update" shipmentsummaryworksheet "github.com/transcom/mymove/pkg/services/shipment_summary_worksheet" signedcertification "github.com/transcom/mymove/pkg/services/signed_certification" @@ -48,6 +49,7 @@ import ( sitstatus "github.com/transcom/mymove/pkg/services/sit_status" transportationaccountingcode "github.com/transcom/mymove/pkg/services/transportation_accounting_code" transportationoffice "github.com/transcom/mymove/pkg/services/transportation_office" + transportaionofficeassignments "github.com/transcom/mymove/pkg/services/transportation_office_assignments" "github.com/transcom/mymove/pkg/services/upload" usersroles "github.com/transcom/mymove/pkg/services/users_roles" weightticket "github.com/transcom/mymove/pkg/services/weight_ticket" @@ -73,6 +75,7 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { newQueryFilter := query.NewQueryFilter newUserRolesCreator := usersroles.NewUsersRolesCreator() newRolesFetcher := roles.NewRolesFetcher() + newTransportaionOfficeAssignmentUpdater := transportaionofficeassignments.NewTransportaionOfficeAssignmentUpdater() signedCertificationCreator := signedcertification.NewSignedCertificationCreator() signedCertificationUpdater := signedcertification.NewSignedCertificationUpdater() moveTaskOrderUpdater := movetaskorder.NewMoveTaskOrderUpdater( @@ -86,6 +89,8 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { SSWPPMComputer := shipmentsummaryworksheet.NewSSWPPMComputer(ppmCloseoutFetcher) uploadCreator := upload.NewUploadCreator(handlerConfig.FileStorer()) + serviceItemFetcher := serviceitem.NewServiceItemFetcher() + userUploader, err := uploader.NewUserUploader(handlerConfig.FileStorer(), uploader.MaxCustomerUserUploadFileSizeLimit) if err != nil { log.Fatalln(err) @@ -236,7 +241,7 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { ghcAPI.MtoServiceItemUpdateMTOServiceItemStatusHandler = UpdateMTOServiceItemStatusHandler{ HandlerConfig: handlerConfig, - MTOServiceItemUpdater: mtoserviceitem.NewMTOServiceItemUpdater(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, shipmentFetcher, addressCreator), + MTOServiceItemUpdater: mtoserviceitem.NewMTOServiceItemUpdater(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, shipmentFetcher, addressCreator, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer(), ghcrateengine.NewDomesticDestinationSITDeliveryPricer(), ghcrateengine.NewDomesticOriginSITFuelSurchargePricer()), Fetcher: fetch.NewFetcher(queryBuilder), ShipmentSITStatus: sitstatus.NewShipmentSITStatus(), MTOShipmentFetcher: mtoshipment.NewMTOShipmentFetcher(), @@ -507,7 +512,7 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { ghcAPI.ShipmentUpdateSITServiceItemCustomerExpenseHandler = UpdateSITServiceItemCustomerExpenseHandler{ handlerConfig, - mtoserviceitem.NewMTOServiceItemUpdater(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, shipmentFetcher, addressCreator), + mtoserviceitem.NewMTOServiceItemUpdater(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, shipmentFetcher, addressCreator, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer(), ghcrateengine.NewDomesticDestinationSITDeliveryPricer(), ghcrateengine.NewDomesticOriginSITFuelSurchargePricer()), mtoshipment.NewMTOShipmentFetcher(), shipmentSITStatus, } @@ -551,6 +556,7 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { ghcAPI.QueuesGetServicesCounselingOriginListHandler = GetServicesCounselingOriginListHandler{ handlerConfig, order.NewOrderFetcher(), + officeusercreator.NewOfficeUserFetcherPop(), } ghcAPI.TacTacValidationHandler = TacValidationHandler{ @@ -646,6 +652,7 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { newQueryFilter, newUserRolesCreator, newRolesFetcher, + newTransportaionOfficeAssignmentUpdater, } ppmShipmentFetcher := ppmshipment.NewPPMShipmentFetcher() paymentPacketCreator := ppmshipment.NewPaymentPacketCreator(ppmShipmentFetcher, pdfGenerator, AOAPacketCreator) @@ -707,5 +714,10 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { MoveUnlocker: movelocker.NewMoveUnlocker(), } + ghcAPI.ReServiceItemsGetAllReServiceItemsHandler = GetReServiceItemsHandler{ + handlerConfig, + serviceItemFetcher, + } + return ghcAPI } diff --git a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go index 992322feecd..0c108e64937 100644 --- a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go +++ b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go @@ -616,7 +616,7 @@ 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.Entitlement.SetWeightAllotment(string(*order.Grade), order.OrdersType) } entitlements := Entitlement(order.Entitlement) @@ -717,6 +717,22 @@ func Entitlement(entitlement *models.Entitlement) *ghcmessages.Entitlements { } requiredMedicalEquipmentWeight := int64(entitlement.RequiredMedicalEquipmentWeight) gunSafe := entitlement.GunSafe + var accompaniedTour *bool + if entitlement.AccompaniedTour != nil { + accompaniedTour = models.BoolPointer(*entitlement.AccompaniedTour) + } + var dependentsUnderTwelve *int64 + if entitlement.DependentsUnderTwelve != nil { + dependentsUnderTwelve = models.Int64Pointer(int64(*entitlement.DependentsUnderTwelve)) + } + var dependentsTwelveAndOver *int64 + if entitlement.DependentsTwelveAndOver != nil { + dependentsTwelveAndOver = models.Int64Pointer(int64(*entitlement.DependentsTwelveAndOver)) + } + var ubAllowance *int64 + if entitlement.UBAllowance != nil { + ubAllowance = models.Int64Pointer(int64(*entitlement.UBAllowance)) + } return &ghcmessages.Entitlements{ ID: strfmt.UUID(entitlement.ID.String()), AuthorizedWeight: authorizedWeight, @@ -729,6 +745,10 @@ func Entitlement(entitlement *models.Entitlement) *ghcmessages.Entitlements { TotalDependents: totalDependents, TotalWeight: totalWeight, RequiredMedicalEquipmentWeight: requiredMedicalEquipmentWeight, + DependentsUnderTwelve: dependentsUnderTwelve, + DependentsTwelveAndOver: dependentsTwelveAndOver, + AccompaniedTour: accompaniedTour, + UnaccompaniedBaggageAllowance: ubAllowance, OrganizationalClothingAndIndividualEquipment: entitlement.OrganizationalClothingAndIndividualEquipment, GunSafe: gunSafe, ETag: etag.GenerateEtag(entitlement.UpdatedAt), @@ -957,6 +977,7 @@ func PPMShipment(_ storage.FileStorer, ppmShipment *models.PPMShipment) *ghcmess HasTertiaryPickupAddress: ppmShipment.HasTertiaryPickupAddress, HasTertiaryDestinationAddress: ppmShipment.HasTertiaryDestinationAddress, EstimatedWeight: handlers.FmtPoundPtr(ppmShipment.EstimatedWeight), + AllowableWeight: handlers.FmtPoundPtr(ppmShipment.AllowableWeight), HasProGear: ppmShipment.HasProGear, ProGearWeight: handlers.FmtPoundPtr(ppmShipment.ProGearWeight), SpouseProGearWeight: handlers.FmtPoundPtr(ppmShipment.SpouseProGearWeight), @@ -1224,7 +1245,6 @@ func WeightTicket(storer storage.FileStorer, weightTicket *models.WeightTicket) ProofOfTrailerOwnershipDocumentID: *handlers.FmtUUID(weightTicket.ProofOfTrailerOwnershipDocumentID), ProofOfTrailerOwnershipDocument: proofOfTrailerOwnershipDocument, AdjustedNetWeight: handlers.FmtPoundPtr(weightTicket.AdjustedNetWeight), - AllowableWeight: handlers.FmtPoundPtr(weightTicket.AllowableWeight), NetWeightRemarks: weightTicket.NetWeightRemarks, ETag: etag.GenerateEtag(weightTicket.UpdatedAt), } @@ -2121,25 +2141,22 @@ func QueueAvailableOfficeUsers(officeUsers []models.OfficeUser) *ghcmessages.Ava availableOfficeUsers := make(ghcmessages.AvailableOfficeUsers, len(officeUsers)) for i, officeUser := range officeUsers { - hasSafety := officeUser.User.Privileges.HasPrivilege(models.PrivilegeTypeSafety) - availableOfficeUsers[i] = &ghcmessages.AvailableOfficeUser{ - LastName: officeUser.LastName, - FirstName: officeUser.FirstName, - OfficeUserID: *handlers.FmtUUID(officeUser.ID), - HasSafetyPrivilege: swag.BoolValue(&hasSafety), + LastName: officeUser.LastName, + FirstName: officeUser.FirstName, + OfficeUserID: *handlers.FmtUUID(officeUser.ID), } } return &availableOfficeUsers } -func queueMoveIsAssignable(move models.Move, assignedToUser *ghcmessages.AssignedOfficeUser, isCloseoutQueue bool, role roles.RoleType, officeUser models.OfficeUser, isSupervisor bool, isHQRole bool, ppmCloseoutGblocs bool) bool { +func queueMoveIsAssignable(move models.Move, assignedToUser *ghcmessages.AssignedOfficeUser, isCloseoutQueue bool, officeUser models.OfficeUser, ppmCloseoutGblocs bool) bool { // default to false isAssignable := false // HQ role is read only - if isHQRole { + if officeUser.User.Roles.HasRole(roles.RoleTypeHQ) { isAssignable = false return isAssignable } @@ -2149,14 +2166,15 @@ func queueMoveIsAssignable(move models.Move, assignedToUser *ghcmessages.Assigne isAssignable = true } + isSupervisor := officeUser.User.Privileges.HasPrivilege(models.PrivilegeTypeSupervisor) // in TOO queues, all moves are assignable for supervisor users - if role == roles.RoleTypeTOO && isSupervisor { + if officeUser.User.Roles.HasRole(roles.RoleTypeTOO) && isSupervisor { isAssignable = true } // if it is assigned in the SCs queue // it is only assignable if the user is a supervisor... - if role == roles.RoleTypeServicesCounselor && isSupervisor { + if officeUser.User.Roles.HasRole(roles.RoleTypeServicesCounselor) && isSupervisor { // AND we are in the counseling queue AND the move's counseling office is the supervisor's transportation office if !isCloseoutQueue && move.CounselingOfficeID != nil && *move.CounselingOfficeID == officeUser.TransportationOfficeID { isAssignable = true @@ -2175,8 +2193,8 @@ func queueMoveIsAssignable(move models.Move, assignedToUser *ghcmessages.Assigne return isAssignable } -func servicesCounselorAvailableOfficeUsers(move models.Move, officeUsers []models.OfficeUser, role roles.RoleType, officeUser models.OfficeUser, ppmCloseoutGblocs bool, isCloseoutQueue bool) []models.OfficeUser { - if role == roles.RoleTypeServicesCounselor { +func servicesCounselorAvailableOfficeUsers(move models.Move, officeUsers []models.OfficeUser, officeUser models.OfficeUser, ppmCloseoutGblocs bool, isCloseoutQueue bool) []models.OfficeUser { + if officeUser.User.Roles.HasRole(roles.RoleTypeServicesCounselor) { // if the office user currently assigned to the move works outside of the logged in users counseling office // add them to the set if move.SCAssignedUser != nil && move.SCAssignedUser.TransportationOfficeID != officeUser.TransportationOfficeID { @@ -2204,7 +2222,7 @@ func servicesCounselorAvailableOfficeUsers(move models.Move, officeUsers []model } // QueueMoves payload -func QueueMoves(moves []models.Move, officeUsers []models.OfficeUser, requestedPpmStatus *models.PPMShipmentStatus, role roles.RoleType, officeUser models.OfficeUser, isSupervisor bool, isHQRole bool) *ghcmessages.QueueMoves { +func QueueMoves(moves []models.Move, officeUsers []models.OfficeUser, requestedPpmStatus *models.PPMShipmentStatus, officeUser models.OfficeUser, officeUsersSafety []models.OfficeUser) *ghcmessages.QueueMoves { queueMoves := make(ghcmessages.QueueMoves, len(moves)) for i, move := range moves { customer := move.Orders.ServiceMember @@ -2234,20 +2252,21 @@ func QueueMoves(moves []models.Move, officeUsers []models.OfficeUser, requestedP deptIndicator = ghcmessages.DeptIndicator(*move.Orders.DepartmentIndicator) } - var gbloc string + var originGbloc string if move.Status == models.MoveStatusNeedsServiceCounseling { - gbloc = swag.StringValue(move.Orders.OriginDutyLocationGBLOC) + originGbloc = swag.StringValue(move.Orders.OriginDutyLocationGBLOC) } else if len(move.ShipmentGBLOC) > 0 && move.ShipmentGBLOC[0].GBLOC != nil { // There is a Pop bug that prevents us from using a has_one association for // Move.ShipmentGBLOC, so we have to treat move.ShipmentGBLOC as an array, even // though there can never be more than one GBLOC for a move. - gbloc = swag.StringValue(move.ShipmentGBLOC[0].GBLOC) + originGbloc = swag.StringValue(move.ShipmentGBLOC[0].GBLOC) } else { // If the move's first shipment doesn't have a pickup address (like with an NTS-Release), // we need to fall back to the origin duty location GBLOC. If that's not available for // some reason, then we should get the empty string (no GBLOC). - gbloc = swag.StringValue(move.Orders.OriginDutyLocationGBLOC) + originGbloc = swag.StringValue(move.Orders.OriginDutyLocationGBLOC) } + var closeoutLocation string if move.CloseoutOffice != nil { closeoutLocation = move.CloseoutOffice.Name @@ -2275,10 +2294,10 @@ func QueueMoves(moves []models.Move, officeUsers []models.OfficeUser, requestedP // determine if there is an assigned user var assignedToUser *ghcmessages.AssignedOfficeUser - if role == roles.RoleTypeServicesCounselor && move.SCAssignedUser != nil { + if officeUser.User.Roles.HasRole(roles.RoleTypeServicesCounselor) && move.SCAssignedUser != nil { assignedToUser = AssignedOfficeUser(move.SCAssignedUser) } - if role == roles.RoleTypeTOO && move.TOOAssignedUser != nil { + if officeUser.User.Roles.HasRole(roles.RoleTypeTOO) && move.TOOAssignedUser != nil { assignedToUser = AssignedOfficeUser(move.TOOAssignedUser) } @@ -2287,16 +2306,20 @@ func QueueMoves(moves []models.Move, officeUsers []models.OfficeUser, requestedP // requestedPpmStatus also represents if we are viewing the closeout queue isCloseoutQueue := requestedPpmStatus != nil && *requestedPpmStatus == models.PPMShipmentStatusNeedsCloseout // determine if the move is assignable - assignable := queueMoveIsAssignable(move, assignedToUser, isCloseoutQueue, role, officeUser, isSupervisor, isHQRole, ppmCloseoutGblocs) + assignable := queueMoveIsAssignable(move, assignedToUser, isCloseoutQueue, officeUser, ppmCloseoutGblocs) + isSupervisor := officeUser.User.Privileges.HasPrivilege(models.PrivilegeTypeSupervisor) // only need to attach available office users if move is assignable var apiAvailableOfficeUsers ghcmessages.AvailableOfficeUsers if assignable { // non SC roles don't need the extra logic, just make availableOfficeUsers = officeUsers availableOfficeUsers := officeUsers - if role == roles.RoleTypeServicesCounselor { - availableOfficeUsers = servicesCounselorAvailableOfficeUsers(move, availableOfficeUsers, role, officeUser, ppmCloseoutGblocs, isCloseoutQueue) + if isSupervisor && move.Orders.OrdersType == "SAFETY" { + availableOfficeUsers = officeUsersSafety + } + if officeUser.User.Roles.HasRole(roles.RoleTypeServicesCounselor) { + availableOfficeUsers = servicesCounselorAvailableOfficeUsers(move, availableOfficeUsers, officeUser, ppmCloseoutGblocs, isCloseoutQueue) } apiAvailableOfficeUsers = *QueueAvailableOfficeUsers(availableOfficeUsers) @@ -2314,7 +2337,7 @@ func QueueMoves(moves []models.Move, officeUsers []models.OfficeUser, requestedP ShipmentsCount: int64(len(validMTOShipments)), OriginDutyLocation: DutyLocation(move.Orders.OriginDutyLocation), DestinationDutyLocation: DutyLocation(&move.Orders.NewDutyLocation), // #nosec G601 new in 1.22.2 - OriginGBLOC: ghcmessages.GBLOC(gbloc), + OriginGBLOC: ghcmessages.GBLOC(originGbloc), PpmType: move.PPMType, CloseoutInitiated: handlers.FmtDateTimePtr(&closeoutInitiated), CloseoutLocation: &closeoutLocation, @@ -2396,7 +2419,7 @@ func queuePaymentRequestStatus(paymentRequest models.PaymentRequest) string { } // QueuePaymentRequests payload -func QueuePaymentRequests(paymentRequests *models.PaymentRequests, officeUsers []models.OfficeUser, officeUser models.OfficeUser, isSupervisor bool, isHQRole bool) *ghcmessages.QueuePaymentRequests { +func QueuePaymentRequests(paymentRequests *models.PaymentRequests, officeUsers []models.OfficeUser, officeUser models.OfficeUser, officeUsersSafety []models.OfficeUser) *ghcmessages.QueuePaymentRequests { queuePaymentRequests := make(ghcmessages.QueuePaymentRequests, len(*paymentRequests)) @@ -2436,11 +2459,12 @@ func QueuePaymentRequests(paymentRequests *models.PaymentRequests, officeUsers [ isAssignable = true } + isSupervisor := officeUser.User.Privileges.HasPrivilege(models.PrivilegeTypeSupervisor) if isSupervisor { isAssignable = true } - if isHQRole { + if officeUser.User.Roles.HasRole(roles.RoleTypeHQ) { isAssignable = false } @@ -2449,6 +2473,9 @@ func QueuePaymentRequests(paymentRequests *models.PaymentRequests, officeUsers [ // only need to attach available office users if move is assignable if queuePaymentRequests[i].Assignable { availableOfficeUsers := officeUsers + if isSupervisor && orders.OrdersType == "SAFETY" { + availableOfficeUsers = officeUsersSafety + } if !isSupervisor { availableOfficeUsers = models.OfficeUsers{officeUser} } @@ -2526,43 +2553,44 @@ func SearchMoves(appCtx appcontext.AppContext, moves models.Moves) *ghcmessages. originGBLOC = swag.StringValue(move.Orders.OriginDutyLocationGBLOC) } - var destinationGBLOC ghcmessages.GBLOC - var PostalCodeToGBLOC models.PostalCodeToGBLOC + // populates the destination gbloc of the move + var destinationGBLOC string var err error - if numShipments > 0 && move.MTOShipments[0].DestinationAddress != nil { - PostalCodeToGBLOC, err = models.FetchGBLOCForPostalCode(appCtx.DB(), move.MTOShipments[0].DestinationAddress.PostalCode) - } else { - // If the move has no shipments or the shipment has no destination address fall back to the origin duty location GBLOC - PostalCodeToGBLOC, err = models.FetchGBLOCForPostalCode(appCtx.DB(), move.Orders.NewDutyLocation.Address.PostalCode) + destinationGBLOC, err = move.GetDestinationGBLOC(appCtx.DB()) + if err != nil { + destinationGBLOC = "" } + if len(destinationGBLOC) > 0 && customer.Affiliation.String() == "MARINES" { + destinationGBLOC = "USMC/" + destinationGBLOC + } + destinationGblocMessage := ghcmessages.GBLOC(destinationGBLOC) + // populates the destination postal code of the move + var destinationPostalCode string + destinationPostalCode, err = move.GetDestinationPostalCode(appCtx.DB()) if err != nil { - destinationGBLOC = *ghcmessages.NewGBLOC("") - } else if customer.Affiliation.String() == "MARINES" { - destinationGBLOC = ghcmessages.GBLOC("USMC/" + PostalCodeToGBLOC.GBLOC) - } else { - destinationGBLOC = ghcmessages.GBLOC(PostalCodeToGBLOC.GBLOC) + destinationPostalCode = "" } searchMoves[i] = &ghcmessages.SearchMove{ - FirstName: customer.FirstName, - LastName: customer.LastName, - Edipi: customer.Edipi, - Emplid: customer.Emplid, - Branch: customer.Affiliation.String(), - Status: ghcmessages.MoveStatus(move.Status), - ID: *handlers.FmtUUID(move.ID), - Locator: move.Locator, - ShipmentsCount: int64(numShipments), - OriginDutyLocationPostalCode: move.Orders.OriginDutyLocation.Address.PostalCode, - DestinationDutyLocationPostalCode: move.Orders.NewDutyLocation.Address.PostalCode, - OrderType: string(move.Orders.OrdersType), - RequestedPickupDate: pickupDate, - RequestedDeliveryDate: deliveryDate, - OriginGBLOC: ghcmessages.GBLOC(originGBLOC), - DestinationGBLOC: destinationGBLOC, - LockedByOfficeUserID: handlers.FmtUUIDPtr(move.LockedByOfficeUserID), - LockExpiresAt: handlers.FmtDateTimePtr(move.LockExpiresAt), + FirstName: customer.FirstName, + LastName: customer.LastName, + Edipi: customer.Edipi, + Emplid: customer.Emplid, + Branch: customer.Affiliation.String(), + Status: ghcmessages.MoveStatus(move.Status), + ID: *handlers.FmtUUID(move.ID), + Locator: move.Locator, + ShipmentsCount: int64(numShipments), + OriginDutyLocationPostalCode: move.Orders.OriginDutyLocation.Address.PostalCode, + DestinationPostalCode: destinationPostalCode, + OrderType: string(move.Orders.OrdersType), + RequestedPickupDate: pickupDate, + RequestedDeliveryDate: deliveryDate, + OriginGBLOC: ghcmessages.GBLOC(originGBLOC), + DestinationGBLOC: destinationGblocMessage, + LockedByOfficeUserID: handlers.FmtUUIDPtr(move.LockedByOfficeUserID), + LockExpiresAt: handlers.FmtDateTimePtr(move.LockExpiresAt), } } return &searchMoves @@ -2620,3 +2648,27 @@ func SearchCustomers(customers models.ServiceMemberSearchResults) *ghcmessages.S } return &searchCustomers } + +// ReServiceItem payload +func ReServiceItem(reServiceItem *models.ReServiceItem) *ghcmessages.ReServiceItem { + if reServiceItem == nil || *reServiceItem == (models.ReServiceItem{}) { + return nil + } + return &ghcmessages.ReServiceItem{ + IsAutoApproved: reServiceItem.IsAutoApproved, + MarketCode: string(reServiceItem.MarketCode), + ServiceCode: string(reServiceItem.ReService.Code), + ShipmentType: string(reServiceItem.ShipmentType), + ServiceName: reServiceItem.ReService.Name, + } +} + +// ReServiceItems payload +func ReServiceItems(reServiceItems models.ReServiceItems) ghcmessages.ReServiceItems { + payload := make(ghcmessages.ReServiceItems, len(reServiceItems)) + for i, reServiceItem := range reServiceItems { + copyOfReServiceItem := reServiceItem + payload[i] = ReServiceItem(©OfReServiceItem) + } + return payload +} diff --git a/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go b/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go index c779256f33e..b46c271c85b 100644 --- a/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go +++ b/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go @@ -29,7 +29,27 @@ func TestMove(t *testing.T) { } func (suite *PayloadsSuite) TestPaymentRequestQueue() { - officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + officeUser := factory.BuildOfficeUserWithPrivileges(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + Email: "officeuser1@example.com", + }, + }, + { + Model: models.User{ + Privileges: []models.Privilege{ + { + PrivilegeType: models.PrivilegeTypeSupervisor, + }, + }, + Roles: []roles.Role{ + { + RoleType: roles.RoleTypeTIO, + }, + }, + }, + }, + }, nil) officeUserTIO := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTIO}) gbloc := "LKNQ" @@ -66,8 +86,9 @@ func (suite *PayloadsSuite) TestPaymentRequestQueue() { }, }, nil) var officeUsers models.OfficeUsers + var officeUsersSafety models.OfficeUsers officeUsers = append(officeUsers, officeUser) - var paymentRequestsQueue = QueuePaymentRequests(&paymentRequests, officeUsers, officeUser, false, false) + var paymentRequestsQueue = QueuePaymentRequests(&paymentRequests, officeUsers, officeUser, officeUsersSafety) suite.Run("Test Payment request is assignable due to not being assigend", func() { paymentRequestCopy := *paymentRequestsQueue @@ -86,7 +107,7 @@ func (suite *PayloadsSuite) TestPaymentRequestQueue() { paymentRequests[0].MoveTaskOrder.TIOAssignedUser = &officeUserTIO paymentRequests[0].MoveTaskOrder.CounselingOffice = &transportationOffice - paymentRequestsQueue = QueuePaymentRequests(&paymentRequests, officeUsers, officeUser, false, false) + paymentRequestsQueue = QueuePaymentRequests(&paymentRequests, officeUsers, officeUser, officeUsersSafety) suite.Run("Test PaymentRequest has both Counseling Office and TIO AssignedUser ", func() { PaymentRequestsCopy := *paymentRequestsQueue @@ -100,13 +121,14 @@ func (suite *PayloadsSuite) TestPaymentRequestQueue() { }) suite.Run("Test PaymentRequest is assignable due to user Supervisor role", func() { - paymentRequests := QueuePaymentRequests(&paymentRequests, officeUsers, officeUser, true, false) + paymentRequests := QueuePaymentRequests(&paymentRequests, officeUsers, officeUser, officeUsersSafety) paymentRequestCopy := *paymentRequests suite.Equal(paymentRequestCopy[0].Assignable, true) }) + officeUserHQ := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeHQ}) suite.Run("Test PaymentRequest is not assignable due to user HQ role", func() { - paymentRequests := QueuePaymentRequests(&paymentRequests, officeUsers, officeUser, false, true) + paymentRequests := QueuePaymentRequests(&paymentRequests, officeUsers, officeUserHQ, officeUsersSafety) paymentRequestCopy := *paymentRequests suite.Equal(paymentRequestCopy[0].Assignable, false) }) @@ -410,6 +432,61 @@ func (suite *PayloadsSuite) TestCustomer() { }) } +func (suite *PayloadsSuite) TestEntitlement() { + entitlementID, _ := uuid.NewV4() + dependentsAuthorized := true + nonTemporaryStorage := true + privatelyOwnedVehicle := true + proGearWeight := 1000 + proGearWeightSpouse := 500 + storageInTransit := 90 + totalDependents := 2 + requiredMedicalEquipmentWeight := 200 + accompaniedTour := true + dependentsUnderTwelve := 1 + dependentsTwelveAndOver := 1 + authorizedWeight := 8000 + ubAllowance := 300 + + entitlement := &models.Entitlement{ + ID: entitlementID, + DBAuthorizedWeight: &authorizedWeight, + DependentsAuthorized: &dependentsAuthorized, + NonTemporaryStorage: &nonTemporaryStorage, + PrivatelyOwnedVehicle: &privatelyOwnedVehicle, + ProGearWeight: proGearWeight, + ProGearWeightSpouse: proGearWeightSpouse, + StorageInTransit: &storageInTransit, + TotalDependents: &totalDependents, + RequiredMedicalEquipmentWeight: requiredMedicalEquipmentWeight, + AccompaniedTour: &accompaniedTour, + DependentsUnderTwelve: &dependentsUnderTwelve, + DependentsTwelveAndOver: &dependentsTwelveAndOver, + UpdatedAt: time.Now(), + UBAllowance: &ubAllowance, + } + + returnedEntitlement := Entitlement(entitlement) + returnedUBAllowance := entitlement.UBAllowance + + suite.IsType(&ghcmessages.Entitlements{}, returnedEntitlement) + + suite.Equal(strfmt.UUID(entitlementID.String()), returnedEntitlement.ID) + suite.Equal(authorizedWeight, int(*returnedEntitlement.AuthorizedWeight)) + suite.Equal(entitlement.DependentsAuthorized, returnedEntitlement.DependentsAuthorized) + suite.Equal(entitlement.NonTemporaryStorage, returnedEntitlement.NonTemporaryStorage) + suite.Equal(entitlement.PrivatelyOwnedVehicle, returnedEntitlement.PrivatelyOwnedVehicle) + suite.Equal(int(*returnedUBAllowance), int(*returnedEntitlement.UnaccompaniedBaggageAllowance)) + suite.Equal(int64(proGearWeight), returnedEntitlement.ProGearWeight) + suite.Equal(int64(proGearWeightSpouse), returnedEntitlement.ProGearWeightSpouse) + suite.Equal(storageInTransit, int(*returnedEntitlement.StorageInTransit)) + suite.Equal(totalDependents, int(returnedEntitlement.TotalDependents)) + suite.Equal(int64(requiredMedicalEquipmentWeight), returnedEntitlement.RequiredMedicalEquipmentWeight) + suite.Equal(models.BoolPointer(accompaniedTour), returnedEntitlement.AccompaniedTour) + suite.Equal(dependentsUnderTwelve, int(*returnedEntitlement.DependentsUnderTwelve)) + suite.Equal(dependentsTwelveAndOver, int(*returnedEntitlement.DependentsTwelveAndOver)) +} + func (suite *PayloadsSuite) TestCreateCustomer() { id, _ := uuid.NewV4() id2, _ := uuid.NewV4() @@ -518,6 +595,94 @@ func (suite *PayloadsSuite) TestMarketCode() { }) } +func (suite *PayloadsSuite) TestReServiceItem() { + suite.Run("returns nil when reServiceItem is nil", func() { + var reServiceItem *models.ReServiceItem = nil + result := ReServiceItem(reServiceItem) + suite.Nil(result, "Expected result to be nil when reServiceItem is nil") + }) + + suite.Run("correctly maps ReServiceItem with all fields populated", func() { + isAutoApproved := true + marketCodeInternational := models.MarketCodeInternational + reServiceCode := models.ReServiceCodePOEFSC + poefscServiceName := "International POE Fuel Surcharge" + reService := models.ReService{ + Code: reServiceCode, + Name: poefscServiceName, + } + ubShipmentType := models.MTOShipmentTypeUnaccompaniedBaggage + reServiceItem := &models.ReServiceItem{ + IsAutoApproved: isAutoApproved, + MarketCode: marketCodeInternational, + ReService: reService, + ShipmentType: ubShipmentType, + } + result := ReServiceItem(reServiceItem) + + suite.NotNil(result, "Expected result to not be nil when reServiceItem has values") + suite.Equal(isAutoApproved, result.IsAutoApproved, "Expected IsAutoApproved to match") + suite.True(result.IsAutoApproved, "Expected IsAutoApproved to be true") + suite.Equal(string(marketCodeInternational), result.MarketCode, "Expected MarketCode to match") + suite.Equal(string(reServiceItem.ReService.Code), result.ServiceCode, "Expected ServiceCode to match") + suite.Equal(string(reServiceItem.ReService.Name), result.ServiceName, "Expected ServiceName to match") + suite.Equal(string(ubShipmentType), result.ShipmentType, "Expected ShipmentType to match") + }) +} + +func (suite *PayloadsSuite) TestReServiceItems() { + suite.Run("Correctly maps ReServiceItems with all fields populated", func() { + isAutoApprovedTrue := true + isAutoApprovedFalse := false + marketCodeInternational := models.MarketCodeInternational + marketCodeDomestic := models.MarketCodeDomestic + poefscReServiceCode := models.ReServiceCodePOEFSC + poedscReServiceCode := models.ReServiceCodePODFSC + poefscServiceName := "International POE Fuel Surcharge" + poedscServiceName := "International POD Fuel Surcharge" + poefscService := models.ReService{ + Code: poefscReServiceCode, + Name: poefscServiceName, + } + podfscService := models.ReService{ + Code: poedscReServiceCode, + Name: poedscServiceName, + } + hhgShipmentType := models.MTOShipmentTypeHHG + ubShipmentType := models.MTOShipmentTypeUnaccompaniedBaggage + poefscServiceItem := models.ReServiceItem{ + IsAutoApproved: isAutoApprovedTrue, + MarketCode: marketCodeInternational, + ReService: poefscService, + ShipmentType: ubShipmentType, + } + podfscServiceItem := models.ReServiceItem{ + IsAutoApproved: isAutoApprovedFalse, + MarketCode: marketCodeDomestic, + ReService: podfscService, + ShipmentType: hhgShipmentType, + } + reServiceItems := make(models.ReServiceItems, 2) + reServiceItems[0] = poefscServiceItem + reServiceItems[1] = podfscServiceItem + result := ReServiceItems(reServiceItems) + + suite.NotNil(result, "Expected result to not be nil when reServiceItems has values") + suite.Equal(poefscServiceItem.IsAutoApproved, result[0].IsAutoApproved, "Expected IsAutoApproved to match") + suite.True(result[0].IsAutoApproved, "Expected IsAutoApproved to be true") + suite.Equal(string(marketCodeInternational), result[0].MarketCode, "Expected MarketCode to match") + suite.Equal(string(poefscServiceItem.ReService.Code), result[0].ServiceCode, "Expected ServiceCode to match") + suite.Equal(string(poefscServiceItem.ReService.Name), result[0].ServiceName, "Expected ServiceName to match") + suite.Equal(string(ubShipmentType), result[0].ShipmentType, "Expected ShipmentType to match") + suite.Equal(podfscServiceItem.IsAutoApproved, result[1].IsAutoApproved, "Expected IsAutoApproved to match") + suite.False(result[1].IsAutoApproved, "Expected IsAutoApproved to be false") + suite.Equal(string(marketCodeDomestic), result[1].MarketCode, "Expected MarketCode to match") + suite.Equal(string(podfscServiceItem.ReService.Code), result[1].ServiceCode, "Expected ServiceCode to match") + suite.Equal(string(podfscServiceItem.ReService.Name), result[1].ServiceName, "Expected ServiceName to match") + suite.Equal(string(hhgShipmentType), result[1].ShipmentType, "Expected ShipmentType to match") + }) +} + func (suite *PayloadsSuite) TestGsrAppeal() { officeUser := factory.BuildOfficeUser(suite.DB(), nil, nil) diff --git a/pkg/handlers/ghcapi/internal/payloads/payload_to_model.go b/pkg/handlers/ghcapi/internal/payloads/payload_to_model.go index dc9f8062b5e..04dc9719f16 100644 --- a/pkg/handlers/ghcapi/internal/payloads/payload_to_model.go +++ b/pkg/handlers/ghcapi/internal/payloads/payload_to_model.go @@ -624,6 +624,7 @@ func PPMShipmentModelFromUpdate(ppmShipment *ghcmessages.UpdatePPMShipment) *mod ActualMoveDate: (*time.Time)(ppmShipment.ActualMoveDate), SITExpected: ppmShipment.SitExpected, EstimatedWeight: handlers.PoundPtrFromInt64Ptr(ppmShipment.EstimatedWeight), + AllowableWeight: handlers.PoundPtrFromInt64Ptr(ppmShipment.AllowableWeight), HasProGear: ppmShipment.HasProGear, IsActualExpenseReimbursement: ppmShipment.IsActualExpenseReimbursement, ProGearWeight: handlers.PoundPtrFromInt64Ptr(ppmShipment.ProGearWeight), @@ -831,7 +832,6 @@ func WeightTicketModelFromUpdate(weightTicket *ghcmessages.UpdateWeightTicket) * Reason: handlers.FmtString(weightTicket.Reason), AdjustedNetWeight: handlers.PoundPtrFromInt64Ptr(weightTicket.AdjustedNetWeight), NetWeightRemarks: handlers.FmtString(weightTicket.NetWeightRemarks), - AllowableWeight: handlers.PoundPtrFromInt64Ptr(weightTicket.AllowableWeight), } return model } diff --git a/pkg/handlers/ghcapi/move_test.go b/pkg/handlers/ghcapi/move_test.go index f345c7cfb85..3b9fda47401 100644 --- a/pkg/handlers/ghcapi/move_test.go +++ b/pkg/handlers/ghcapi/move_test.go @@ -290,6 +290,174 @@ func (suite *HandlerSuite) TestSearchMovesHandler() { } + /* setupGblocTestData is a helper function to set up test data for the search moves handler specifically for testing GBLOCs. + * returns a non-PPM move and a PPM move with different destination postal codes and GBLOCs. */ + setupGblocTestData := func() (*models.Move, *models.Move, *models.Move) { + // ZIPs takes a GBLOC and returns a ZIP + ZIPs := map[string]string{ + "AGFM": "62225", + "KKFA": "90210", + "BGNC": "47712", + "CLPK": "33009", + } + + for k, v := range ZIPs { + factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), v, k) + } + + serviceMember := factory.BuildServiceMember(suite.DB(), nil, nil) + + defaultPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + addressAGFM := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "123 Main St", + City: "Beverly Hills", + State: "CA", + PostalCode: ZIPs["AGFM"], + }, + }, + }, nil) + addressKKFA := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "123 Main St", + City: "Beverly Hills", + State: "CA", + PostalCode: ZIPs["KKFA"], + }, + }, + }, nil) + addressBGNC := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "123 Main St", + City: "Beverly Hills", + State: "CA", + PostalCode: ZIPs["BGNC"], + }, + }, + }, nil) + addressCLPK := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "123 Main St", + City: "Beverly Hills", + State: "CA", + PostalCode: ZIPs["CLPK"], + }, + }, + }, nil) + + destDutyLocationAGFM := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + Name: "Test AGFM", + AddressID: addressAGFM.ID, + }, + }, + }, nil) + destDutyLocationCLPK := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + Name: "Test CLPK", + AddressID: addressCLPK.ID, + }, + }, + }, nil) + + order := factory.BuildOrder(suite.DB(), []factory.Customization{ + { + Model: models.Order{ + ServiceMemberID: serviceMember.ID, + NewDutyLocationID: destDutyLocationAGFM.ID, + DestinationGBLOC: handlers.FmtString("AGFM"), + HasDependents: false, + SpouseHasProGear: false, + OrdersType: "PERMANENT_CHANGE_OF_STATION", + OrdersTypeDetail: nil, + }, + }, + }, nil) + + orderWithShipment := factory.BuildOrder(suite.DB(), []factory.Customization{ + { + Model: models.Order{ + ServiceMemberID: serviceMember.ID, + NewDutyLocationID: destDutyLocationAGFM.ID, + DestinationGBLOC: handlers.FmtString("AGFM"), + HasDependents: false, + SpouseHasProGear: false, + OrdersType: "PERMANENT_CHANGE_OF_STATION", + OrdersTypeDetail: nil, + }, + }, + }, nil) + + orderWithShipmentPPM := factory.BuildOrder(suite.DB(), []factory.Customization{ + { + Model: models.Order{ + ServiceMemberID: serviceMember.ID, + NewDutyLocationID: destDutyLocationCLPK.ID, + DestinationGBLOC: handlers.FmtString("CLPK"), + HasDependents: false, + SpouseHasProGear: false, + OrdersType: "PERMANENT_CHANGE_OF_STATION", + OrdersTypeDetail: nil, + }, + }, + }, nil) + + moveWithoutShipment := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + OrdersID: order.ID, + }, + }, + }, nil) + + moveWithShipment := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + DestinationAddressID: &addressKKFA.ID, + PickupAddressID: &defaultPickupAddress.ID, + Status: models.MTOShipmentStatusSubmitted, + ShipmentType: models.MTOShipmentTypeHHG, + }, + }, + { + Model: models.Move{ + OrdersID: orderWithShipment.ID, + }, + }, + }, nil) + + moveWithShipmentPPM := factory.BuildMoveWithPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusSUBMITTED, + OrdersID: orderWithShipmentPPM.ID, + }, + }, + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusSubmitted, + DestinationAddressID: &addressBGNC.ID, + PickupAddressID: &defaultPickupAddress.ID, + }, + }, + { + Model: models.PPMShipment{ + Status: models.PPMShipmentStatusSubmitted, + DestinationAddressID: &addressBGNC.ID, + PickupAddressID: &defaultPickupAddress.ID, + }, + }, + }, nil) + + return &moveWithoutShipment, &moveWithShipment, &moveWithShipmentPPM + } + suite.Run("Successful move search by locator", func() { req := setupTestData() move := factory.BuildMove(suite.DB(), nil, nil) @@ -331,7 +499,7 @@ func (suite *HandlerSuite) TestSearchMovesHandler() { payloadMove := *(*payload).SearchMoves[0] suite.Equal(move.ID.String(), payloadMove.ID.String()) suite.Equal(*move.Orders.ServiceMember.Edipi, *payloadMove.Edipi) - suite.Equal(move.Orders.NewDutyLocation.Address.PostalCode, payloadMove.DestinationDutyLocationPostalCode) + suite.Equal(move.Orders.NewDutyLocation.Address.PostalCode, payloadMove.DestinationPostalCode) suite.Equal(move.Orders.OriginDutyLocation.Address.PostalCode, payloadMove.OriginDutyLocationPostalCode) suite.Equal(ghcmessages.MoveStatusDRAFT, payloadMove.Status) suite.Equal("ARMY", payloadMove.Branch) @@ -383,6 +551,139 @@ func (suite *HandlerSuite) TestSearchMovesHandler() { suite.Equal(move.ID.String(), (*payload).SearchMoves[0].ID.String()) }) + + suite.Run("Destination Postal Code and GBLOC is correct for different shipment types", func() { + req := setupTestData() + move, moveWithShipment, moveWithShipmentPPM := setupGblocTestData() + + moves := models.Moves{*move} + movesWithShipment := models.Moves{*moveWithShipment} + movesWithShipmentPPM := models.Moves{*moveWithShipmentPPM} + + // Mocks + mockSearcher := mocks.MoveSearcher{} + mockUnlocker := movelocker.NewMoveUnlocker() + handler := SearchMovesHandler{ + HandlerConfig: suite.HandlerConfig(), + MoveSearcher: &mockSearcher, + MoveUnlocker: mockUnlocker, + } + + // Set Mock Search settings for move without Shipment + mockSearcher.On("SearchMoves", + mock.AnythingOfType("*appcontext.appContext"), + mock.MatchedBy(func(params *services.SearchMovesParams) bool { + return *params.Locator == move.Locator + }), + ).Return(moves, 1, nil) + + // Move search params without Shipment + params := moveops.SearchMovesParams{ + HTTPRequest: req, + Body: moveops.SearchMovesBody{ + Locator: &move.Locator, + Edipi: nil, + }, + } + + // Validate incoming payload non-PPM + suite.NoError(params.Body.Validate(strfmt.Default)) + + // set and validate response and payload + response := handler.Handle(params) + suite.IsType(&moveops.SearchMovesOK{}, response) + payload := response.(*moveops.SearchMovesOK).Payload + + // Validate outgoing payload without shipment + suite.NoError(payload.Validate(strfmt.Default)) + + var moveDestinationPostalCode string + var moveDestinationGBLOC string + var err error + + // Get destination postal code and GBLOC based on business logic + moveDestinationPostalCode, err = move.GetDestinationPostalCode(suite.DB()) + suite.NoError(err) + moveDestinationGBLOC, err = move.GetDestinationGBLOC(suite.DB()) + suite.NoError(err) + + suite.Equal(moveDestinationPostalCode, "62225") + suite.Equal(ghcmessages.GBLOC(moveDestinationGBLOC), ghcmessages.GBLOC("AGFM")) + + // Set Mock Search settings for move with MTO Shipment + mockSearcher.On("SearchMoves", + mock.AnythingOfType("*appcontext.appContext"), + mock.MatchedBy(func(params *services.SearchMovesParams) bool { + return *params.Locator == moveWithShipment.Locator + }), + ).Return(movesWithShipment, 1, nil) + + // Move search params with MTO Shipment + params = moveops.SearchMovesParams{ + HTTPRequest: req, + Body: moveops.SearchMovesBody{ + Locator: &moveWithShipment.Locator, + Edipi: nil, + }, + } + + // Validate incoming payload with shipment + suite.NoError(params.Body.Validate(strfmt.Default)) + + // reset and validate response and payload + response = handler.Handle(params) + suite.IsType(&moveops.SearchMovesOK{}, response) + payload = response.(*moveops.SearchMovesOK).Payload + + // Validate outgoing payload with shipment + suite.NoError(payload.Validate(strfmt.Default)) + + // Get destination postal code and GBLOC based on business logic + moveDestinationPostalCode, err = moveWithShipment.GetDestinationPostalCode(suite.DB()) + suite.NoError(err) + moveDestinationGBLOC, err = moveWithShipment.GetDestinationGBLOC(suite.DB()) + suite.NoError(err) + + suite.Equal(moveDestinationPostalCode, "90210") + suite.Equal(ghcmessages.GBLOC(moveDestinationGBLOC), ghcmessages.GBLOC("KKFA")) + + // Set Mock Search settings for move with PPM Shipment + mockSearcher.On("SearchMoves", + mock.AnythingOfType("*appcontext.appContext"), + mock.MatchedBy(func(params *services.SearchMovesParams) bool { + return *params.Locator == moveWithShipmentPPM.Locator + }), + ).Return(movesWithShipmentPPM, 1, nil) + + // Move search params with PPM Shipment + params = moveops.SearchMovesParams{ + HTTPRequest: req, + Body: moveops.SearchMovesBody{ + Locator: &moveWithShipmentPPM.Locator, + Edipi: nil, + }, + } + + // Validate incoming payload with PPM shipment + suite.NoError(params.Body.Validate(strfmt.Default)) + + // reset and validate response and payload + response = handler.Handle(params) + suite.IsType(&moveops.SearchMovesOK{}, response) + payload = response.(*moveops.SearchMovesOK).Payload + + // Validate outgoing payload non-PPM + suite.NoError(payload.Validate(strfmt.Default)) + + // Get destination postal code and GBLOC based on business logic + moveDestinationPostalCode, err = moveWithShipmentPPM.GetDestinationPostalCode(suite.DB()) + suite.NoError(err) + moveDestinationGBLOC, err = moveWithShipmentPPM.GetDestinationGBLOC(suite.DB()) + suite.NoError(err) + + suite.Equal(moveDestinationPostalCode, payload.SearchMoves[0].DestinationPostalCode) + suite.Equal(ghcmessages.GBLOC(moveDestinationGBLOC), payload.SearchMoves[0].DestinationGBLOC) + }) } func (suite *HandlerSuite) TestSetFinancialReviewFlagHandler() { diff --git a/pkg/handlers/ghcapi/mto_service_items_test.go b/pkg/handlers/ghcapi/mto_service_items_test.go index 16bd37da126..37085402a93 100644 --- a/pkg/handlers/ghcapi/mto_service_items_test.go +++ b/pkg/handlers/ghcapi/mto_service_items_test.go @@ -563,7 +563,7 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemStatusHandler() { mock.Anything, mock.Anything, ).Return(400, nil) - mtoServiceItemStatusUpdater := mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator) + mtoServiceItemStatusUpdater := mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer(), ghcrateengine.NewDomesticDestinationSITDeliveryPricer(), ghcrateengine.NewDomesticOriginSITFuelSurchargePricer()) handler := UpdateMTOServiceItemStatusHandler{ HandlerConfig: suite.HandlerConfig(), @@ -623,7 +623,7 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemStatusHandler() { mock.Anything, mock.Anything, ).Return(400, nil) - mtoServiceItemStatusUpdater := mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator) + mtoServiceItemStatusUpdater := mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer(), ghcrateengine.NewDomesticDestinationSITDeliveryPricer(), ghcrateengine.NewDomesticOriginSITFuelSurchargePricer()) handler := UpdateMTOServiceItemStatusHandler{ HandlerConfig: suite.HandlerConfig(), diff --git a/pkg/handlers/ghcapi/mto_shipment_test.go b/pkg/handlers/ghcapi/mto_shipment_test.go index 083cf93475e..fc00fb3feec 100644 --- a/pkg/handlers/ghcapi/mto_shipment_test.go +++ b/pkg/handlers/ghcapi/mto_shipment_test.go @@ -3593,6 +3593,12 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandlerUsingPPM() { mock.AnythingOfType("*models.PPMShipment")). Return(models.CentPointer(unit.Cents(estimatedIncentive)), models.CentPointer(unit.Cents(sitEstimatedCost)), nil).Once() + ppmEstimator.On("MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(nil, nil) + // Validate incoming payload suite.NoError(params.Body.Validate(strfmt.Default)) @@ -3747,6 +3753,12 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandlerUsingPPM() { mock.AnythingOfType("*models.PPMShipment")). Return(models.CentPointer(unit.Cents(estimatedIncentive)), nil, nil).Once() + ppmEstimator.On("MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(nil, nil) + // Validate incoming payload suite.NoError(params.Body.Validate(strfmt.Default)) @@ -3891,6 +3903,12 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandlerUsingPPM() { mock.AnythingOfType("*models.PPMShipment")). Return(models.CentPointer(unit.Cents(estimatedIncentive)), nil, nil).Once() + ppmEstimator.On("MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(nil, nil) + // Validate incoming payload suite.NoError(params.Body.Validate(strfmt.Default)) @@ -4221,6 +4239,12 @@ func (suite *HandlerSuite) TestUpdateShipmentHandler() { mock.AnythingOfType("*models.PPMShipment")). Return(models.CentPointer(unit.Cents(estimatedIncentive)), models.CentPointer(unit.Cents(sitEstimatedCost)), nil).Once() + ppmEstimator.On("MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(nil, nil) + ppmEstimator.On("FinalIncentiveWithDefaultChecks", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("models.PPMShipment"), @@ -4315,6 +4339,12 @@ func (suite *HandlerSuite) TestUpdateShipmentHandler() { mock.AnythingOfType("*models.PPMShipment")). Return(models.CentPointer(unit.Cents(estimatedIncentive)), models.CentPointer(unit.Cents(sitEstimatedCost)), nil).Once() + ppmEstimator.On("MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(nil, nil) + ppmEstimator.On("FinalIncentiveWithDefaultChecks", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("models.PPMShipment"), @@ -4388,6 +4418,12 @@ func (suite *HandlerSuite) TestUpdateShipmentHandler() { mock.AnythingOfType("*models.PPMShipment")). Return(models.CentPointer(unit.Cents(estimatedIncentive)), models.CentPointer(unit.Cents(sitEstimatedCost)), nil).Once() + ppmEstimator.On("MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(nil, nil) + ppmEstimator.On("FinalIncentiveWithDefaultChecks", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("models.PPMShipment"), @@ -4661,7 +4697,7 @@ func (suite *HandlerSuite) TestUpdateSITServiceItemCustomerExpenseHandler() { mock.Anything, mock.Anything, ).Return(400, nil) - updater := mtoserviceitem.NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator) + updater := mtoserviceitem.NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer(), ghcrateengine.NewDomesticDestinationSITDeliveryPricer(), ghcrateengine.NewDomesticOriginSITFuelSurchargePricer()) req := httptest.NewRequest("PATCH", fmt.Sprintf("/shipments/%s/sit-service-item/convert-to-customer-expense", approvedShipment.ID.String()), nil) req = suite.AuthenticateOfficeRequest(req, officeUser) handlerConfig := suite.HandlerConfig() @@ -4737,7 +4773,7 @@ func (suite *HandlerSuite) TestUpdateSITServiceItemCustomerExpenseHandler() { mock.Anything, mock.Anything, ).Return(400, nil) - updater := mtoserviceitem.NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator) + updater := mtoserviceitem.NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer(), ghcrateengine.NewDomesticDestinationSITDeliveryPricer(), ghcrateengine.NewDomesticOriginSITFuelSurchargePricer()) req := httptest.NewRequest("PATCH", fmt.Sprintf("/shipments/%s/sit-service-item/convert-to-customer-expense", approvedShipment.ID.String()), nil) req = suite.AuthenticateOfficeRequest(req, officeUser) handlerConfig := suite.HandlerConfig() diff --git a/pkg/handlers/ghcapi/office_users.go b/pkg/handlers/ghcapi/office_users.go index 724f260198d..3b7ebacfc00 100644 --- a/pkg/handlers/ghcapi/office_users.go +++ b/pkg/handlers/ghcapi/office_users.go @@ -26,6 +26,7 @@ type RequestOfficeUserHandler struct { services.NewQueryFilter services.UserRoleAssociator services.RoleAssociater + services.TransportaionOfficeAssignmentUpdater } // Convert internal role model to ghc role model @@ -59,19 +60,20 @@ func payloadForOfficeUserModel(o models.OfficeUser) *ghcmessages.OfficeUser { user = o.User } payload := &ghcmessages.OfficeUser{ - ID: handlers.FmtUUID(o.ID), - FirstName: handlers.FmtString(o.FirstName), - MiddleInitials: handlers.FmtStringPtr(o.MiddleInitials), - LastName: handlers.FmtString(o.LastName), - Telephone: handlers.FmtString(o.Telephone), - Email: handlers.FmtString(o.Email), - Edipi: handlers.FmtStringPtr(o.EDIPI), - OtherUniqueID: handlers.FmtStringPtr(o.OtherUniqueID), - TransportationOfficeID: handlers.FmtUUID(o.TransportationOfficeID), - Active: handlers.FmtBool(o.Active), - Status: (*string)(o.Status), - CreatedAt: *handlers.FmtDateTime(o.CreatedAt), - UpdatedAt: *handlers.FmtDateTime(o.UpdatedAt), + ID: handlers.FmtUUID(o.ID), + FirstName: handlers.FmtString(o.FirstName), + MiddleInitials: handlers.FmtStringPtr(o.MiddleInitials), + LastName: handlers.FmtString(o.LastName), + Telephone: handlers.FmtString(o.Telephone), + Email: handlers.FmtString(o.Email), + Edipi: handlers.FmtStringPtr(o.EDIPI), + OtherUniqueID: handlers.FmtStringPtr(o.OtherUniqueID), + TransportationOfficeID: handlers.FmtUUID(o.TransportationOfficeID), + TransportationOfficeAssignments: payloadForTransportationOfficeAssignments(o.TransportationOfficeAssignments), + Active: handlers.FmtBool(o.Active), + Status: (*string)(o.Status), + CreatedAt: *handlers.FmtDateTime(o.CreatedAt), + UpdatedAt: *handlers.FmtDateTime(o.UpdatedAt), } if o.UserID != nil { userIDFmt := handlers.FmtUUID(*o.UserID) @@ -85,6 +87,18 @@ func payloadForOfficeUserModel(o models.OfficeUser) *ghcmessages.OfficeUser { return payload } +func payloadForTransportationOfficeAssignments(toas models.TransportationOfficeAssignments) []*ghcmessages.TransportationOfficeAssignment { + var payload []*ghcmessages.TransportationOfficeAssignment + for _, toa := range toas { + payload = append(payload, &ghcmessages.TransportationOfficeAssignment{ + OfficeUserID: handlers.FmtUUID(toa.ID), + TransportationOfficeID: handlers.FmtUUID(toa.TransportationOfficeID), + PrimaryOffice: handlers.FmtBool(*toa.PrimaryOffice), + }) + } + return payload +} + // Handle creates the office user with a status of requested func (h RequestOfficeUserHandler) Handle(params officeuserop.CreateRequestedOfficeUserParams) middleware.Responder { payload := params.OfficeUser @@ -181,6 +195,22 @@ func (h RequestOfficeUserHandler) Handle(params officeuserop.CreateRequestedOffi createdOfficeUser.User.Roles = roles + transportationOfficeAssignments := models.TransportationOfficeAssignments{ + { + TransportationOfficeID: transportationOfficeID, + PrimaryOffice: models.BoolPointer(true), + }, + } + + createdTransportationOfficeAssignments, err := + h.TransportaionOfficeAssignmentUpdater.UpdateTransportaionOfficeAssignments(appCtx, createdOfficeUser.ID, transportationOfficeAssignments) + if err != nil { + appCtx.Logger().Error("Error updating office user's transportation office assignments", zap.Error(err)) + return officeuserop.NewCreateRequestedOfficeUserUnprocessableEntity(), err + } + + createdOfficeUser.TransportationOfficeAssignments = createdTransportationOfficeAssignments + _, err = audit.Capture(appCtx, createdOfficeUser, nil, params.HTTPRequest) if err != nil { appCtx.Logger().Error("Error capturing audit record", zap.Error(err)) diff --git a/pkg/handlers/ghcapi/office_users_test.go b/pkg/handlers/ghcapi/office_users_test.go index f3727b6ded7..33230293099 100644 --- a/pkg/handlers/ghcapi/office_users_test.go +++ b/pkg/handlers/ghcapi/office_users_test.go @@ -19,18 +19,20 @@ import ( "github.com/transcom/mymove/pkg/services/query" ) -func (suite *HandlerSuite) setupOfficeUserCreatorTestScenario() (*mocks.OfficeUserCreator, *mocks.UserRoleAssociator, *mocks.RoleAssociater, *RequestOfficeUserHandler) { +func (suite *HandlerSuite) setupOfficeUserCreatorTestScenario() (*mocks.OfficeUserCreator, *mocks.UserRoleAssociator, *mocks.RoleAssociater, *mocks.TransportaionOfficeAssignmentUpdater, *RequestOfficeUserHandler) { mockCreator := &mocks.OfficeUserCreator{} mockUserRoleAssociator := &mocks.UserRoleAssociator{} mockRoleAssociator := &mocks.RoleAssociater{} + mockTransportaionOfficeAssignmentUpdater := &mocks.TransportaionOfficeAssignmentUpdater{} handler := &RequestOfficeUserHandler{ - HandlerConfig: suite.HandlerConfig(), - OfficeUserCreator: mockCreator, - NewQueryFilter: query.NewQueryFilter, - UserRoleAssociator: mockUserRoleAssociator, - RoleAssociater: mockRoleAssociator, + HandlerConfig: suite.HandlerConfig(), + OfficeUserCreator: mockCreator, + NewQueryFilter: query.NewQueryFilter, + UserRoleAssociator: mockUserRoleAssociator, + RoleAssociater: mockRoleAssociator, + TransportaionOfficeAssignmentUpdater: mockTransportaionOfficeAssignmentUpdater, } - return mockCreator, mockUserRoleAssociator, mockRoleAssociator, handler + return mockCreator, mockUserRoleAssociator, mockRoleAssociator, mockTransportaionOfficeAssignmentUpdater, handler } // Services Counselor. Task Ordering Officer (TOO), Task Invoicing Officer (TIO), @@ -38,7 +40,7 @@ func (suite *HandlerSuite) setupOfficeUserCreatorTestScenario() (*mocks.OfficeUs // Are all roles allowed to request office user (They authenticate with AuthenticateOfficeRequest) func (suite *HandlerSuite) TestRequestOfficeUserHandler() { suite.Run("Successfully requests the creation of an office user", func() { - mockCreator, mockRoleAssociator, mockRoleFetcher, handler := suite.setupOfficeUserCreatorTestScenario() + mockCreator, mockRoleAssociator, mockRoleFetcher, mockTransportaionOfficeAssignmentUpdater, handler := suite.setupOfficeUserCreatorTestScenario() transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) @@ -102,6 +104,22 @@ func (suite *HandlerSuite) TestRequestOfficeUserHandler() { mock.Anything, ).Return(mockRoles, nil) + mockTransportationAssignments := models.TransportationOfficeAssignments{ + models.TransportationOfficeAssignment{ + ID: officeUser.ID, + TransportationOfficeID: officeUser.TransportationOfficeID, + PrimaryOffice: models.BoolPointer(true), + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }, + } + mockTransportaionOfficeAssignmentUpdater.On( + "UpdateTransportaionOfficeAssignments", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + ).Return(mockTransportationAssignments, nil) + // Handle params with mocked services response := handler.Handle(params) @@ -109,6 +127,7 @@ func (suite *HandlerSuite) TestRequestOfficeUserHandler() { createdResponse := response.(*officeuserop.CreateRequestedOfficeUserCreated) suite.Equal("John", *createdResponse.Payload.FirstName) suite.Equal("REQUESTED", *createdResponse.Payload.Status) + suite.Equal(1, len(createdResponse.Payload.TransportationOfficeAssignments)) // Ensure that the mock assertions are met mockCreator.AssertExpectations(suite.T()) @@ -116,7 +135,7 @@ func (suite *HandlerSuite) TestRequestOfficeUserHandler() { }) suite.Run("Responds proper validation errors", func() { - mockCreator, _, _, handler := suite.setupOfficeUserCreatorTestScenario() + mockCreator, _, _, _, handler := suite.setupOfficeUserCreatorTestScenario() officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO, roles.RoleTypeServicesCounselor}) transportationOfficeID, _ := uuid.NewV4() @@ -157,7 +176,7 @@ func (suite *HandlerSuite) TestRequestOfficeUserHandler() { }) suite.Run("Bad transportation office ID", func() { - _, _, _, handler := suite.setupOfficeUserCreatorTestScenario() + _, _, _, _, handler := suite.setupOfficeUserCreatorTestScenario() officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeServicesCounselor}) transportationOfficeID := "Not a UUID" request := httptest.NewRequest("POST", "/requested-office-users", nil) @@ -181,7 +200,7 @@ func (suite *HandlerSuite) TestRequestOfficeUserHandler() { }) suite.Run("No payload roles", func() { - _, _, _, handler := suite.setupOfficeUserCreatorTestScenario() + _, _, _, _, handler := suite.setupOfficeUserCreatorTestScenario() officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeServicesCounselor}) transportationOfficeID, _ := uuid.NewV4() request := httptest.NewRequest("POST", "/requested-office-users", nil) @@ -204,7 +223,7 @@ func (suite *HandlerSuite) TestRequestOfficeUserHandler() { }) suite.Run("Bad payload roles", func() { - _, _, _, handler := suite.setupOfficeUserCreatorTestScenario() + _, _, _, _, handler := suite.setupOfficeUserCreatorTestScenario() officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{}) transportationOfficeID := "Not a UUID" request := httptest.NewRequest("POST", "/requested-office-users", nil) @@ -228,7 +247,7 @@ func (suite *HandlerSuite) TestRequestOfficeUserHandler() { }) suite.Run("Enforces identification rule", func() { - _, _, _, handler := suite.setupOfficeUserCreatorTestScenario() + _, _, _, _, handler := suite.setupOfficeUserCreatorTestScenario() transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) diff --git a/pkg/handlers/ghcapi/orders.go b/pkg/handlers/ghcapi/orders.go index a4231d9df06..cd9ff484a59 100644 --- a/pkg/handlers/ghcapi/orders.go +++ b/pkg/handlers/ghcapi/orders.go @@ -218,7 +218,7 @@ func (h CreateOrderHandler) Handle(params orderop.CreateOrderParams) middleware. return orderop.NewCreateOrderUnprocessableEntity(), err } - newDutyLocationGBLOC, err := models.FetchGBLOCForPostalCode(appCtx.DB(), newDutyLocation.Address.PostalCode) + destinationGBLOC, err := models.FetchGBLOCForPostalCode(appCtx.DB(), newDutyLocation.Address.PostalCode) if err != nil { err = apperror.NewBadDataError("New duty location GBLOC cannot be verified") appCtx.Logger().Error(err.Error()) @@ -236,8 +236,8 @@ func (h CreateOrderHandler) Handle(params orderop.CreateOrderParams) middleware. } grade := (internalmessages.OrderPayGrade)(*payload.Grade) - weightAllotment := models.GetWeightAllotment(grade) - + ordersType := (internalmessages.OrdersType)(*payload.OrdersType) + weightAllotment := models.GetWeightAllotment(grade, ordersType) weight := weightAllotment.TotalWeightSelf if *payload.HasDependents { weight = weightAllotment.TotalWeightSelfPlusDependents @@ -245,12 +245,30 @@ func (h CreateOrderHandler) Handle(params orderop.CreateOrderParams) middleware. sitDaysAllowance := models.DefaultServiceMemberSITDaysAllowance + var dependentsTwelveAndOver *int + var dependentsUnderTwelve *int + if payload.DependentsTwelveAndOver != nil { + dependentsTwelveAndOver = models.IntPointer(int(*payload.DependentsTwelveAndOver)) + } + if payload.DependentsUnderTwelve != nil { + dependentsUnderTwelve = models.IntPointer(int(*payload.DependentsUnderTwelve)) + } + // Calculate UB allowance for the order entitlement + unaccompaniedBaggageAllowance, err := models.GetUBWeightAllowance(appCtx, originDutyLocation.Address.IsOconus, newDutyLocation.Address.IsOconus, serviceMember.Affiliation, &grade, (*internalmessages.OrdersType)(payload.OrdersType), payload.HasDependents, payload.AccompaniedTour, dependentsUnderTwelve, dependentsTwelveAndOver) + if err == nil { + weightAllotment.UnaccompaniedBaggageAllowance = unaccompaniedBaggageAllowance + } + entitlement := models.Entitlement{ - DependentsAuthorized: payload.HasDependents, - DBAuthorizedWeight: models.IntPointer(weight), - StorageInTransit: models.IntPointer(sitDaysAllowance), - ProGearWeight: weightAllotment.ProGearWeight, - ProGearWeightSpouse: weightAllotment.ProGearWeightSpouse, + DependentsAuthorized: payload.HasDependents, + DBAuthorizedWeight: models.IntPointer(weight), + StorageInTransit: models.IntPointer(sitDaysAllowance), + ProGearWeight: weightAllotment.ProGearWeight, + ProGearWeightSpouse: weightAllotment.ProGearWeightSpouse, + AccompaniedTour: payload.AccompaniedTour, + DependentsUnderTwelve: dependentsUnderTwelve, + DependentsTwelveAndOver: dependentsTwelveAndOver, + UBAllowance: &weightAllotment.UnaccompaniedBaggageAllowance, } if saveEntitlementErr := appCtx.DB().Save(&entitlement); saveEntitlementErr != nil { @@ -295,7 +313,7 @@ func (h CreateOrderHandler) Handle(params orderop.CreateOrderParams) middleware. &entitlement, &originDutyLocationGBLOC.GBLOC, packingAndShippingInstructions, - &newDutyLocationGBLOC.GBLOC, + &destinationGBLOC.GBLOC, ) if err != nil || verrs.HasAny() { return handlers.ResponseForVErrors(appCtx.Logger(), verrs, err), err diff --git a/pkg/handlers/ghcapi/orders_test.go b/pkg/handlers/ghcapi/orders_test.go index 35e92e4611e..6be28b76967 100644 --- a/pkg/handlers/ghcapi/orders_test.go +++ b/pkg/handlers/ghcapi/orders_test.go @@ -24,7 +24,6 @@ import ( "github.com/transcom/mymove/pkg/services" "github.com/transcom/mymove/pkg/services/ghcrateengine" "github.com/transcom/mymove/pkg/services/mocks" - "github.com/transcom/mymove/pkg/services/move" moverouter "github.com/transcom/mymove/pkg/services/move" movetaskorder "github.com/transcom/mymove/pkg/services/move_task_order" mtoserviceitem "github.com/transcom/mymove/pkg/services/mto_service_item" @@ -106,6 +105,85 @@ func (suite *HandlerSuite) TestCreateOrder() { suite.NotEmpty(createdOrder.NAICS) } +func (suite *HandlerSuite) TestCreateOrderWithOCONUSValues() { + sm := factory.BuildExtendedServiceMember(suite.AppContextForTest().DB(), nil, nil) + officeUser := factory.BuildOfficeUserWithRoles(suite.AppContextForTest().DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + + originDutyLocation := factory.BuildDutyLocation(suite.AppContextForTest().DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + Name: "Not Yuma AFB", + }, + }, + }, nil) + dutyLocation := factory.FetchOrBuildCurrentDutyLocation(suite.AppContextForTest().DB()) + factory.FetchOrBuildPostalCodeToGBLOC(suite.AppContextForTest().DB(), dutyLocation.Address.PostalCode, "KKFA") + factory.FetchOrBuildDefaultContractor(suite.AppContextForTest().DB(), nil, nil) + + req := httptest.NewRequest("POST", "/orders", nil) + req = suite.AuthenticateOfficeRequest(req, officeUser) + + hasDependents := true + spouseHasProGear := true + issueDate := time.Date(2018, time.March, 10, 0, 0, 0, 0, time.UTC) + reportByDate := time.Date(2018, time.August, 1, 0, 0, 0, 0, time.UTC) + ordersType := ghcmessages.OrdersTypePERMANENTCHANGEOFSTATION + deptIndicator := ghcmessages.DeptIndicatorAIRANDSPACEFORCE + dependentsTwelveAndOver := 2 + dependentsUnderTwelve := 4 + accompaniedTour := true + payload := &ghcmessages.CreateOrders{ + HasDependents: handlers.FmtBool(hasDependents), + SpouseHasProGear: handlers.FmtBool(spouseHasProGear), + IssueDate: handlers.FmtDate(issueDate), + ReportByDate: handlers.FmtDate(reportByDate), + OrdersType: ghcmessages.NewOrdersType(ordersType), + OriginDutyLocationID: *handlers.FmtUUIDPtr(&originDutyLocation.ID), + NewDutyLocationID: handlers.FmtUUID(dutyLocation.ID), + ServiceMemberID: handlers.FmtUUID(sm.ID), + OrdersNumber: handlers.FmtString("123456"), + Tac: handlers.FmtString("E19A"), + Sac: handlers.FmtString("SacNumber"), + DepartmentIndicator: ghcmessages.NewDeptIndicator(deptIndicator), + Grade: ghcmessages.GradeE1.Pointer(), + AccompaniedTour: &accompaniedTour, + DependentsTwelveAndOver: models.Int64Pointer(int64(dependentsTwelveAndOver)), + DependentsUnderTwelve: models.Int64Pointer(int64(dependentsUnderTwelve)), + } + + params := orderop.CreateOrderParams{ + HTTPRequest: req, + CreateOrders: payload, + } + + fakeS3 := storageTest.NewFakeS3Storage(true) + handlerConfig := suite.HandlerConfig() + handlerConfig.SetFileStorer(fakeS3) + createHandler := CreateOrderHandler{handlerConfig} + + response := createHandler.Handle(params) + + suite.Assertions.IsType(&orderop.CreateOrderOK{}, response) + okResponse := response.(*orderop.CreateOrderOK) + orderID := okResponse.Payload.ID.String() + createdOrder, _ := models.FetchOrder(suite.DB(), uuid.FromStringOrNil(orderID)) + + suite.Assertions.Equal(sm.ID.String(), okResponse.Payload.CustomerID.String()) + suite.Assertions.Equal(ordersType, okResponse.Payload.OrderType) + suite.Assertions.Equal(handlers.FmtString("123456"), okResponse.Payload.OrderNumber) + suite.Assertions.Equal(handlers.FmtString("E19A"), okResponse.Payload.Tac) + suite.Assertions.Equal(handlers.FmtString("SacNumber"), okResponse.Payload.Sac) + suite.Assertions.Equal(&deptIndicator, okResponse.Payload.DepartmentIndicator) + suite.Assertions.Equal(*okResponse.Payload.Entitlement.AccompaniedTour, accompaniedTour) + suite.Assertions.Equal(*okResponse.Payload.Entitlement.DependentsTwelveAndOver, int64(dependentsTwelveAndOver)) + suite.Assertions.Equal(*okResponse.Payload.Entitlement.DependentsUnderTwelve, int64(dependentsUnderTwelve)) + suite.NotNil(&createdOrder.Entitlement) + suite.NotEmpty(createdOrder.SupplyAndServicesCostEstimate) + suite.NotEmpty(createdOrder.PackingAndShippingInstructions) + suite.NotEmpty(createdOrder.MethodOfPayment) + suite.NotEmpty(createdOrder.NAICS) +} + func (suite *HandlerSuite) TestGetOrderHandlerIntegration() { officeUser := factory.BuildOfficeUserWithRoles(nil, nil, []roles.RoleType{roles.RoleTypeTOO}) @@ -2361,7 +2439,7 @@ func (suite *HandlerSuite) TestUploadAmendedOrdersHandlerUnit() { } func (suite *HandlerSuite) TestUploadAmendedOrdersHandlerIntegration() { - orderUpdater := orderservice.NewOrderUpdater(move.NewMoveRouter()) + orderUpdater := orderservice.NewOrderUpdater(moverouter.NewMoveRouter()) setUpRequestAndParams := func(orders models.Order) *orderop.UploadAmendedOrdersParams { endpoint := fmt.Sprintf("/orders/%v/upload_amended_orders", orders.ID.String()) diff --git a/pkg/handlers/ghcapi/ppm_closeout.go b/pkg/handlers/ghcapi/ppm_closeout.go index 43b46cf42d4..d3fa3d7f9fe 100644 --- a/pkg/handlers/ghcapi/ppm_closeout.go +++ b/pkg/handlers/ghcapi/ppm_closeout.go @@ -69,7 +69,7 @@ type GetPPMActualWeightHandler struct { ppmShipmentFetcher services.PPMShipmentFetcher } -// Handle retrieves all calcuations for a PPM closeout +// Handle retrieves actual weight for a PPM pending closeout func (h GetPPMActualWeightHandler) Handle(params ppmcloseoutops.GetPPMActualWeightParams) middleware.Responder { return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, func(appCtx appcontext.AppContext) (middleware.Responder, error) { @@ -79,15 +79,15 @@ func (h GetPPMActualWeightHandler) Handle(params ppmcloseoutops.GetPPMActualWeig payload := &ghcmessages.Error{Message: handlers.FmtString(err.Error())} switch err.(type) { case apperror.NotFoundError: - return ppmcloseoutops.NewGetPPMCloseoutNotFound().WithPayload(payload), err + return ppmcloseoutops.NewGetPPMActualWeightNotFound().WithPayload(payload), err case apperror.PPMNotReadyForCloseoutError: - return ppmcloseoutops.NewGetPPMCloseoutNotFound().WithPayload(payload), err + return ppmcloseoutops.NewGetPPMActualWeightNotFound().WithPayload(payload), err case apperror.ForbiddenError: - return ppmcloseoutops.NewGetPPMCloseoutForbidden().WithPayload(payload), err + return ppmcloseoutops.NewGetPPMActualWeightForbidden().WithPayload(payload), err case apperror.QueryError: - return ppmcloseoutops.NewGetPPMCloseoutInternalServerError().WithPayload(payload), err + return ppmcloseoutops.NewGetPPMActualWeightInternalServerError().WithPayload(payload), err default: - return ppmcloseoutops.NewGetPPMCloseoutInternalServerError().WithPayload(payload), err + return ppmcloseoutops.NewGetPPMActualWeightInternalServerError().WithPayload(payload), err } } errInstance := fmt.Sprintf("Instance: %s", h.GetTraceIDFromRequest(params.HTTPRequest)) @@ -95,7 +95,7 @@ func (h GetPPMActualWeightHandler) Handle(params ppmcloseoutops.GetPPMActualWeig errPayload := &ghcmessages.Error{Message: &errInstance} if !appCtx.Session().IsOfficeApp() { - return ppmcloseoutops.NewGetPPMCloseoutForbidden().WithPayload(errPayload), apperror.NewSessionError("Request should come from the office app.") + return ppmcloseoutops.NewGetPPMActualWeightForbidden().WithPayload(errPayload), apperror.NewSessionError("Request should come from the office app.") } ppmShipmentID := uuid.FromStringOrNil(params.PpmShipmentID.String()) diff --git a/pkg/handlers/ghcapi/queues.go b/pkg/handlers/ghcapi/queues.go index a2d38582045..5ffbedb35d5 100644 --- a/pkg/handlers/ghcapi/queues.go +++ b/pkg/handlers/ghcapi/queues.go @@ -78,39 +78,45 @@ func (h GetMovesQueueHandler) Handle(params queues.GetMovesQueueParams) middlewa ListOrderParams.PerPage = models.Int64Pointer(20) } - if params.ViewAsGBLOC != nil && appCtx.Session().Roles.HasRole(roles.RoleTypeHQ) { - ListOrderParams.ViewAsGBLOC = params.ViewAsGBLOC - } - - moves, count, err := h.OrderFetcher.ListOrders( - appCtx, - appCtx.Session().OfficeUserID, - roles.RoleTypeTOO, - &ListOrderParams, - ) - if err != nil { - appCtx.Logger(). - Error("error fetching list of moves for office user", zap.Error(err)) - return queues.NewGetMovesQueueInternalServerError(), err - } - var officeUser models.OfficeUser + var assignedGblocs []string + var err error if appCtx.Session().OfficeUserID != uuid.Nil { - officeUser, err = h.OfficeUserFetcherPop.FetchOfficeUserByID(appCtx, appCtx.Session().OfficeUserID) + officeUser, err = h.OfficeUserFetcherPop.FetchOfficeUserByIDWithTransportationOfficeAssignments(appCtx, appCtx.Session().OfficeUserID) if err != nil { appCtx.Logger().Error("Error retrieving office_user", zap.Error(err)) - return queues.NewGetServicesCounselingQueueInternalServerError(), err + return queues.NewGetMovesQueueInternalServerError(), err } + + assignedGblocs = models.GetAssignedGBLOCs(officeUser) + } + + if params.ViewAsGBLOC != nil && (appCtx.Session().Roles.HasRole(roles.RoleTypeHQ) || slices.Contains(assignedGblocs, *params.ViewAsGBLOC)) { + ListOrderParams.ViewAsGBLOC = params.ViewAsGBLOC } privileges, err := models.FetchPrivilegesForUser(appCtx.DB(), appCtx.Session().UserID) if err != nil { appCtx.Logger().Error("Error retreiving user privileges", zap.Error(err)) } + officeUser.User.Privileges = privileges + officeUser.User.Roles = appCtx.Session().Roles - isSupervisor := privileges.HasPrivilege(models.PrivilegeTypeSupervisor) var officeUsers models.OfficeUsers - if isSupervisor { + var officeUsersSafety models.OfficeUsers + if privileges.HasPrivilege(models.PrivilegeTypeSupervisor) { + if privileges.HasPrivilege(models.PrivilegeTypeSafety) { + officeUsersSafety, err = h.OfficeUserFetcherPop.FetchSafetyMoveOfficeUsersByRoleAndOffice( + appCtx, + roles.RoleTypeTOO, + officeUser.TransportationOfficeID, + ) + if err != nil { + appCtx.Logger(). + Error("error fetching safety move office users", zap.Error(err)) + return queues.NewGetMovesQueueInternalServerError(), err + } + } officeUsers, err = h.OfficeUserFetcherPop.FetchOfficeUsersByRoleAndOffice( appCtx, roles.RoleTypeTOO, @@ -126,6 +132,18 @@ func (h GetMovesQueueHandler) Handle(params queues.GetMovesQueueParams) middlewa return queues.NewGetMovesQueueInternalServerError(), err } + moves, count, err := h.OrderFetcher.ListOrders( + appCtx, + appCtx.Session().OfficeUserID, + roles.RoleTypeTOO, + &ListOrderParams, + ) + if err != nil { + appCtx.Logger(). + Error("error fetching list of moves for office user", zap.Error(err)) + return queues.NewGetMovesQueueInternalServerError(), err + } + // if the TOO/office user is accessing the queue, we need to unlock move/moves they have locked if appCtx.Session().IsOfficeUser() { officeUserID := appCtx.Session().OfficeUserID @@ -146,9 +164,8 @@ func (h GetMovesQueueHandler) Handle(params queues.GetMovesQueueParams) middlewa appCtx.Logger().Error(fmt.Sprintf("failed to unlock moves for office user ID: %s", officeUserID), zap.Error(err)) } } - isHQrole := appCtx.Session().Roles.HasRole(roles.RoleTypeHQ) - queueMoves := payloads.QueueMoves(moves, officeUsers, nil, roles.RoleTypeTOO, officeUser, isSupervisor, isHQrole) + queueMoves := payloads.QueueMoves(moves, officeUsers, nil, officeUser, officeUsersSafety) result := &ghcmessages.QueueMovesResult{ Page: *ListOrderParams.Page, @@ -266,39 +283,67 @@ func (h GetPaymentRequestsQueueHandler) Handle( listPaymentRequestParams.PerPage = models.Int64Pointer(20) } - if params.ViewAsGBLOC != nil && appCtx.Session().Roles.HasRole(roles.RoleTypeHQ) { + var officeUser models.OfficeUser + var assignedGblocs []string + var err error + if appCtx.Session().OfficeUserID != uuid.Nil { + officeUser, err = h.OfficeUserFetcherPop.FetchOfficeUserByIDWithTransportationOfficeAssignments(appCtx, appCtx.Session().OfficeUserID) + if err != nil { + appCtx.Logger().Error("Error retrieving office_user", zap.Error(err)) + return queues.NewGetPaymentRequestsQueueInternalServerError(), err + } + + assignedGblocs = models.GetAssignedGBLOCs(officeUser) + } + + if params.ViewAsGBLOC != nil && (appCtx.Session().Roles.HasRole(roles.RoleTypeHQ) || slices.Contains(assignedGblocs, *params.ViewAsGBLOC)) { listPaymentRequestParams.ViewAsGBLOC = params.ViewAsGBLOC } - paymentRequests, count, err := h.FetchPaymentRequestList( - appCtx, - appCtx.Session().OfficeUserID, - &listPaymentRequestParams, - ) + privileges, err := models.FetchPrivilegesForUser(appCtx.DB(), appCtx.Session().UserID) if err != nil { - appCtx.Logger(). - Error("payment requests queue", zap.String("office_user_id", appCtx.Session().OfficeUserID.String()), zap.Error(err)) - return queues.NewGetPaymentRequestsQueueInternalServerError(), err + appCtx.Logger().Error("Error retreiving user privileges", zap.Error(err)) } + officeUser.User.Privileges = privileges + officeUser.User.Roles = appCtx.Session().Roles - var officeUser models.OfficeUser - if appCtx.Session().OfficeUserID != uuid.Nil { - officeUser, err = h.OfficeUserFetcherPop.FetchOfficeUserByID(appCtx, appCtx.Session().OfficeUserID) - if err != nil { - appCtx.Logger().Error("Error retrieving office_user", zap.Error(err)) - return queues.NewGetServicesCounselingQueueInternalServerError(), err + var officeUsers models.OfficeUsers + var officeUsersSafety models.OfficeUsers + + if privileges.HasPrivilege(models.PrivilegeTypeSupervisor) { + if privileges.HasPrivilege(models.PrivilegeTypeSafety) { + officeUsersSafety, err = h.OfficeUserFetcherPop.FetchSafetyMoveOfficeUsersByRoleAndOffice( + appCtx, + roles.RoleTypeTIO, + officeUser.TransportationOfficeID, + ) + if err != nil { + appCtx.Logger(). + Error("error fetching safety move office users", zap.Error(err)) + return queues.NewGetMovesQueueInternalServerError(), err + } } + officeUsers, err = h.OfficeUserFetcherPop.FetchOfficeUsersByRoleAndOffice( + appCtx, + roles.RoleTypeTIO, + officeUser.TransportationOfficeID, + ) } - officeUsers, err := h.OfficeUserFetcherPop.FetchOfficeUsersByRoleAndOffice( + if err != nil { + appCtx.Logger(). + Error("error fetching office users", zap.Error(err)) + return queues.NewGetPaymentRequestsQueueInternalServerError(), err + } + + paymentRequests, count, err := h.FetchPaymentRequestList( appCtx, - roles.RoleTypeTIO, - officeUser.TransportationOfficeID, + appCtx.Session().OfficeUserID, + &listPaymentRequestParams, ) - if err != nil { appCtx.Logger(). - Error("error fetching office users", zap.Error(err)) + Error("payment requests queue", zap.String("office_user_id", appCtx.Session().OfficeUserID.String()), zap.Error(err)) return queues.NewGetPaymentRequestsQueueInternalServerError(), err } @@ -323,15 +368,7 @@ func (h GetPaymentRequestsQueueHandler) Handle( } } - privileges, err := models.FetchPrivilegesForUser(appCtx.DB(), appCtx.Session().UserID) - if err != nil { - appCtx.Logger().Error("Error retreiving user privileges", zap.Error(err)) - } - - isHQrole := appCtx.Session().Roles.HasRole(roles.RoleTypeHQ) - - isSupervisor := privileges.HasPrivilege(models.PrivilegeTypeSupervisor) - queuePaymentRequests := payloads.QueuePaymentRequests(paymentRequests, officeUsers, officeUser, isSupervisor, isHQrole) + queuePaymentRequests := payloads.QueuePaymentRequests(paymentRequests, officeUsers, officeUser, officeUsersSafety) result := &ghcmessages.QueuePaymentRequestsResult{ TotalCount: int64(count), @@ -412,40 +449,46 @@ func (h GetServicesCounselingQueueHandler) Handle( ListOrderParams.PerPage = models.Int64Pointer(20) } - if params.ViewAsGBLOC != nil && appCtx.Session().Roles.HasRole(roles.RoleTypeHQ) { - ListOrderParams.ViewAsGBLOC = params.ViewAsGBLOC - } - - moves, count, err := h.OrderFetcher.ListOrders( - appCtx, - appCtx.Session().OfficeUserID, - roles.RoleTypeServicesCounselor, - &ListOrderParams, - ) - if err != nil { - appCtx.Logger(). - Error("error fetching list of moves for office user", zap.Error(err)) - return queues.NewGetServicesCounselingQueueInternalServerError(), err - } - var officeUser models.OfficeUser + var assignedGblocs []string + var err error if appCtx.Session().OfficeUserID != uuid.Nil { - officeUser, err = h.OfficeUserFetcherPop.FetchOfficeUserByID(appCtx, appCtx.Session().OfficeUserID) + officeUser, err = h.OfficeUserFetcherPop.FetchOfficeUserByIDWithTransportationOfficeAssignments(appCtx, appCtx.Session().OfficeUserID) if err != nil { appCtx.Logger().Error("Error retrieving office_user", zap.Error(err)) return queues.NewGetServicesCounselingQueueInternalServerError(), err } + + assignedGblocs = models.GetAssignedGBLOCs(officeUser) + } + + if params.ViewAsGBLOC != nil && (appCtx.Session().Roles.HasRole(roles.RoleTypeHQ) || slices.Contains(assignedGblocs, *params.ViewAsGBLOC)) { + ListOrderParams.ViewAsGBLOC = params.ViewAsGBLOC } privileges, err := models.FetchPrivilegesForUser(appCtx.DB(), appCtx.Session().UserID) if err != nil { appCtx.Logger().Error("Error retreiving user privileges", zap.Error(err)) } + officeUser.User.Privileges = privileges + officeUser.User.Roles = appCtx.Session().Roles - isSupervisor := privileges.HasPrivilege(models.PrivilegeTypeSupervisor) var officeUsers models.OfficeUsers - - if isSupervisor { + var officeUsersSafety models.OfficeUsers + + if privileges.HasPrivilege(models.PrivilegeTypeSupervisor) { + if privileges.HasPrivilege(models.PrivilegeTypeSafety) { + officeUsersSafety, err = h.OfficeUserFetcherPop.FetchSafetyMoveOfficeUsersByRoleAndOffice( + appCtx, + roles.RoleTypeServicesCounselor, + officeUser.TransportationOfficeID, + ) + if err != nil { + appCtx.Logger(). + Error("error fetching safety move office users", zap.Error(err)) + return queues.NewGetMovesQueueInternalServerError(), err + } + } officeUsers, err = h.OfficeUserFetcherPop.FetchOfficeUsersByRoleAndOffice( appCtx, roles.RoleTypeServicesCounselor, @@ -461,6 +504,19 @@ func (h GetServicesCounselingQueueHandler) Handle( return queues.NewGetServicesCounselingQueueInternalServerError(), err } + moves, count, err := h.OrderFetcher.ListOrders( + appCtx, + appCtx.Session().OfficeUserID, + roles.RoleTypeServicesCounselor, + &ListOrderParams, + ) + + if err != nil { + appCtx.Logger(). + Error("error fetching list of moves for office user", zap.Error(err)) + return queues.NewGetServicesCounselingQueueInternalServerError(), err + } + // if the SC/office user is accessing the queue, we need to unlock move/moves they have locked if appCtx.Session().IsOfficeUser() { officeUserID := appCtx.Session().OfficeUserID @@ -481,9 +537,8 @@ func (h GetServicesCounselingQueueHandler) Handle( appCtx.Logger().Error(fmt.Sprintf("failed to unlock moves for office user ID: %s", officeUserID), zap.Error(err)) } } - isHQrole := appCtx.Session().Roles.HasRole(roles.RoleTypeHQ) - queueMoves := payloads.QueueMoves(moves, officeUsers, &requestedPpmStatus, roles.RoleTypeServicesCounselor, officeUser, isSupervisor, isHQrole) + queueMoves := payloads.QueueMoves(moves, officeUsers, &requestedPpmStatus, officeUser, officeUsersSafety) result := &ghcmessages.QueueMovesResult{ Page: *ListOrderParams.Page, @@ -500,6 +555,7 @@ func (h GetServicesCounselingQueueHandler) Handle( type GetServicesCounselingOriginListHandler struct { handlers.HandlerConfig services.OrderFetcher + services.OfficeUserFetcherPop } // Handle returns the list of origin list for the services counselor @@ -527,11 +583,29 @@ func (h GetServicesCounselingOriginListHandler) Handle( ListOrderParams.Status = []string{string(models.MoveStatusNeedsServiceCounseling)} } + var officeUser models.OfficeUser + var assignedGblocs []string + var err error + if appCtx.Session().OfficeUserID != uuid.Nil { + officeUser, err = h.OfficeUserFetcherPop.FetchOfficeUserByIDWithTransportationOfficeAssignments(appCtx, appCtx.Session().OfficeUserID) + if err != nil { + appCtx.Logger().Error("Error retrieving office_user", zap.Error(err)) + return queues.NewGetServicesCounselingOriginListInternalServerError(), err + } + + assignedGblocs = models.GetAssignedGBLOCs(officeUser) + } + + if params.ViewAsGBLOC != nil && (appCtx.Session().Roles.HasRole(roles.RoleTypeHQ) || slices.Contains(assignedGblocs, *params.ViewAsGBLOC)) { + ListOrderParams.ViewAsGBLOC = params.ViewAsGBLOC + } + moves, err := h.OrderFetcher.ListAllOrderLocations( appCtx, appCtx.Session().OfficeUserID, &ListOrderParams, ) + if err != nil { appCtx.Logger(). Error("error fetching list of moves for office user", zap.Error(err)) diff --git a/pkg/handlers/ghcapi/re_service_items.go b/pkg/handlers/ghcapi/re_service_items.go new file mode 100644 index 00000000000..fee38303b34 --- /dev/null +++ b/pkg/handlers/ghcapi/re_service_items.go @@ -0,0 +1,32 @@ +package ghcapi + +import ( + "github.com/go-openapi/runtime/middleware" + "go.uber.org/zap" + + "github.com/transcom/mymove/pkg/appcontext" + reserviceitemsop "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/re_service_items" + "github.com/transcom/mymove/pkg/handlers" + "github.com/transcom/mymove/pkg/handlers/ghcapi/internal/payloads" + "github.com/transcom/mymove/pkg/services" +) + +// GetReServiceItemsHandler returns a list of service items +type GetReServiceItemsHandler struct { + handlers.HandlerConfig + services.ServiceItemListFetcher +} + +func (h GetReServiceItemsHandler) Handle(params reserviceitemsop.GetAllReServiceItemsParams) middleware.Responder { + return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, + func(appCtx appcontext.AppContext) (middleware.Responder, error) { + + serviceItemList, err := h.FetchServiceItemList(appCtx) + if err != nil { + appCtx.Logger().Error("Error fetching Service Item List", zap.Error(err)) + return reserviceitemsop.NewGetAllReServiceItemsInternalServerError(), err + } + returnPayload := payloads.ReServiceItems(*serviceItemList) + return reserviceitemsop.NewGetAllReServiceItemsOK().WithPayload(returnPayload), nil + }) +} diff --git a/pkg/handlers/internalapi/api.go b/pkg/handlers/internalapi/api.go index 91673f1f838..cf7833eb80a 100644 --- a/pkg/handlers/internalapi/api.go +++ b/pkg/handlers/internalapi/api.go @@ -110,7 +110,10 @@ func NewInternalAPI(handlerConfig handlers.HandlerConfig) *internalops.MymoveAPI internalAPI.FeatureFlagsBooleanFeatureFlagForUserHandler = BooleanFeatureFlagsForUserHandler{handlerConfig} internalAPI.FeatureFlagsVariantFeatureFlagForUserHandler = VariantFeatureFlagsForUserHandler{handlerConfig} - internalAPI.UsersShowLoggedInUserHandler = ShowLoggedInUserHandler{handlerConfig, officeuser.NewOfficeUserFetcherPop()} + internalAPI.UsersShowLoggedInUserHandler = ShowLoggedInUserHandler{ + handlerConfig, + officeuser.NewOfficeUserFetcherPop(), + } internalAPI.CertificationCreateSignedCertificationHandler = CreateSignedCertificationHandler{handlerConfig} internalAPI.CertificationIndexSignedCertificationHandler = IndexSignedCertificationsHandler{handlerConfig} diff --git a/pkg/handlers/internalapi/duty_locations.go b/pkg/handlers/internalapi/duty_locations.go index 69368252172..d3c7d9e0373 100644 --- a/pkg/handlers/internalapi/duty_locations.go +++ b/pkg/handlers/internalapi/duty_locations.go @@ -50,18 +50,30 @@ func (h SearchDutyLocationsHandler) Handle(params locationop.SearchDutyLocations /** Feature Flag - Alaska - Determines if AK can be included/excluded **/ isAlaskaEnabled := false - featureFlagName := "enable_alaska" - flag, err := h.FeatureFlagFetcher().GetBooleanFlagForUser(context.TODO(), appCtx, featureFlagName, map[string]string{}) + akFeatureFlagName := "enable_alaska" + flag, err := h.FeatureFlagFetcher().GetBooleanFlagForUser(context.TODO(), appCtx, akFeatureFlagName, map[string]string{}) if err != nil { - appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", featureFlagName), zap.Error(err)) + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", akFeatureFlagName), zap.Error(err)) } else { isAlaskaEnabled = flag.Match } + /** Feature Flag - Hawaii - Determines if HI can be included/excluded **/ + isHawaiiEnabled := false + hiFeatureFlagName := "enable_hawaii" + flag, err = h.FeatureFlagFetcher().GetBooleanFlagForUser(context.TODO(), appCtx, hiFeatureFlagName, map[string]string{}) + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", hiFeatureFlagName), zap.Error(err)) + } else { + isHawaiiEnabled = flag.Match + } + + // build states to exlude filter list statesToExclude := make([]string, 0) if !isAlaskaEnabled { - // HI locations will also be excluded as part of AK embargo statesToExclude = append(statesToExclude, "AK") + } + if !isHawaiiEnabled { statesToExclude = append(statesToExclude, "HI") } diff --git a/pkg/handlers/internalapi/internal/payloads/model_to_payload.go b/pkg/handlers/internalapi/internal/payloads/model_to_payload.go index 38beccf4ef9..d701b543607 100644 --- a/pkg/handlers/internalapi/internal/payloads/model_to_payload.go +++ b/pkg/handlers/internalapi/internal/payloads/model_to_payload.go @@ -301,6 +301,32 @@ func TransportationOffices(transportationOffices models.TransportationOffices) i return payload } +// TransportationOffice internal payload +func TransportationOfficeAssignment(toa models.TransportationOfficeAssignment) *internalmessages.TransportationOfficeAssignment { + if toa.ID == uuid.Nil || toa.TransportationOfficeID == uuid.Nil { + return nil + } + payload := &internalmessages.TransportationOfficeAssignment{ + OfficeUserID: handlers.FmtUUID(toa.ID), + TransportationOfficeID: handlers.FmtUUID(toa.TransportationOfficeID), + TransportationOffice: TransportationOffice(toa.TransportationOffice), + PrimaryOffice: toa.PrimaryOffice, + CreatedAt: *handlers.FmtDateTime(toa.CreatedAt), + UpdatedAt: *handlers.FmtDateTime(toa.UpdatedAt), + } + return payload +} + +// TransportationOffice internal payload +func TransportationOfficeAssignments(toas models.TransportationOfficeAssignments) []*internalmessages.TransportationOfficeAssignment { + payload := make([]*internalmessages.TransportationOfficeAssignment, len(toas)) + + for i, toa := range toas { + payload[i] = TransportationOfficeAssignment(toa) + } + return payload +} + func CounselingOffices(counselingOffices models.TransportationOffices) internalmessages.CounselingOffices { payload := make(internalmessages.CounselingOffices, len(counselingOffices)) @@ -320,16 +346,17 @@ func OfficeUser(officeUser *models.OfficeUser) *internalmessages.OfficeUser { } payload := &internalmessages.OfficeUser{ - ID: strfmt.UUID(officeUser.ID.String()), - UserID: strfmt.UUID(officeUser.UserID.String()), - Email: &officeUser.Email, - FirstName: &officeUser.FirstName, - LastName: &officeUser.LastName, - MiddleName: officeUser.MiddleInitials, - Telephone: &officeUser.Telephone, - TransportationOffice: TransportationOffice(officeUser.TransportationOffice), - CreatedAt: strfmt.DateTime(officeUser.CreatedAt), - UpdatedAt: strfmt.DateTime(officeUser.UpdatedAt), + ID: strfmt.UUID(officeUser.ID.String()), + UserID: strfmt.UUID(officeUser.UserID.String()), + Email: &officeUser.Email, + FirstName: &officeUser.FirstName, + LastName: &officeUser.LastName, + MiddleName: officeUser.MiddleInitials, + Telephone: &officeUser.Telephone, + TransportationOffice: TransportationOffice(officeUser.TransportationOffice), + TransportationOfficeAssignments: TransportationOfficeAssignments(officeUser.TransportationOfficeAssignments), + CreatedAt: strfmt.DateTime(officeUser.CreatedAt), + UpdatedAt: strfmt.DateTime(officeUser.UpdatedAt), } return payload diff --git a/pkg/handlers/internalapi/internal/payloads/model_to_payload_test.go b/pkg/handlers/internalapi/internal/payloads/model_to_payload_test.go index 0ac8934be7f..059cfab5dd5 100644 --- a/pkg/handlers/internalapi/internal/payloads/model_to_payload_test.go +++ b/pkg/handlers/internalapi/internal/payloads/model_to_payload_test.go @@ -16,7 +16,8 @@ func (suite *PayloadsSuite) TestFetchPPMShipment() { state := "FL" postalcode := "33621" country := models.Country{ - Country: "US", + Country: "US", + CountryName: "United States", } county := "HILLSBOROUGH" diff --git a/pkg/handlers/internalapi/move_queue_items.go b/pkg/handlers/internalapi/move_queue_items.go index d42b1fa2026..071ae2fba79 100644 --- a/pkg/handlers/internalapi/move_queue_items.go +++ b/pkg/handlers/internalapi/move_queue_items.go @@ -27,7 +27,7 @@ func payloadForMoveQueueItem(MoveQueueItem models.MoveQueueItem) *internalmessag Locator: models.StringPointer(MoveQueueItem.Locator), Status: models.StringPointer(MoveQueueItem.Status), PpmStatus: handlers.FmtStringPtr(MoveQueueItem.PpmStatus), - OrdersType: models.StringPointer(MoveQueueItem.OrdersType), + OrdersType: MoveQueueItem.OrdersType, MoveDate: handlers.FmtDatePtr(MoveQueueItem.MoveDate), SubmittedDate: handlers.FmtDateTimePtr(MoveQueueItem.SubmittedDate), LastModifiedDate: handlers.FmtDateTime(MoveQueueItem.LastModifiedDate), @@ -38,7 +38,7 @@ func payloadForMoveQueueItem(MoveQueueItem models.MoveQueueItem) *internalmessag DestinationGbloc: handlers.FmtStringPtr(MoveQueueItem.DestinationGBLOC), DeliveredDate: handlers.FmtDateTimePtr(MoveQueueItem.DeliveredDate), InvoiceApprovedDate: handlers.FmtDateTimePtr(MoveQueueItem.InvoiceApprovedDate), - WeightAllotment: payloadForWeightAllotmentModel(models.GetWeightAllotment(*MoveQueueItem.Grade)), + WeightAllotment: payloadForWeightAllotmentModel(models.GetWeightAllotment(*MoveQueueItem.Grade, *MoveQueueItem.OrdersType)), BranchOfService: handlers.FmtString(MoveQueueItem.BranchOfService), ActualMoveDate: handlers.FmtDatePtr(MoveQueueItem.ActualMoveDate), OriginalMoveDate: handlers.FmtDatePtr(MoveQueueItem.OriginalMoveDate), diff --git a/pkg/handlers/internalapi/moves_test.go b/pkg/handlers/internalapi/moves_test.go index 2d32bc6eca9..40e2e9283f5 100644 --- a/pkg/handlers/internalapi/moves_test.go +++ b/pkg/handlers/internalapi/moves_test.go @@ -291,7 +291,7 @@ func (suite *HandlerSuite) TestSubmitMoveForApprovalHandler() { suite.NoError(err) // And: Returned query to have a submitted status - suite.Assertions.Equal(internalmessages.MoveStatusSUBMITTED, okResponse.Payload.Status) + suite.Assertions.Equal(internalmessages.MoveStatusNEEDSSERVICECOUNSELING, okResponse.Payload.Status) suite.Assertions.NotNil(okResponse.Payload.SubmittedAt) // And: SignedCertification was created diff --git a/pkg/handlers/internalapi/mto_shipment_test.go b/pkg/handlers/internalapi/mto_shipment_test.go index a45c7427832..fca0de04f0b 100644 --- a/pkg/handlers/internalapi/mto_shipment_test.go +++ b/pkg/handlers/internalapi/mto_shipment_test.go @@ -366,6 +366,12 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandlerV1() { mock.AnythingOfType("*models.PPMShipment")). Return(nil, nil, nil).Once() + ppmEstimator.On("MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(nil, nil) + suite.Nil(params.Body.Validate(strfmt.Default)) response := subtestData.handler.Handle(params) @@ -449,6 +455,12 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandlerV1() { mock.AnythingOfType("*models.PPMShipment")). Return(nil, nil, nil).Once() + ppmEstimator.On("MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(nil, nil) + suite.Nil(params.Body.Validate(strfmt.Default)) response := subtestData.handler.Handle(params) @@ -1322,6 +1334,12 @@ func (suite *HandlerSuite) TestUpdateMTOShipmentHandler() { mock.AnythingOfType("*models.PPMShipment")). Return(tc.estimatedIncentive, nil, nil).Once() + ppmEstimator.On("MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(nil, nil) + ppmEstimator.On("FinalIncentiveWithDefaultChecks", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("models.PPMShipment"), diff --git a/pkg/handlers/internalapi/orders.go b/pkg/handlers/internalapi/orders.go index 3d66cbf98f0..e87a286d7b6 100644 --- a/pkg/handlers/internalapi/orders.go +++ b/pkg/handlers/internalapi/orders.go @@ -72,6 +72,18 @@ func payloadForOrdersModel(storer storage.FileStorer, order models.Order) (*inte dBAuthorizedWeight = models.Int64Pointer(int64(*order.Entitlement.AuthorizedWeight())) entitlement.ProGear = models.Int64Pointer(int64(order.Entitlement.ProGearWeight)) entitlement.ProGearSpouse = models.Int64Pointer(int64(order.Entitlement.ProGearWeightSpouse)) + if order.Entitlement.AccompaniedTour != nil { + entitlement.AccompaniedTour = models.BoolPointer(*order.Entitlement.AccompaniedTour) + } + if order.Entitlement.DependentsUnderTwelve != nil { + entitlement.DependentsUnderTwelve = models.Int64Pointer(int64(*order.Entitlement.DependentsUnderTwelve)) + } + if order.Entitlement.DependentsTwelveAndOver != nil { + entitlement.DependentsTwelveAndOver = models.Int64Pointer(int64(*order.Entitlement.DependentsTwelveAndOver)) + } + if order.Entitlement.UBAllowance != nil { + entitlement.UbAllowance = models.Int64Pointer(int64(*order.Entitlement.UBAllowance)) + } } var originDutyLocation models.DutyLocation originDutyLocation = models.DutyLocation{} @@ -160,6 +172,17 @@ func (h CreateOrdersHandler) Handle(params ordersop.CreateOrdersParams) middlewa return handlers.ResponseForError(appCtx.Logger(), err), err } + var dependentsTwelveAndOver *int + var dependentsUnderTwelve *int + if payload.DependentsTwelveAndOver != nil { + // Convert from int64 to int + dependentsTwelveAndOver = models.IntPointer(int(*payload.DependentsTwelveAndOver)) + } + if payload.DependentsUnderTwelve != nil { + // Convert from int64 to int + dependentsUnderTwelve = models.IntPointer(int(*payload.DependentsUnderTwelve)) + } + originDutyLocationGBLOC, err := models.FetchGBLOCForPostalCode(appCtx.DB(), originDutyLocation.Address.PostalCode) if err != nil { switch err { @@ -171,23 +194,34 @@ func (h CreateOrdersHandler) Handle(params ordersop.CreateOrdersParams) middlewa } grade := payload.Grade - weightAllotment := models.GetWeightAllotment(*grade) + // Calculate the entitlement for the order + ordersType := payload.OrdersType + weightAllotment := models.GetWeightAllotment(*grade, *ordersType) weight := weightAllotment.TotalWeightSelf if *payload.HasDependents { weight = weightAllotment.TotalWeightSelfPlusDependents } + // Calculate UB allowance for the order entitlement + unaccompaniedBaggageAllowance, err := models.GetUBWeightAllowance(appCtx, originDutyLocation.Address.IsOconus, newDutyLocation.Address.IsOconus, serviceMember.Affiliation, grade, payload.OrdersType, payload.HasDependents, payload.AccompaniedTour, dependentsUnderTwelve, dependentsTwelveAndOver) + if err == nil { + weightAllotment.UnaccompaniedBaggageAllowance = unaccompaniedBaggageAllowance + } // Assign default SIT allowance based on customer type. // We only have service members right now, but once we introduce more, this logic will have to change. sitDaysAllowance := models.DefaultServiceMemberSITDaysAllowance entitlement := models.Entitlement{ - DependentsAuthorized: payload.HasDependents, - DBAuthorizedWeight: models.IntPointer(weight), - StorageInTransit: models.IntPointer(sitDaysAllowance), - ProGearWeight: weightAllotment.ProGearWeight, - ProGearWeightSpouse: weightAllotment.ProGearWeightSpouse, + DependentsAuthorized: payload.HasDependents, + AccompaniedTour: payload.AccompaniedTour, + DependentsUnderTwelve: dependentsUnderTwelve, + DependentsTwelveAndOver: dependentsTwelveAndOver, + DBAuthorizedWeight: models.IntPointer(weight), + StorageInTransit: models.IntPointer(sitDaysAllowance), + ProGearWeight: weightAllotment.ProGearWeight, + ProGearWeightSpouse: weightAllotment.ProGearWeightSpouse, + UBAllowance: &weightAllotment.UnaccompaniedBaggageAllowance, } /* @@ -196,8 +230,15 @@ func (h CreateOrdersHandler) Handle(params ordersop.CreateOrdersParams) middlewa in move_dats.go, move_weights, and move_submitted, etc */ - if saveEntitlementErr := appCtx.DB().Save(&entitlement); saveEntitlementErr != nil { - return handlers.ResponseForError(appCtx.Logger(), saveEntitlementErr), saveEntitlementErr + verrs, err := appCtx.DB().ValidateAndSave(&entitlement) + if err != nil { + appCtx.Logger().Error("Error saving customer entitlement", zap.Error(err)) + return handlers.ResponseForError(appCtx.Logger(), err), err + } + + if verrs.HasAny() { + appCtx.Logger().Error("Validation error saving customer entitlement", zap.Any("errors", verrs.Errors)) + return handlers.ResponseForVErrors(appCtx.Logger(), verrs, nil), nil } var deptIndicator *string @@ -342,7 +383,6 @@ func (h UpdateOrdersHandler) Handle(params ordersop.UpdateOrdersParams) middlewa order.OriginDutyLocationID = &originDutyLocationID if payload.MoveID != "" { - moveID, err := uuid.FromString(payload.MoveID.String()) if err != nil { return handlers.ResponseForError(appCtx.Logger(), err), err @@ -385,9 +425,18 @@ func (h UpdateOrdersHandler) Handle(params ordersop.UpdateOrdersParams) middlewa order.TAC = payload.Tac order.SAC = payload.Sac - // Check if the grade is receiving an update - if order.Grade != payload.Grade { - weightAllotment := models.GetWeightAllotment(*payload.Grade) + serviceMemberID, err := uuid.FromString(payload.ServiceMemberID.String()) + if err != nil { + return handlers.ResponseForError(appCtx.Logger(), err), err + } + serviceMember, err := models.FetchServiceMemberForUser(appCtx.DB(), appCtx.Session(), serviceMemberID) + if err != nil { + return handlers.ResponseForError(appCtx.Logger(), err), err + } + + // 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) weight := weightAllotment.TotalWeightSelf if *payload.HasDependents { weight = weightAllotment.TotalWeightSelfPlusDependents @@ -396,13 +445,41 @@ func (h UpdateOrdersHandler) Handle(params ordersop.UpdateOrdersParams) middlewa // Assign default SIT allowance based on customer type. // We only have service members right now, but once we introduce more, this logic will have to change. sitDaysAllowance := models.DefaultServiceMemberSITDaysAllowance + var dependentsTwelveAndOver *int + var dependentsUnderTwelve *int + if payload.DependentsTwelveAndOver != nil { + // Convert from int64 to int + dependentsTwelveAndOver = models.IntPointer(int(*payload.DependentsTwelveAndOver)) + } + if payload.DependentsUnderTwelve != nil { + // Convert from int64 to int + dependentsUnderTwelve = models.IntPointer(int(*payload.DependentsUnderTwelve)) + } + var grade *internalmessages.OrderPayGrade + if payload.Grade != nil { + grade = payload.Grade + } else { + grade = order.Grade + } + + // Calculate UB allowance for the order entitlement + if order.Entitlement != nil { + unaccompaniedBaggageAllowance, err := models.GetUBWeightAllowance(appCtx, order.OriginDutyLocation.Address.IsOconus, order.NewDutyLocation.Address.IsOconus, serviceMember.Affiliation, grade, payload.OrdersType, payload.HasDependents, payload.AccompaniedTour, dependentsUnderTwelve, dependentsTwelveAndOver) + if err == nil { + weightAllotment.UnaccompaniedBaggageAllowance = unaccompaniedBaggageAllowance + } + } entitlement := models.Entitlement{ - DependentsAuthorized: payload.HasDependents, - DBAuthorizedWeight: models.IntPointer(weight), - StorageInTransit: models.IntPointer(sitDaysAllowance), - ProGearWeight: weightAllotment.ProGearWeight, - ProGearWeightSpouse: weightAllotment.ProGearWeightSpouse, + DependentsAuthorized: payload.HasDependents, + DBAuthorizedWeight: models.IntPointer(weight), + StorageInTransit: models.IntPointer(sitDaysAllowance), + ProGearWeight: weightAllotment.ProGearWeight, + ProGearWeightSpouse: weightAllotment.ProGearWeightSpouse, + DependentsUnderTwelve: dependentsUnderTwelve, + DependentsTwelveAndOver: dependentsTwelveAndOver, + AccompaniedTour: payload.AccompaniedTour, + UBAllowance: &weightAllotment.UnaccompaniedBaggageAllowance, } /* @@ -465,6 +542,45 @@ func (h UpdateOrdersHandler) Handle(params ordersop.UpdateOrdersParams) middlewa }) } +// Helper func for the UpdateOrdersHandler to check if the entitlement has changed from the new payload +// This handles the nil checks and value comparisons outside of the handler func for organization +func hasEntitlementChanged(order models.Order, payloadOrderType *internalmessages.OrdersType, payloadPayGrade *internalmessages.OrderPayGrade, payloadDependentsUnderTwelve *int64, payloadDependentsTwelveAndOver *int64, payloadAccompaniedTour *bool) bool { + // Check pay grade + if (order.Grade == nil && payloadPayGrade != nil) || (order.Grade != nil && payloadPayGrade == nil) || (order.Grade != nil && payloadPayGrade != nil && *order.Grade != *payloadPayGrade) { + return true + } + // check orders type + if (order.OrdersType == "" && payloadOrderType != nil) || (order.OrdersType != "" && payloadPayGrade == nil) || (order.OrdersType != "" && payloadPayGrade != nil && internalmessages.OrderPayGrade(order.OrdersType) != *payloadPayGrade) { + return true + } + // Check entitlement + if order.Entitlement != nil { + // Check dependents under twelve + if (order.Entitlement.DependentsUnderTwelve == nil && payloadDependentsUnderTwelve != nil) || + (order.Entitlement.DependentsUnderTwelve != nil && payloadDependentsUnderTwelve == nil) || + (order.Entitlement.DependentsUnderTwelve != nil && payloadDependentsUnderTwelve != nil && *order.Entitlement.DependentsUnderTwelve != int(*payloadDependentsUnderTwelve)) { + return true + } + + // Check dependents twelve and over + if (order.Entitlement.DependentsTwelveAndOver == nil && payloadDependentsTwelveAndOver != nil) || + (order.Entitlement.DependentsTwelveAndOver != nil && payloadDependentsTwelveAndOver == nil) || + (order.Entitlement.DependentsTwelveAndOver != nil && payloadDependentsTwelveAndOver != nil && *order.Entitlement.DependentsTwelveAndOver != int(*payloadDependentsTwelveAndOver)) { + return true + } + + // Check accompanied tour + if (order.Entitlement.AccompaniedTour == nil && payloadAccompaniedTour != nil) || + (order.Entitlement.AccompaniedTour != nil && payloadAccompaniedTour == nil) || + (order.Entitlement.AccompaniedTour != nil && payloadAccompaniedTour != nil && *order.Entitlement.AccompaniedTour != *payloadAccompaniedTour) { + return true + } + + } + + return false +} + // UploadAmendedOrdersHandler uploads amended orders to an order via PATCH /orders/{orderId}/upload_amended_orders type UploadAmendedOrdersHandler struct { handlers.HandlerConfig diff --git a/pkg/handlers/internalapi/orders_test.go b/pkg/handlers/internalapi/orders_test.go index 3f9a9bfc730..b2ad7897f8a 100644 --- a/pkg/handlers/internalapi/orders_test.go +++ b/pkg/handlers/internalapi/orders_test.go @@ -25,74 +25,193 @@ 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 { + test string + isOconus bool + }{ + {test: "Can create OCONUS order", isOconus: true}, + {test: "Can create CONUS order", isOconus: false}, + } + for _, tc := range testCases { + address := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + IsOconus: &tc.isOconus, + }, + }, + }, nil) + + originDutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + Name: factory.MakeRandomString(8), + }, + }, + { + Model: address, + LinkOnly: true, + }, + }, nil) + + dutyLocation := factory.FetchOrBuildCurrentDutyLocation(suite.DB()) + factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), dutyLocation.Address.PostalCode, "KKFA") + factory.FetchOrBuildDefaultContractor(suite.DB(), nil, nil) + + req := httptest.NewRequest("POST", "/orders", nil) + req = suite.AuthenticateRequest(req, sm) + + hasDependents := true + spouseHasProGear := true + issueDate := time.Date(2018, time.March, 10, 0, 0, 0, 0, time.UTC) + reportByDate := time.Date(2018, time.August, 1, 0, 0, 0, 0, time.UTC) + ordersType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION + deptIndicator := internalmessages.DeptIndicatorAIRANDSPACEFORCE + payload := &internalmessages.CreateUpdateOrders{ + HasDependents: handlers.FmtBool(hasDependents), + SpouseHasProGear: handlers.FmtBool(spouseHasProGear), + IssueDate: handlers.FmtDate(issueDate), + ReportByDate: handlers.FmtDate(reportByDate), + OrdersType: internalmessages.NewOrdersType(ordersType), + OriginDutyLocationID: *handlers.FmtUUIDPtr(&originDutyLocation.ID), + NewDutyLocationID: handlers.FmtUUID(dutyLocation.ID), + ServiceMemberID: handlers.FmtUUID(sm.ID), + OrdersNumber: handlers.FmtString("123456"), + Tac: handlers.FmtString("E19A"), + Sac: handlers.FmtString("SacNumber"), + DepartmentIndicator: internalmessages.NewDeptIndicator(deptIndicator), + Grade: models.ServiceMemberGradeE1.Pointer(), + } + if tc.isOconus { + payload.AccompaniedTour = models.BoolPointer(true) + payload.DependentsTwelveAndOver = models.Int64Pointer(5) + payload.DependentsUnderTwelve = models.Int64Pointer(5) + } + + params := ordersop.CreateOrdersParams{ + HTTPRequest: req, + CreateOrders: payload, + } + + fakeS3 := storageTest.NewFakeS3Storage(true) + handlerConfig := suite.HandlerConfig() + handlerConfig.SetFileStorer(fakeS3) + createHandler := CreateOrdersHandler{handlerConfig} + + response := createHandler.Handle(params) + + suite.Assertions.IsType(&ordersop.CreateOrdersCreated{}, response) + okResponse := response.(*ordersop.CreateOrdersCreated) + orderID := okResponse.Payload.ID.String() + createdOrder, _ := models.FetchOrder(suite.DB(), uuid.FromStringOrNil(orderID)) + var createdEntitlement models.Entitlement + err := suite.DB().Find(&createdEntitlement, createdOrder.EntitlementID) + suite.NoError(err) + suite.NotEmpty(createdEntitlement) + suite.Assertions.Equal(sm.ID.String(), okResponse.Payload.ServiceMemberID.String()) + suite.Assertions.Len(okResponse.Payload.Moves, 1) + suite.Assertions.Equal(ordersType, *okResponse.Payload.OrdersType) + suite.Assertions.Equal(handlers.FmtString("123456"), okResponse.Payload.OrdersNumber) + suite.Assertions.Equal(handlers.FmtString("E19A"), okResponse.Payload.Tac) + suite.Assertions.Equal(handlers.FmtString("SacNumber"), okResponse.Payload.Sac) + suite.Assertions.Equal(&deptIndicator, okResponse.Payload.DepartmentIndicator) + suite.Assertions.Equal(*models.Int64Pointer(8000), *okResponse.Payload.AuthorizedWeight) + suite.NotNil(&createdOrder.Entitlement) + suite.NotEmpty(createdOrder.SupplyAndServicesCostEstimate) + suite.NotEmpty(createdOrder.PackingAndShippingInstructions) + suite.NotEmpty(createdOrder.MethodOfPayment) + suite.NotEmpty(createdOrder.NAICS) + if tc.isOconus { + suite.NotNil(createdEntitlement.AccompaniedTour) + suite.NotNil(createdEntitlement.DependentsTwelveAndOver) + suite.NotNil(createdEntitlement.DependentsUnderTwelve) + } else { + suite.Nil(createdEntitlement.AccompaniedTour) + suite.Nil(createdEntitlement.DependentsTwelveAndOver) + suite.Nil(createdEntitlement.DependentsUnderTwelve) + } - originDutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ - { - Model: models.DutyLocation{ - Name: "Not Yuma AFB", + } + }) + + suite.Run("properly handles entitlement validation", func() { + address := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + IsOconus: models.BoolPointer(true), + }, }, - }, - }, nil) - dutyLocation := factory.FetchOrBuildCurrentDutyLocation(suite.DB()) - factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), dutyLocation.Address.PostalCode, "KKFA") - factory.FetchOrBuildDefaultContractor(suite.DB(), nil, nil) + }, nil) - req := httptest.NewRequest("POST", "/orders", nil) - req = suite.AuthenticateRequest(req, sm) + originDutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + Name: factory.MakeRandomString(8), + }, + }, + { + Model: address, + LinkOnly: true, + }, + }, nil) + + dutyLocation := factory.FetchOrBuildCurrentDutyLocation(suite.DB()) + factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), dutyLocation.Address.PostalCode, "KKFA") + factory.FetchOrBuildDefaultContractor(suite.DB(), nil, nil) + + req := httptest.NewRequest("POST", "/orders", nil) + req = suite.AuthenticateRequest(req, sm) + + hasDependents := true + spouseHasProGear := true + issueDate := time.Date(2018, time.March, 10, 0, 0, 0, 0, time.UTC) + reportByDate := time.Date(2018, time.August, 1, 0, 0, 0, 0, time.UTC) + ordersType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION + deptIndicator := internalmessages.DeptIndicatorAIRANDSPACEFORCE + payload := &internalmessages.CreateUpdateOrders{ + HasDependents: handlers.FmtBool(hasDependents), + SpouseHasProGear: handlers.FmtBool(spouseHasProGear), + IssueDate: handlers.FmtDate(issueDate), + ReportByDate: handlers.FmtDate(reportByDate), + OrdersType: internalmessages.NewOrdersType(ordersType), + OriginDutyLocationID: *handlers.FmtUUIDPtr(&originDutyLocation.ID), + NewDutyLocationID: handlers.FmtUUID(dutyLocation.ID), + ServiceMemberID: handlers.FmtUUID(sm.ID), + OrdersNumber: handlers.FmtString("123456"), + Tac: handlers.FmtString("E19A"), + Sac: handlers.FmtString("SacNumber"), + DepartmentIndicator: internalmessages.NewDeptIndicator(deptIndicator), + Grade: models.ServiceMemberGradeE1.Pointer(), + DependentsTwelveAndOver: models.Int64Pointer(-2), + } - hasDependents := true - spouseHasProGear := true - issueDate := time.Date(2018, time.March, 10, 0, 0, 0, 0, time.UTC) - reportByDate := time.Date(2018, time.August, 1, 0, 0, 0, 0, time.UTC) - ordersType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION - deptIndicator := internalmessages.DeptIndicatorAIRANDSPACEFORCE - payload := &internalmessages.CreateUpdateOrders{ - HasDependents: handlers.FmtBool(hasDependents), - SpouseHasProGear: handlers.FmtBool(spouseHasProGear), - IssueDate: handlers.FmtDate(issueDate), - ReportByDate: handlers.FmtDate(reportByDate), - OrdersType: internalmessages.NewOrdersType(ordersType), - OriginDutyLocationID: *handlers.FmtUUIDPtr(&originDutyLocation.ID), - NewDutyLocationID: handlers.FmtUUID(dutyLocation.ID), - ServiceMemberID: handlers.FmtUUID(sm.ID), - OrdersNumber: handlers.FmtString("123456"), - Tac: handlers.FmtString("E19A"), - Sac: handlers.FmtString("SacNumber"), - DepartmentIndicator: internalmessages.NewDeptIndicator(deptIndicator), - Grade: models.ServiceMemberGradeE1.Pointer(), - } + params := ordersop.CreateOrdersParams{ + HTTPRequest: req, + CreateOrders: payload, + } - params := ordersop.CreateOrdersParams{ - HTTPRequest: req, - CreateOrders: payload, - } + fakeS3 := storageTest.NewFakeS3Storage(true) + handlerConfig := suite.HandlerConfig() + handlerConfig.SetFileStorer(fakeS3) + createHandler := CreateOrdersHandler{handlerConfig} - fakeS3 := storageTest.NewFakeS3Storage(true) - handlerConfig := suite.HandlerConfig() - handlerConfig.SetFileStorer(fakeS3) - createHandler := CreateOrdersHandler{handlerConfig} - - response := createHandler.Handle(params) - - suite.Assertions.IsType(&ordersop.CreateOrdersCreated{}, response) - okResponse := response.(*ordersop.CreateOrdersCreated) - orderID := okResponse.Payload.ID.String() - createdOrder, _ := models.FetchOrder(suite.DB(), uuid.FromStringOrNil(orderID)) - - suite.Assertions.Equal(sm.ID.String(), okResponse.Payload.ServiceMemberID.String()) - suite.Assertions.Len(okResponse.Payload.Moves, 1) - suite.Assertions.Equal(ordersType, *okResponse.Payload.OrdersType) - suite.Assertions.Equal(handlers.FmtString("123456"), okResponse.Payload.OrdersNumber) - suite.Assertions.Equal(handlers.FmtString("E19A"), okResponse.Payload.Tac) - suite.Assertions.Equal(handlers.FmtString("SacNumber"), okResponse.Payload.Sac) - suite.Assertions.Equal(&deptIndicator, okResponse.Payload.DepartmentIndicator) - suite.Assertions.Equal(*models.Int64Pointer(8000), *okResponse.Payload.AuthorizedWeight) - suite.NotNil(&createdOrder.Entitlement) - suite.NotEmpty(createdOrder.SupplyAndServicesCostEstimate) - suite.NotEmpty(createdOrder.PackingAndShippingInstructions) - suite.NotEmpty(createdOrder.MethodOfPayment) - suite.NotEmpty(createdOrder.NAICS) + response := createHandler.Handle(params) + suite.IsType(&handlers.ValidationErrorsResponse{}, response) + verrsResponse, ok := response.(*handlers.ValidationErrorsResponse) + suite.True(ok) + suite.Contains(verrsResponse.Errors, "dependents_twelve_and_over") + }) } func (suite *HandlerSuite) TestShowOrder() { @@ -138,6 +257,28 @@ func (suite *HandlerSuite) TestShowOrder() { suite.Assertions.Equal(order.SpouseHasProGear, *okResponse.Payload.SpouseHasProGear) } +func (suite *HandlerSuite) TestPayloadForOrdersModel() { + dutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + { + Model: factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress2}), + LinkOnly: true, + }, + }, nil) + order := factory.BuildOrder(suite.DB(), []factory.Customization{ + { + Model: dutyLocation, + LinkOnly: true, + Type: &factory.DutyLocations.OriginDutyLocation, + }, + }, nil) + + fakeS3 := storageTest.NewFakeS3Storage(true) + + payload, err := payloadForOrdersModel(fakeS3, order) + suite.NoError(err) + suite.NotNil(payload) +} + func setUpMockOrders() models.Order { orders := factory.BuildOrderWithoutDefaults(nil, nil, nil) @@ -443,95 +584,201 @@ func (suite *HandlerSuite) TestUploadAmendedOrdersHandlerIntegration() { } func (suite *HandlerSuite) TestUpdateOrdersHandler() { - dutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ - { - Model: models.DutyLocation{ - ProvidesServicesCounseling: false, - }, - }, - }, nil) - order := factory.BuildOrder(suite.DB(), []factory.Customization{ - { - Model: dutyLocation, - LinkOnly: true, - Type: &factory.DutyLocations.OriginDutyLocation, - }, - }, nil) - move := factory.BuildMove(suite.DB(), []factory.Customization{ - { - Model: order, - LinkOnly: true, - }}, nil) - newDutyLocation := factory.BuildDutyLocation(suite.DB(), nil, nil) - newTransportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) - newDutyLocation.TransportationOffice = newTransportationOffice - - newOrdersType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION - newOrdersNumber := "123456" - issueDate := time.Date(2018, time.March, 10, 0, 0, 0, 0, time.UTC) - reportByDate := time.Date(2018, time.August, 1, 0, 0, 0, 0, time.UTC) - deptIndicator := internalmessages.DeptIndicatorAIRANDSPACEFORCE - - payload := &internalmessages.CreateUpdateOrders{ - OrdersNumber: handlers.FmtString(newOrdersNumber), - OrdersType: &newOrdersType, - NewDutyLocationID: handlers.FmtUUID(newDutyLocation.ID), - OriginDutyLocationID: *handlers.FmtUUID(*order.OriginDutyLocationID), - IssueDate: handlers.FmtDate(issueDate), - ReportByDate: handlers.FmtDate(reportByDate), - DepartmentIndicator: &deptIndicator, - HasDependents: handlers.FmtBool(false), - SpouseHasProGear: handlers.FmtBool(false), - Grade: models.ServiceMemberGradeE4.Pointer(), - MoveID: *handlers.FmtUUID(move.ID), - CounselingOfficeID: handlers.FmtUUID(*newDutyLocation.TransportationOfficeID), - } - - path := fmt.Sprintf("/orders/%v", order.ID.String()) - req := httptest.NewRequest("PUT", path, nil) - req = suite.AuthenticateRequest(req, order.ServiceMember) - - params := ordersop.UpdateOrdersParams{ - HTTPRequest: req, - OrdersID: *handlers.FmtUUID(order.ID), - UpdateOrders: payload, - } - - fakeS3 := storageTest.NewFakeS3Storage(true) - handlerConfig := suite.HandlerConfig() - handlerConfig.SetFileStorer(fakeS3) - - handler := UpdateOrdersHandler{handlerConfig} - - response := handler.Handle(params) - - suite.IsType(&ordersop.UpdateOrdersOK{}, response) - okResponse := response.(*ordersop.UpdateOrdersOK) - - suite.NoError(okResponse.Payload.Validate(strfmt.Default)) - suite.Equal(string(newOrdersType), string(*okResponse.Payload.OrdersType)) - suite.Equal(newOrdersNumber, *okResponse.Payload.OrdersNumber) + suite.Run("Can update CONUS and OCONUS orders", func() { + testCases := []struct { + isOconus bool + }{ + {isOconus: true}, + {isOconus: false}, + } - updatedOrder, err := models.FetchOrder(suite.DB(), order.ID) - 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) - expectedUpdatedOrderAuthorizedWeight := expectedUpdatedOrderWeightAllotment.TotalWeightSelf - if *payload.HasDependents { - expectedUpdatedOrderAuthorizedWeight = expectedUpdatedOrderWeightAllotment.TotalWeightSelfPlusDependents - } + for _, tc := range testCases { + address := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + IsOconus: &tc.isOconus, + }, + }, + }, nil) + + // Set duty location to either CONUS or OCONUS + dutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + ProvidesServicesCounseling: false, + }, + }, + { + Model: address, + LinkOnly: true, + }, + }, nil) + order := factory.BuildOrder(suite.DB(), []factory.Customization{ + { + Model: dutyLocation, + LinkOnly: true, + Type: &factory.DutyLocations.OriginDutyLocation, + }, + }, nil) + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: order, + LinkOnly: true, + }}, nil) + + newDutyLocation := factory.BuildDutyLocation(suite.DB(), nil, nil) + newTransportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) + newDutyLocation.TransportationOffice = newTransportationOffice + + newOrdersType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION + newOrdersNumber := "123456" + issueDate := time.Date(2018, time.March, 10, 0, 0, 0, 0, time.UTC) + reportByDate := time.Date(2018, time.August, 1, 0, 0, 0, 0, time.UTC) + deptIndicator := internalmessages.DeptIndicatorAIRANDSPACEFORCE + + payload := &internalmessages.CreateUpdateOrders{ + OrdersNumber: handlers.FmtString(newOrdersNumber), + OrdersType: &newOrdersType, + NewDutyLocationID: handlers.FmtUUID(newDutyLocation.ID), + OriginDutyLocationID: *handlers.FmtUUID(*order.OriginDutyLocationID), + IssueDate: handlers.FmtDate(issueDate), + ReportByDate: handlers.FmtDate(reportByDate), + DepartmentIndicator: &deptIndicator, + HasDependents: handlers.FmtBool(false), + SpouseHasProGear: handlers.FmtBool(false), + Grade: models.ServiceMemberGradeE4.Pointer(), + MoveID: *handlers.FmtUUID(move.ID), + CounselingOfficeID: handlers.FmtUUID(*newDutyLocation.TransportationOfficeID), + ServiceMemberID: handlers.FmtUUID(order.ServiceMemberID), + } + // The default move factory does not include OCONUS fields, set these + // new fields conditionally for the update + if tc.isOconus { + payload.AccompaniedTour = models.BoolPointer(true) + payload.DependentsTwelveAndOver = models.Int64Pointer(5) + payload.DependentsUnderTwelve = models.Int64Pointer(5) + } + + path := fmt.Sprintf("/orders/%v", order.ID.String()) + req := httptest.NewRequest("PUT", path, nil) + req = suite.AuthenticateRequest(req, order.ServiceMember) + + params := ordersop.UpdateOrdersParams{ + HTTPRequest: req, + OrdersID: *handlers.FmtUUID(order.ID), + UpdateOrders: payload, + } + + fakeS3 := storageTest.NewFakeS3Storage(true) + handlerConfig := suite.HandlerConfig() + handlerConfig.SetFileStorer(fakeS3) + + handler := UpdateOrdersHandler{handlerConfig} + + response := handler.Handle(params) + + suite.IsType(&ordersop.UpdateOrdersOK{}, response) + okResponse := response.(*ordersop.UpdateOrdersOK) + + suite.NoError(okResponse.Payload.Validate(strfmt.Default)) + suite.Equal(string(newOrdersType), string(*okResponse.Payload.OrdersType)) + suite.Equal(newOrdersNumber, *okResponse.Payload.OrdersNumber) + + updatedOrder, err := models.FetchOrder(suite.DB(), order.ID) + 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) + expectedUpdatedOrderAuthorizedWeight := expectedUpdatedOrderWeightAllotment.TotalWeightSelf + if *payload.HasDependents { + expectedUpdatedOrderAuthorizedWeight = expectedUpdatedOrderWeightAllotment.TotalWeightSelfPlusDependents + } + + expectedOriginalOrderWeightAllotment := models.GetWeightAllotment(*order.Grade, updatedOrder.OrdersType) + expectedOriginalOrderAuthorizedWeight := expectedOriginalOrderWeightAllotment.TotalWeightSelf + if *payload.HasDependents { + expectedUpdatedOrderAuthorizedWeight = expectedOriginalOrderWeightAllotment.TotalWeightSelfPlusDependents + } + + suite.Equal(expectedUpdatedOrderAuthorizedWeight, 7000) // Ensure that when GetWeightAllotment is recalculated that it also returns 7000. This ensures that the database stored the correct information + suite.Equal(expectedOriginalOrderAuthorizedWeight, 5000) // The order was created as an E1. Ensure that the E1 authorized weight is 5000. + suite.Equal(string(newOrdersType), string(updatedOrder.OrdersType)) + // Check updated entitlement + var updatedEntitlement models.Entitlement + err = suite.DB().Find(&updatedEntitlement, updatedOrder.EntitlementID) + suite.NoError(err) + suite.NotEmpty(updatedEntitlement) + + if tc.isOconus { + suite.NotNil(updatedEntitlement.AccompaniedTour) + suite.NotNil(updatedEntitlement.DependentsTwelveAndOver) + suite.NotNil(updatedEntitlement.DependentsUnderTwelve) + } else { + suite.Nil(updatedEntitlement.AccompaniedTour) + suite.Nil(updatedEntitlement.DependentsTwelveAndOver) + suite.Nil(updatedEntitlement.DependentsUnderTwelve) + } + } + }) +} - expectedOriginalOrderWeightAllotment := models.GetWeightAllotment(*order.Grade) - expectedOriginalOrderAuthorizedWeight := expectedOriginalOrderWeightAllotment.TotalWeightSelf - if *payload.HasDependents { - expectedUpdatedOrderAuthorizedWeight = expectedOriginalOrderWeightAllotment.TotalWeightSelfPlusDependents - } +func (suite *HandlerSuite) TestEntitlementHelperFunc() { + orderGrade := internalmessages.OrderPayGrade("O-3") + int64Dependents := int64(2) + intDependents := int(int64Dependents) + suite.Run("Can fully cover the hasEntitlementChangedFunc", func() { + testCases := []struct { + order models.Order + payloadPayGrade *internalmessages.OrderPayGrade + payloadDependentsUnderTwelve *int64 + payloadDependentsTwelveAndOver *int64 + payloadAccompaniedTour *bool + shouldReturnFalse *bool + payloadOrdersType *internalmessages.OrdersType + }{ + { + order: models.Order{ + Grade: &orderGrade, + }, + }, + { + order: models.Order{ + Entitlement: &models.Entitlement{ + DependentsUnderTwelve: &intDependents, + }, + }, + }, + { + order: models.Order{ + Entitlement: &models.Entitlement{ + DependentsTwelveAndOver: &intDependents, + }, + }, + }, + { + order: models.Order{ + Entitlement: &models.Entitlement{ + AccompaniedTour: models.BoolPointer(true), + }, + }, + }, + { + order: models.Order{}, + shouldReturnFalse: models.BoolPointer(true), + }, + } + for _, tc := range testCases { + if tc.shouldReturnFalse != nil && *tc.shouldReturnFalse { + // Test should return false + suite.False(hasEntitlementChanged(tc.order, tc.payloadOrdersType, tc.payloadPayGrade, tc.payloadDependentsUnderTwelve, tc.payloadDependentsTwelveAndOver, tc.payloadAccompaniedTour)) + } else { + // Test defaults to returning true + suite.True(hasEntitlementChanged(tc.order, tc.payloadOrdersType, tc.payloadPayGrade, tc.payloadDependentsUnderTwelve, tc.payloadDependentsTwelveAndOver, tc.payloadAccompaniedTour)) + } - suite.Equal(expectedUpdatedOrderAuthorizedWeight, 7000) // Ensure that when GetWeightAllotment is recalculated that it also returns 7000. This ensures that the database stored the correct information - suite.Equal(expectedOriginalOrderAuthorizedWeight, 5000) // The order was created as an E1. Ensure that the E1 authorized weight is 5000. - suite.Equal(string(newOrdersType), string(updatedOrder.OrdersType)) + } + }) } func (suite *HandlerSuite) TestUpdateOrdersHandlerWithCounselingOffice() { @@ -578,6 +825,7 @@ func (suite *HandlerSuite) TestUpdateOrdersHandlerWithCounselingOffice() { Grade: models.ServiceMemberGradeE4.Pointer(), MoveID: *handlers.FmtUUID(move.ID), CounselingOfficeID: handlers.FmtUUID(*newDutyLocation.TransportationOfficeID), + ServiceMemberID: handlers.FmtUUID(order.ServiceMemberID), } path := fmt.Sprintf("/orders/%v", order.ID.String()) diff --git a/pkg/handlers/internalapi/users.go b/pkg/handlers/internalapi/users.go index 0245dada8a7..2df18e1332a 100644 --- a/pkg/handlers/internalapi/users.go +++ b/pkg/handlers/internalapi/users.go @@ -60,7 +60,7 @@ func (h ShowLoggedInUserHandler) Handle(params userop.ShowLoggedInUserParams) mi var officeUser models.OfficeUser var err error if appCtx.Session().OfficeUserID != uuid.Nil { - officeUser, err = h.officeUserFetcherPop.FetchOfficeUserByID(appCtx, appCtx.Session().OfficeUserID) + officeUser, err = h.officeUserFetcherPop.FetchOfficeUserByIDWithTransportationOfficeAssignments(appCtx, appCtx.Session().OfficeUserID) if err != nil { appCtx.Logger().Error("Error retrieving office_user", zap.Error(err)) return userop.NewIsLoggedInUserInternalServerError(), err @@ -73,6 +73,7 @@ func (h ShowLoggedInUserHandler) Handle(params userop.ShowLoggedInUserParams) mi Email: appCtx.Session().Email, OfficeUser: payloads.OfficeUser(&officeUser), } + decoratePayloadWithRoles(appCtx.Session(), &userPayload) decoratePayloadWithPermissions(appCtx.Session(), &userPayload) decoratePayloadWithPrivileges(appCtx, &userPayload) diff --git a/pkg/handlers/primeapi/api.go b/pkg/handlers/primeapi/api.go index b614fb13d60..25afdb43b77 100644 --- a/pkg/handlers/primeapi/api.go +++ b/pkg/handlers/primeapi/api.go @@ -45,7 +45,7 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primeoperations.MymoveAP shipmentFetcher := mtoshipment.NewMTOShipmentFetcher() moveWeights := move.NewMoveWeights(mtoshipment.NewShipmentReweighRequester()) uploadCreator := upload.NewUploadCreator(handlerConfig.FileStorer()) - serviceItemUpdater := mtoserviceitem.NewMTOServiceItemUpdater(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, shipmentFetcher, addressCreator) + serviceItemUpdater := mtoserviceitem.NewMTOServiceItemUpdater(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, shipmentFetcher, addressCreator, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer(), ghcrateengine.NewDomesticDestinationSITDeliveryPricer(), ghcrateengine.NewDomesticOriginSITFuelSurchargePricer()) userUploader, err := uploader.NewUserUploader(handlerConfig.FileStorer(), uploader.MaxCustomerUserUploadFileSizeLimit) if err != nil { diff --git a/pkg/handlers/primeapi/move_task_order.go b/pkg/handlers/primeapi/move_task_order.go index 6c6795cb56c..d82f2b72fb8 100644 --- a/pkg/handlers/primeapi/move_task_order.go +++ b/pkg/handlers/primeapi/move_task_order.go @@ -45,7 +45,7 @@ func (h ListMovesHandler) Handle(params movetaskorderops.ListMovesParams) middle return movetaskorderops.NewListMovesInternalServerError().WithPayload(payloads.InternalServerError(nil, h.GetTraceIDFromRequest(params.HTTPRequest))), err } - payload := payloads.ListMoves(&mtos, amendmentCountInfo) + payload := payloads.ListMoves(&mtos, appCtx, amendmentCountInfo) return movetaskorderops.NewListMovesOK().WithPayload(payload), nil }) @@ -141,7 +141,7 @@ func (h GetMoveTaskOrderHandler) Handle(params movetaskorderops.GetMoveTaskOrder } /** End of Feature Flag **/ - moveTaskOrderPayload := payloads.MoveTaskOrder(mto) + moveTaskOrderPayload := payloads.MoveTaskOrder(appCtx, mto) return movetaskorderops.NewGetMoveTaskOrderOK().WithPayload(moveTaskOrderPayload), nil }) @@ -250,7 +250,7 @@ func (h UpdateMTOPostCounselingInformationHandler) Handle(params movetaskorderop } } - mtoPayload := payloads.MoveTaskOrder(mto) + mtoPayload := payloads.MoveTaskOrder(appCtx, mto) /* Don't send prime related emails on BLUEBARK moves */ if mto.Orders.CanSendEmailWithOrdersType() { diff --git a/pkg/handlers/primeapi/mto_service_item_test.go b/pkg/handlers/primeapi/mto_service_item_test.go index c3e8b9daef6..83b894bbab4 100644 --- a/pkg/handlers/primeapi/mto_service_item_test.go +++ b/pkg/handlers/primeapi/mto_service_item_test.go @@ -1543,7 +1543,7 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemDDDSIT() { ).Return(400, nil) subtestData.handler = UpdateMTOServiceItemHandler{ suite.HandlerConfig(), - mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator), + mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer(), ghcrateengine.NewDomesticDestinationSITDeliveryPricer(), ghcrateengine.NewDomesticOriginSITFuelSurchargePricer()), } // create the params struct @@ -1825,7 +1825,7 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemDOPSIT() { ).Return(400, nil) subtestData.handler = UpdateMTOServiceItemHandler{ suite.HandlerConfig(), - mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator), + mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer(), ghcrateengine.NewDomesticDestinationSITDeliveryPricer(), ghcrateengine.NewDomesticOriginSITFuelSurchargePricer()), } // create the params struct diff --git a/pkg/handlers/primeapi/payloads/model_to_payload.go b/pkg/handlers/primeapi/payloads/model_to_payload.go index 32fbcde6cec..451bcf12ebd 100644 --- a/pkg/handlers/primeapi/payloads/model_to_payload.go +++ b/pkg/handlers/primeapi/payloads/model_to_payload.go @@ -20,7 +20,8 @@ import ( ) // MoveTaskOrder payload -func MoveTaskOrder(moveTaskOrder *models.Move) *primemessages.MoveTaskOrder { +func MoveTaskOrder(appCtx appcontext.AppContext, moveTaskOrder *models.Move) *primemessages.MoveTaskOrder { + db := appCtx.DB() if moveTaskOrder == nil { return nil } @@ -28,6 +29,17 @@ func MoveTaskOrder(moveTaskOrder *models.Move) *primemessages.MoveTaskOrder { mtoServiceItems := MTOServiceItems(&moveTaskOrder.MTOServiceItems) mtoShipments := MTOShipmentsWithoutServiceItems(&moveTaskOrder.MTOShipments) + var destGbloc, destZip string + var err error + destGbloc, err = moveTaskOrder.GetDestinationGBLOC(db) + if err != nil { + destGbloc = "" + } + destZip, err = moveTaskOrder.GetDestinationPostalCode(db) + if err != nil { + destZip = "" + } + payload := &primemessages.MoveTaskOrder{ ID: strfmt.UUID(moveTaskOrder.ID.String()), MoveCode: moveTaskOrder.Locator, @@ -40,6 +52,8 @@ func MoveTaskOrder(moveTaskOrder *models.Move) *primemessages.MoveTaskOrder { ExcessWeightUploadID: handlers.FmtUUIDPtr(moveTaskOrder.ExcessWeightUploadID), OrderID: strfmt.UUID(moveTaskOrder.OrdersID.String()), Order: Order(&moveTaskOrder.Orders), + DestinationGBLOC: destGbloc, + DestinationPostalCode: destZip, ReferenceID: *moveTaskOrder.ReferenceID, PaymentRequests: *paymentRequests, MtoShipments: *mtoShipments, @@ -63,21 +77,36 @@ func MoveTaskOrder(moveTaskOrder *models.Move) *primemessages.MoveTaskOrder { } // ListMove payload -func ListMove(move *models.Move, moveOrderAmendmentsCount *services.MoveOrderAmendmentAvailableSinceCount) *primemessages.ListMove { +func ListMove(move *models.Move, appCtx appcontext.AppContext, moveOrderAmendmentsCount *services.MoveOrderAmendmentAvailableSinceCount) *primemessages.ListMove { if move == nil { return nil } + db := appCtx.DB() + + var destGbloc, destZip string + var err error + destGbloc, err = move.GetDestinationGBLOC(db) + if err != nil { + destGbloc = "" + } + destZip, err = move.GetDestinationPostalCode(db) + if err != nil { + destZip = "" + } + payload := &primemessages.ListMove{ - ID: strfmt.UUID(move.ID.String()), - MoveCode: move.Locator, - CreatedAt: strfmt.DateTime(move.CreatedAt), - AvailableToPrimeAt: handlers.FmtDateTimePtr(move.AvailableToPrimeAt), - ApprovedAt: handlers.FmtDateTimePtr(move.ApprovedAt), - OrderID: strfmt.UUID(move.OrdersID.String()), - ReferenceID: *move.ReferenceID, - UpdatedAt: strfmt.DateTime(move.UpdatedAt), - ETag: etag.GenerateEtag(move.UpdatedAt), + ID: strfmt.UUID(move.ID.String()), + MoveCode: move.Locator, + CreatedAt: strfmt.DateTime(move.CreatedAt), + AvailableToPrimeAt: handlers.FmtDateTimePtr(move.AvailableToPrimeAt), + ApprovedAt: handlers.FmtDateTimePtr(move.ApprovedAt), + DestinationGBLOC: destGbloc, + DestinationPostalCode: destZip, + OrderID: strfmt.UUID(move.OrdersID.String()), + ReferenceID: *move.ReferenceID, + UpdatedAt: strfmt.DateTime(move.UpdatedAt), + ETag: etag.GenerateEtag(move.UpdatedAt), Amendments: &primemessages.Amendments{ Total: handlers.FmtInt64(0), AvailableSince: handlers.FmtInt64(0), @@ -97,7 +126,7 @@ func ListMove(move *models.Move, moveOrderAmendmentsCount *services.MoveOrderAme } // ListMoves payload -func ListMoves(moves *models.Moves, moveOrderAmendmentAvailableSinceCounts services.MoveOrderAmendmentAvailableSinceCounts) []*primemessages.ListMove { +func ListMoves(moves *models.Moves, appCtx appcontext.AppContext, moveOrderAmendmentAvailableSinceCounts services.MoveOrderAmendmentAvailableSinceCounts) []*primemessages.ListMove { payload := make(primemessages.ListMoves, len(*moves)) moveOrderAmendmentsFilterCountMap := make(map[uuid.UUID]services.MoveOrderAmendmentAvailableSinceCount, len(*moves)) @@ -108,9 +137,9 @@ func ListMoves(moves *models.Moves, moveOrderAmendmentAvailableSinceCounts servi for i, m := range *moves { copyOfM := m // Make copy to avoid implicit memory aliasing of items from a range statement. if value, ok := moveOrderAmendmentsFilterCountMap[m.ID]; ok { - payload[i] = ListMove(©OfM, &value) + payload[i] = ListMove(©OfM, appCtx, &value) } else { - payload[i] = ListMove(©OfM, nil) + payload[i] = ListMove(©OfM, appCtx, nil) } } @@ -152,7 +181,7 @@ 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.Entitlement.SetWeightAllotment(string(*order.Grade), order.OrdersType) } var grade string @@ -207,9 +236,14 @@ func Entitlement(entitlement *models.Entitlement) *primemessages.Entitlements { if entitlement.TotalDependents != nil { totalDependents = int64(*entitlement.TotalDependents) } + var ubAllowance int64 + if entitlement.UBAllowance != nil { + ubAllowance = int64(*entitlement.UBAllowance) + } return &primemessages.Entitlements{ ID: strfmt.UUID(entitlement.ID.String()), AuthorizedWeight: authorizedWeight, + UnaccompaniedBaggageAllowance: &ubAllowance, DependentsAuthorized: entitlement.DependentsAuthorized, GunSafe: entitlement.GunSafe, NonTemporaryStorage: entitlement.NonTemporaryStorage, diff --git a/pkg/handlers/primeapi/payloads/model_to_payload_test.go b/pkg/handlers/primeapi/payloads/model_to_payload_test.go index 74e90a5ee3c..06e5a218d70 100644 --- a/pkg/handlers/primeapi/payloads/model_to_payload_test.go +++ b/pkg/handlers/primeapi/payloads/model_to_payload_test.go @@ -55,7 +55,7 @@ func (suite *PayloadsSuite) TestMoveTaskOrder() { } suite.Run("Success - Returns a basic move payload with no payment requests, service items or shipments", func() { - returnedModel := MoveTaskOrder(&basicMove) + returnedModel := MoveTaskOrder(suite.AppContextForTest(), &basicMove) suite.IsType(&primemessages.MoveTaskOrder{}, returnedModel) suite.Equal(strfmt.UUID(basicMove.ID.String()), returnedModel.ID) @@ -278,6 +278,7 @@ func (suite *PayloadsSuite) TestEntitlement() { NonTemporaryStorage: nil, PrivatelyOwnedVehicle: nil, DBAuthorizedWeight: nil, + UBAllowance: nil, StorageInTransit: nil, RequiredMedicalEquipmentWeight: 0, OrganizationalClothingAndIndividualEquipment: false, @@ -306,6 +307,7 @@ func (suite *PayloadsSuite) TestEntitlement() { suite.Equal(int64(0), payload.StorageInTransit) suite.Equal(int64(0), payload.TotalDependents) suite.Equal(int64(0), payload.TotalWeight) + suite.Equal(int64(0), *payload.UnaccompaniedBaggageAllowance) }) suite.Run("Success - Returns the entitlement payload with all optional fields populated", func() { @@ -316,6 +318,7 @@ func (suite *PayloadsSuite) TestEntitlement() { NonTemporaryStorage: handlers.FmtBool(true), PrivatelyOwnedVehicle: handlers.FmtBool(true), DBAuthorizedWeight: handlers.FmtInt(10000), + UBAllowance: handlers.FmtInt(400), StorageInTransit: handlers.FmtInt(45), RequiredMedicalEquipmentWeight: 500, OrganizationalClothingAndIndividualEquipment: true, @@ -327,7 +330,7 @@ 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)) + entitlement.SetWeightAllotment(string(models.ServiceMemberGradeE5), internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) payload := Entitlement(&entitlement) @@ -337,6 +340,7 @@ func (suite *PayloadsSuite) TestEntitlement() { 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) @@ -355,6 +359,7 @@ func (suite *PayloadsSuite) TestEntitlement() { NonTemporaryStorage: handlers.FmtBool(true), PrivatelyOwnedVehicle: handlers.FmtBool(true), DBAuthorizedWeight: handlers.FmtInt(10000), + UBAllowance: handlers.FmtInt(400), StorageInTransit: handlers.FmtInt(45), RequiredMedicalEquipmentWeight: 500, OrganizationalClothingAndIndividualEquipment: true, @@ -366,7 +371,7 @@ 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)) + entitlement.SetWeightAllotment(string(models.ServiceMemberGradeE5), internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) payload := Entitlement(&entitlement) @@ -376,6 +381,7 @@ func (suite *PayloadsSuite) TestEntitlement() { 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) @@ -739,6 +745,55 @@ func (suite *PayloadsSuite) TestMTOServiceItemDDSHUT() { suite.True(ok) } +func (suite *PayloadsSuite) TestDestinationPostalCodeAndGBLOC() { + moveID := uuid.Must(uuid.NewV4()) + moveLocator := "TESTTEST" + primeTime := time.Now() + ordersID := uuid.Must(uuid.NewV4()) + refID := "123456" + contractNum := "HTC-123-456" + address := models.Address{PostalCode: "35023"} + shipment := models.MTOShipment{ + ID: uuid.Must(uuid.NewV4()), + DestinationAddress: &address, + } + shipments := models.MTOShipments{shipment} + contractor := models.Contractor{ + ContractNumber: contractNum, + } + + basicMove := models.Move{ + ID: moveID, + Locator: moveLocator, + CreatedAt: primeTime, + ReferenceID: &refID, + AvailableToPrimeAt: &primeTime, + ApprovedAt: &primeTime, + OrdersID: ordersID, + Contractor: &contractor, + PaymentRequests: models.PaymentRequests{}, + SubmittedAt: &primeTime, + UpdatedAt: primeTime, + Status: models.MoveStatusAPPROVED, + SignedCertifications: models.SignedCertifications{}, + MTOServiceItems: models.MTOServiceItems{}, + MTOShipments: shipments, + } + + suite.Run("Returns values needed to get the destination postal code and GBLOC", func() { + returnedModel := MoveTaskOrder(suite.AppContextForTest(), &basicMove) + + suite.IsType(&primemessages.MoveTaskOrder{}, returnedModel) + suite.Equal(strfmt.UUID(basicMove.ID.String()), returnedModel.ID) + suite.Equal(basicMove.Locator, returnedModel.MoveCode) + suite.Equal(strfmt.DateTime(basicMove.CreatedAt), returnedModel.CreatedAt) + suite.Equal(handlers.FmtDateTimePtr(basicMove.AvailableToPrimeAt), returnedModel.AvailableToPrimeAt) + suite.Equal(strfmt.UUID(basicMove.OrdersID.String()), returnedModel.OrderID) + suite.Equal(strfmt.DateTime(basicMove.UpdatedAt), returnedModel.UpdatedAt) + suite.NotEmpty(returnedModel.ETag) + }) +} + func (suite *PayloadsSuite) TestStorageFacilityPayload() { phone := "555" email := "email" diff --git a/pkg/handlers/primeapiv2/move_task_order.go b/pkg/handlers/primeapiv2/move_task_order.go index 0ac24d23651..f3ecd6db066 100644 --- a/pkg/handlers/primeapiv2/move_task_order.go +++ b/pkg/handlers/primeapiv2/move_task_order.go @@ -104,7 +104,7 @@ func (h GetMoveTaskOrderHandler) Handle(params movetaskorderops.GetMoveTaskOrder } /** End of Feature Flag **/ - moveTaskOrderPayload := payloads.MoveTaskOrder(mto) + moveTaskOrderPayload := payloads.MoveTaskOrder(appCtx, mto) return movetaskorderops.NewGetMoveTaskOrderOK().WithPayload(moveTaskOrderPayload), nil }) diff --git a/pkg/handlers/primeapiv2/mto_shipment_test.go b/pkg/handlers/primeapiv2/mto_shipment_test.go index f6857729a1d..151fff9352f 100644 --- a/pkg/handlers/primeapiv2/mto_shipment_test.go +++ b/pkg/handlers/primeapiv2/mto_shipment_test.go @@ -300,6 +300,12 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { mock.AnythingOfType("*models.PPMShipment")). Return(models.CentPointer(unit.Cents(estimatedIncentive)), models.CentPointer(unit.Cents(sitEstimatedCost)), nil).Once() + ppmEstimator.On("MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(nil, nil) + // Validate incoming payload suite.NoError(params.Body.Validate(strfmt.Default)) diff --git a/pkg/handlers/primeapiv2/payloads/model_to_payload.go b/pkg/handlers/primeapiv2/payloads/model_to_payload.go index e7350548dd9..ea1307e260b 100644 --- a/pkg/handlers/primeapiv2/payloads/model_to_payload.go +++ b/pkg/handlers/primeapiv2/payloads/model_to_payload.go @@ -9,6 +9,7 @@ import ( "github.com/gobuffalo/validate/v3" "github.com/gofrs/uuid" + "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/etag" "github.com/transcom/mymove/pkg/gen/primev2messages" "github.com/transcom/mymove/pkg/handlers" @@ -16,7 +17,8 @@ import ( ) // MoveTaskOrder payload -func MoveTaskOrder(moveTaskOrder *models.Move) *primev2messages.MoveTaskOrder { +func MoveTaskOrder(appCtx appcontext.AppContext, moveTaskOrder *models.Move) *primev2messages.MoveTaskOrder { + db := appCtx.DB() if moveTaskOrder == nil { return nil } @@ -24,6 +26,17 @@ func MoveTaskOrder(moveTaskOrder *models.Move) *primev2messages.MoveTaskOrder { mtoServiceItems := MTOServiceItems(&moveTaskOrder.MTOServiceItems) mtoShipments := MTOShipmentsWithoutServiceItems(&moveTaskOrder.MTOShipments) + var destGbloc, destZip string + var err error + destGbloc, err = moveTaskOrder.GetDestinationGBLOC(db) + if err != nil { + destGbloc = "" + } + destZip, err = moveTaskOrder.GetDestinationPostalCode(db) + if err != nil { + destZip = "" + } + payload := &primev2messages.MoveTaskOrder{ ID: strfmt.UUID(moveTaskOrder.ID.String()), MoveCode: moveTaskOrder.Locator, @@ -36,6 +49,8 @@ func MoveTaskOrder(moveTaskOrder *models.Move) *primev2messages.MoveTaskOrder { ExcessWeightUploadID: handlers.FmtUUIDPtr(moveTaskOrder.ExcessWeightUploadID), OrderID: strfmt.UUID(moveTaskOrder.OrdersID.String()), Order: Order(&moveTaskOrder.Orders), + DestinationGBLOC: destGbloc, + DestinationPostalCode: destZip, ReferenceID: *moveTaskOrder.ReferenceID, PaymentRequests: *paymentRequests, MtoShipments: *mtoShipments, @@ -94,7 +109,7 @@ 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.Entitlement.SetWeightAllotment(string(*order.Grade), order.OrdersType) } var grade string @@ -152,9 +167,14 @@ func Entitlement(entitlement *models.Entitlement) *primev2messages.Entitlements if entitlement.TotalDependents != nil { totalDependents = int64(*entitlement.TotalDependents) } + var ubAllowance int64 + if entitlement.UBAllowance != nil { + ubAllowance = int64(*entitlement.UBAllowance) + } return &primev2messages.Entitlements{ ID: strfmt.UUID(entitlement.ID.String()), AuthorizedWeight: authorizedWeight, + UnaccompaniedBaggageAllowance: &ubAllowance, DependentsAuthorized: entitlement.DependentsAuthorized, GunSafe: entitlement.GunSafe, NonTemporaryStorage: entitlement.NonTemporaryStorage, diff --git a/pkg/handlers/primeapiv2/payloads/model_to_payload_test.go b/pkg/handlers/primeapiv2/payloads/model_to_payload_test.go index ba3a804b267..ec125590da5 100644 --- a/pkg/handlers/primeapiv2/payloads/model_to_payload_test.go +++ b/pkg/handlers/primeapiv2/payloads/model_to_payload_test.go @@ -64,7 +64,7 @@ func (suite *PayloadsSuite) TestMoveTaskOrder() { } suite.Run("Success - Returns a basic move payload with no payment requests, service items or shipments", func() { - returnedModel := MoveTaskOrder(&basicMove) + returnedModel := MoveTaskOrder(suite.AppContextForTest(), &basicMove) suite.IsType(&primev2messages.MoveTaskOrder{}, returnedModel) suite.Equal(strfmt.UUID(basicMove.ID.String()), returnedModel.ID) @@ -89,6 +89,55 @@ func (suite *PayloadsSuite) TestMoveTaskOrder() { }) } +func (suite *PayloadsSuite) TestDestinationPostalCodeAndGBLOC() { + moveID := uuid.Must(uuid.NewV4()) + moveLocator := "TESTTEST" + primeTime := time.Now() + ordersID := uuid.Must(uuid.NewV4()) + refID := "123456" + contractNum := "HTC-123-456" + address := models.Address{PostalCode: "35023"} + shipment := models.MTOShipment{ + ID: uuid.Must(uuid.NewV4()), + DestinationAddress: &address, + } + shipments := models.MTOShipments{shipment} + contractor := models.Contractor{ + ContractNumber: contractNum, + } + + basicMove := models.Move{ + ID: moveID, + Locator: moveLocator, + CreatedAt: primeTime, + ReferenceID: &refID, + AvailableToPrimeAt: &primeTime, + ApprovedAt: &primeTime, + OrdersID: ordersID, + Contractor: &contractor, + PaymentRequests: models.PaymentRequests{}, + SubmittedAt: &primeTime, + UpdatedAt: primeTime, + Status: models.MoveStatusAPPROVED, + SignedCertifications: models.SignedCertifications{}, + MTOServiceItems: models.MTOServiceItems{}, + MTOShipments: shipments, + } + + suite.Run("Returns values needed to get the destination postal code and GBLOC", func() { + returnedModel := MoveTaskOrder(suite.AppContextForTest(), &basicMove) + + suite.IsType(&primev2messages.MoveTaskOrder{}, returnedModel) + suite.Equal(strfmt.UUID(basicMove.ID.String()), returnedModel.ID) + suite.Equal(basicMove.Locator, returnedModel.MoveCode) + suite.Equal(strfmt.DateTime(basicMove.CreatedAt), returnedModel.CreatedAt) + suite.Equal(handlers.FmtDateTimePtr(basicMove.AvailableToPrimeAt), returnedModel.AvailableToPrimeAt) + suite.Equal(strfmt.UUID(basicMove.OrdersID.String()), returnedModel.OrderID) + suite.Equal(strfmt.DateTime(basicMove.UpdatedAt), returnedModel.UpdatedAt) + suite.NotEmpty(returnedModel.ETag) + }) +} + func (suite *PayloadsSuite) TestReweigh() { id, _ := uuid.NewV4() shipmentID, _ := uuid.NewV4() @@ -224,6 +273,7 @@ func (suite *PayloadsSuite) TestEntitlement() { NonTemporaryStorage: nil, PrivatelyOwnedVehicle: nil, DBAuthorizedWeight: nil, + UBAllowance: nil, StorageInTransit: nil, RequiredMedicalEquipmentWeight: 0, OrganizationalClothingAndIndividualEquipment: false, @@ -252,6 +302,7 @@ func (suite *PayloadsSuite) TestEntitlement() { suite.Equal(int64(0), payload.StorageInTransit) suite.Equal(int64(0), payload.TotalDependents) suite.Equal(int64(0), payload.TotalWeight) + suite.Equal(int64(0), *payload.UnaccompaniedBaggageAllowance) }) suite.Run("Success - Returns the entitlement payload with all optional fields populated", func() { @@ -262,6 +313,7 @@ func (suite *PayloadsSuite) TestEntitlement() { NonTemporaryStorage: handlers.FmtBool(true), PrivatelyOwnedVehicle: handlers.FmtBool(true), DBAuthorizedWeight: handlers.FmtInt(10000), + UBAllowance: handlers.FmtInt(400), StorageInTransit: handlers.FmtInt(45), RequiredMedicalEquipmentWeight: 500, OrganizationalClothingAndIndividualEquipment: true, @@ -273,7 +325,7 @@ 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)) + entitlement.SetWeightAllotment(string(models.ServiceMemberGradeE5), internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) payload := Entitlement(&entitlement) @@ -283,6 +335,7 @@ func (suite *PayloadsSuite) TestEntitlement() { 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) @@ -301,6 +354,7 @@ func (suite *PayloadsSuite) TestEntitlement() { NonTemporaryStorage: handlers.FmtBool(true), PrivatelyOwnedVehicle: handlers.FmtBool(true), DBAuthorizedWeight: handlers.FmtInt(10000), + UBAllowance: handlers.FmtInt(400), StorageInTransit: handlers.FmtInt(45), RequiredMedicalEquipmentWeight: 500, OrganizationalClothingAndIndividualEquipment: true, @@ -312,7 +366,7 @@ 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)) + entitlement.SetWeightAllotment(string(models.ServiceMemberGradeE5), internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) payload := Entitlement(&entitlement) @@ -322,6 +376,7 @@ func (suite *PayloadsSuite) TestEntitlement() { 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) diff --git a/pkg/handlers/primeapiv3/api.go b/pkg/handlers/primeapiv3/api.go index 568a47949cf..7d441a87818 100644 --- a/pkg/handlers/primeapiv3/api.go +++ b/pkg/handlers/primeapiv3/api.go @@ -46,6 +46,7 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primev3operations.Mymove primeAPIV3.MoveTaskOrderGetMoveTaskOrderHandler = GetMoveTaskOrderHandler{ handlerConfig, movetaskorder.NewMoveTaskOrderFetcher(), + mtoshipment.NewMTOShipmentRateAreaFetcher(), } signedCertificationCreator := signedcertification.NewSignedCertificationCreator() diff --git a/pkg/handlers/primeapiv3/move_task_order.go b/pkg/handlers/primeapiv3/move_task_order.go index 8c9ffe918b0..c725fe7210b 100644 --- a/pkg/handlers/primeapiv3/move_task_order.go +++ b/pkg/handlers/primeapiv3/move_task_order.go @@ -17,7 +17,8 @@ import ( // GetMoveTaskOrderHandler returns the details for a particular move type GetMoveTaskOrderHandler struct { handlers.HandlerConfig - moveTaskOrderFetcher services.MoveTaskOrderFetcher + moveTaskOrderFetcher services.MoveTaskOrderFetcher + shipmentRateAreaFinder services.ShipmentRateAreaFinder } // Handle fetches a move from the database using its UUID or move code @@ -104,7 +105,15 @@ func (h GetMoveTaskOrderHandler) Handle(params movetaskorderops.GetMoveTaskOrder } /** End of Feature Flag **/ - moveTaskOrderPayload := payloads.MoveTaskOrder(mto) + // Add oconus rate area information to payload + shipmentPostalCodeRateArea, err := h.shipmentRateAreaFinder.GetPrimeMoveShipmentOconusRateArea(appCtx, *mto) + if err != nil { + appCtx.Logger().Error("primeapi.GetMoveTaskOrderHandler error", zap.Error(err)) + return movetaskorderops.NewGetMoveTaskOrderInternalServerError().WithPayload( + payloads.InternalServerError(handlers.FmtString(err.Error()), h.GetTraceIDFromRequest(params.HTTPRequest))), err + } + + moveTaskOrderPayload := payloads.MoveTaskOrderWithShipmentOconusRateArea(appCtx, mto, shipmentPostalCodeRateArea) return movetaskorderops.NewGetMoveTaskOrderOK().WithPayload(moveTaskOrderPayload), nil }) diff --git a/pkg/handlers/primeapiv3/move_task_order_test.go b/pkg/handlers/primeapiv3/move_task_order_test.go index cb76222e7ad..b1585307e7b 100644 --- a/pkg/handlers/primeapiv3/move_task_order_test.go +++ b/pkg/handlers/primeapiv3/move_task_order_test.go @@ -7,13 +7,19 @@ import ( "time" "github.com/go-openapi/strfmt" + "github.com/gofrs/uuid" + "github.com/stretchr/testify/mock" + "github.com/transcom/mymove/pkg/apperror" "github.com/transcom/mymove/pkg/factory" movetaskorderops "github.com/transcom/mymove/pkg/gen/primev3api/primev3operations/move_task_order" "github.com/transcom/mymove/pkg/gen/primev3messages" "github.com/transcom/mymove/pkg/handlers" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "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" "github.com/transcom/mymove/pkg/testdatagen" "github.com/transcom/mymove/pkg/unit" ) @@ -36,12 +42,22 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.NotNil(payload.ETag) } - suite.Run("Success with Prime-available move by ID", func() { + setupDefaultTestHandler := func() GetMoveTaskOrderHandler { + mockShipmentRateAreaFinder := &mocks.ShipmentRateAreaFinder{} + mockShipmentRateAreaFinder.On("GetPrimeMoveShipmentOconusRateArea", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.Move"), + ).Return(nil, nil) handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), movetaskorder.NewMoveTaskOrderFetcher(), + mockShipmentRateAreaFinder, } + return handler + } + suite.Run("Success with Prime-available move by ID", func() { + handler := setupDefaultTestHandler() successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) params := movetaskorderops.GetMoveTaskOrderParams{ HTTPRequest: request, @@ -66,10 +82,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { }) suite.Run("Success with Prime-available move by Locator", func() { - handler := GetMoveTaskOrderHandler{ - suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), - } + handler := setupDefaultTestHandler() successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) params := movetaskorderops.GetMoveTaskOrderParams{ HTTPRequest: request, @@ -94,10 +107,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { }) suite.Run("Success returns reweighs on shipments if they exist", func() { - handler := GetMoveTaskOrderHandler{ - suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), - } + handler := setupDefaultTestHandler() successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) params := movetaskorderops.GetMoveTaskOrderParams{ HTTPRequest: request, @@ -146,10 +156,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { }) suite.Run("Success - returns sit extensions on shipments if they exist", func() { - handler := GetMoveTaskOrderHandler{ - suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), - } + handler := setupDefaultTestHandler() successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) params := movetaskorderops.GetMoveTaskOrderParams{ HTTPRequest: request, @@ -204,10 +211,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { }) suite.Run("Success - filters shipments handled by an external vendor", func() { - handler := GetMoveTaskOrderHandler{ - suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), - } + handler := setupDefaultTestHandler() move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) // Create two shipments, one prime, one external. Only prime one should be returned. @@ -259,10 +263,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { }) suite.Run("Success - returns shipment with attached PpmShipment", func() { - handler := GetMoveTaskOrderHandler{ - suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), - } + handler := setupDefaultTestHandler() move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) ppmShipment := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ { @@ -295,10 +296,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Run("Success - returns all the fields at the mtoShipment level", func() { // This tests fields that aren't other structs and Addresses - handler := GetMoveTaskOrderHandler{ - suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), - } + handler := setupDefaultTestHandler() successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) destinationAddress := factory.BuildAddress(suite.DB(), nil, nil) destinationType := models.DestinationTypeHomeOfRecord @@ -422,10 +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(), - } + handler := setupDefaultTestHandler() successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) params := movetaskorderops.GetMoveTaskOrderParams{ HTTPRequest: request, @@ -478,10 +473,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { }) suite.Run("Success - returns all the fields associated with Agents within MtoShipments", func() { - handler := GetMoveTaskOrderHandler{ - suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), - } + handler := setupDefaultTestHandler() successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) params := movetaskorderops.GetMoveTaskOrderParams{ HTTPRequest: request, @@ -529,10 +521,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { }) suite.Run("Success - return all base fields assoicated with the getMoveTaskOrder", func() { - handler := GetMoveTaskOrderHandler{ - suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), - } + handler := setupDefaultTestHandler() now := time.Now() aWeekAgo := now.AddDate(0, 0, -7) upload := factory.BuildUpload(suite.DB(), nil, nil) @@ -581,10 +570,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { }) suite.Run("Success - return all Order fields assoicated with the getMoveTaskOrder", func() { - handler := GetMoveTaskOrderHandler{ - suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), - } + handler := setupDefaultTestHandler() currentAddress := factory.BuildAddress(suite.DB(), nil, nil) successMove := factory.BuildAvailableToPrimeMove(suite.DB(), []factory.Customization{ { @@ -676,10 +662,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { }) suite.Run("Success - return all PaymentRequests fields assoicated with the getMoveTaskOrder", func() { - handler := GetMoveTaskOrderHandler{ - suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), - } + handler := setupDefaultTestHandler() successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) successShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ @@ -865,10 +848,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { }) suite.Run("Success - return all MTOServiceItemBasic fields assoicated with the getMoveTaskOrder", func() { - handler := GetMoveTaskOrderHandler{ - suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), - } + handler := setupDefaultTestHandler() successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) successShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ @@ -944,10 +924,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { }) suite.Run("Success - return all MTOServiceItemOriginSIT fields assoicated with the getMoveTaskOrder", func() { - handler := GetMoveTaskOrderHandler{ - suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), - } + handler := setupDefaultTestHandler() successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) successShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ @@ -1058,10 +1035,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { }) suite.Run("Success - return all MTOServiceItemDestSIT fields assoicated with the getMoveTaskOrder", func() { - handler := GetMoveTaskOrderHandler{ - suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), - } + handler := setupDefaultTestHandler() successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) successShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ @@ -1183,10 +1157,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { }) suite.Run("Success - return all MTOServiceItemShuttle fields assoicated with the getMoveTaskOrder", func() { - handler := GetMoveTaskOrderHandler{ - suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), - } + handler := setupDefaultTestHandler() successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) successShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ @@ -1269,10 +1240,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { }) suite.Run("Success - return all MTOServiceItemDomesticCrating fields assoicated with the getMoveTaskOrder", func() { - handler := GetMoveTaskOrderHandler{ - suite.HandlerConfig(), - movetaskorder.NewMoveTaskOrderFetcher(), - } + handler := setupDefaultTestHandler() successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) successShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ @@ -1400,6 +1368,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), movetaskorder.NewMoveTaskOrderFetcher(), + mtoshipment.NewMTOShipmentRateAreaFetcher(), } failureMove := factory.BuildMove(suite.DB(), nil, nil) // default is not available to Prime params := movetaskorderops.GetMoveTaskOrderParams{ @@ -1421,4 +1390,181 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Contains(*movePayload.Detail, failureMove.ID.String()) }) + + suite.Run("Success - returns Oconus RateArea information", func() { + const fairbanksAlaskaPostalCode = "99716" + const anchorageAlaskaPostalCode = "99521" + + mockShipmentRateAreaFinder := &mocks.ShipmentRateAreaFinder{} + + // This tests fields that aren't other structs and Addresses + handler := GetMoveTaskOrderHandler{ + suite.HandlerConfig(), + movetaskorder.NewMoveTaskOrderFetcher(), + mockShipmentRateAreaFinder, + } + successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + + pickupAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "1234 Street", + City: "Fairbanks", + State: "AK", + PostalCode: fairbanksAlaskaPostalCode, + }, + }, + }, nil) + destinationAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "1234 Street", + City: "Anchorage", + State: "AK", + PostalCode: anchorageAlaskaPostalCode, + }, + }, + }, nil) + + // mock up ShipmentPostalCodeRateArea + shipmentPostalCodeRateArea := []services.ShipmentPostalCodeRateArea{ + { + PostalCode: fairbanksAlaskaPostalCode, + RateArea: &models.ReRateArea{ + ID: uuid.Must(uuid.NewV4()), + Code: fairbanksAlaskaPostalCode, + Name: fairbanksAlaskaPostalCode, + }, + }, + { + PostalCode: anchorageAlaskaPostalCode, + RateArea: &models.ReRateArea{ + ID: uuid.Must(uuid.NewV4()), + Code: anchorageAlaskaPostalCode, + Name: anchorageAlaskaPostalCode, + }, + }, + } + + mockShipmentRateAreaFinder.On("GetPrimeMoveShipmentOconusRateArea", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.Move"), + ).Return(&shipmentPostalCodeRateArea, nil) + + destinationType := models.DestinationTypeHomeOfRecord + now := time.Now() + nowDate := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC) + yesterDate := nowDate.AddDate(0, 0, -1) + aWeekAgo := nowDate.AddDate(0, 0, -7) + successShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + ActualDeliveryDate: &nowDate, + CounselorRemarks: models.StringPointer("LGTM"), + PickupAddressID: &pickupAddress.ID, + DestinationAddressID: &destinationAddress.ID, + DestinationType: &destinationType, + FirstAvailableDeliveryDate: &yesterDate, + Status: models.MTOShipmentStatusApproved, + NTSRecordedWeight: models.PoundPointer(unit.Pound(249)), + PrimeEstimatedWeight: models.PoundPointer(unit.Pound(980)), + PrimeEstimatedWeightRecordedDate: &aWeekAgo, + RequiredDeliveryDate: &nowDate, + ScheduledDeliveryDate: &nowDate, + }, + }, + { + Model: successMove, + LinkOnly: true, + }, + }, nil) + params := movetaskorderops.GetMoveTaskOrderParams{ + HTTPRequest: request, + MoveID: successMove.ID.String(), + } + + // Validate incoming payload: no body to validate + + response := handler.Handle(params) + suite.NotNil(response) + + suite.IsNotErrResponse(response) + suite.IsType(&movetaskorderops.GetMoveTaskOrderOK{}, response) + + moveResponse := response.(*movetaskorderops.GetMoveTaskOrderOK) + movePayload := moveResponse.Payload + + // Validate outgoing payload + suite.NoError(movePayload.Validate(strfmt.Default)) + + suite.Equal(movePayload.ID.String(), successMove.ID.String()) + + suite.NotNil(movePayload.AvailableToPrimeAt) + suite.NotEmpty(movePayload.AvailableToPrimeAt) + + suite.NotNil(movePayload.MtoShipments[0].OriginRateArea) + suite.NotNil(movePayload.MtoShipments[0].DestinationRateArea) + + suite.Equal(shipmentPostalCodeRateArea[0].RateArea.Code, *movePayload.MtoShipments[0].OriginRateArea.RateAreaID) + suite.Equal(shipmentPostalCodeRateArea[1].RateArea.Code, *movePayload.MtoShipments[0].DestinationRateArea.RateAreaID) + + suite.Equal(successShipment.ID, handlers.FmtUUIDToPop(movePayload.MtoShipments[0].ID)) + }) + + suite.Run("failure - error while attempting to retrieve Oconus RateArea information for shipments", func() { + + mockShipmentRateAreaFinder := &mocks.ShipmentRateAreaFinder{} + + // This tests fields that aren't other structs and Addresses + handler := GetMoveTaskOrderHandler{ + suite.HandlerConfig(), + movetaskorder.NewMoveTaskOrderFetcher(), + mockShipmentRateAreaFinder, + } + successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + + defaultAddress := factory.BuildAddress(suite.DB(), nil, nil) + + mockShipmentRateAreaFinder.On("GetPrimeMoveShipmentOconusRateArea", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.Move"), + ).Return(nil, apperror.InternalServerError{}) + + destinationType := models.DestinationTypeHomeOfRecord + now := time.Now() + nowDate := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC) + yesterDate := nowDate.AddDate(0, 0, -1) + aWeekAgo := nowDate.AddDate(0, 0, -7) + factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + ActualDeliveryDate: &nowDate, + CounselorRemarks: models.StringPointer("LGTM"), + PickupAddressID: &defaultAddress.ID, + DestinationAddressID: &defaultAddress.ID, + DestinationType: &destinationType, + FirstAvailableDeliveryDate: &yesterDate, + Status: models.MTOShipmentStatusApproved, + NTSRecordedWeight: models.PoundPointer(unit.Pound(249)), + PrimeEstimatedWeight: models.PoundPointer(unit.Pound(980)), + PrimeEstimatedWeightRecordedDate: &aWeekAgo, + RequiredDeliveryDate: &nowDate, + ScheduledDeliveryDate: &nowDate, + }, + }, + { + Model: successMove, + LinkOnly: true, + }, + }, nil) + params := movetaskorderops.GetMoveTaskOrderParams{ + HTTPRequest: request, + MoveID: successMove.ID.String(), + } + + response := handler.Handle(params) + suite.NotNil(response) + + suite.IsType(&movetaskorderops.GetMoveTaskOrderInternalServerError{}, response) + }) } diff --git a/pkg/handlers/primeapiv3/mto_shipment_test.go b/pkg/handlers/primeapiv3/mto_shipment_test.go index 151534f6b79..71d1d07f37c 100644 --- a/pkg/handlers/primeapiv3/mto_shipment_test.go +++ b/pkg/handlers/primeapiv3/mto_shipment_test.go @@ -95,10 +95,11 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { var pickupAddress primev3messages.Address var secondaryPickupAddress primev3messages.Address + var tertiaryPickupAddress primev3messages.Address var destinationAddress primev3messages.Address var ppmDestinationAddress primev3messages.PPMDestinationAddress var secondaryDestinationAddress primev3messages.Address - + var tertiaryDestinationAddress primev3messages.Address creator := paymentrequest.NewPaymentRequestCreator(planner, ghcrateengine.NewServiceItemPricer()) statusUpdater := paymentrequest.NewPaymentRequestStatusUpdater(query.NewQueryBuilder()) recalculator := paymentrequest.NewPaymentRequestRecalculator(creator, statusUpdater) @@ -211,6 +212,22 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { StreetAddress2: newAddress.StreetAddress2, StreetAddress3: newAddress.StreetAddress3, } + secondaryPickupAddress = primev3messages.Address{ + City: &newAddress.City, + PostalCode: &newAddress.PostalCode, + State: &newAddress.State, + StreetAddress1: &newAddress.StreetAddress1, + StreetAddress2: newAddress.StreetAddress2, + StreetAddress3: newAddress.StreetAddress3, + } + tertiaryPickupAddress = primev3messages.Address{ + City: &newAddress.City, + PostalCode: &newAddress.PostalCode, + State: &newAddress.State, + StreetAddress1: &newAddress.StreetAddress1, + StreetAddress2: newAddress.StreetAddress2, + StreetAddress3: newAddress.StreetAddress3, + } newAddress = factory.BuildAddress(nil, nil, []factory.Trait{factory.GetTraitAddress2}) destinationAddress = primev3messages.Address{ City: &newAddress.City, @@ -220,6 +237,22 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { StreetAddress2: newAddress.StreetAddress2, StreetAddress3: newAddress.StreetAddress3, } + secondaryDestinationAddress = primev3messages.Address{ + City: &newAddress.City, + PostalCode: &newAddress.PostalCode, + State: &newAddress.State, + StreetAddress1: &newAddress.StreetAddress1, + StreetAddress2: newAddress.StreetAddress2, + StreetAddress3: newAddress.StreetAddress3, + } + tertiaryDestinationAddress = primev3messages.Address{ + City: &newAddress.City, + PostalCode: &newAddress.PostalCode, + State: &newAddress.State, + StreetAddress1: &newAddress.StreetAddress1, + StreetAddress2: newAddress.StreetAddress2, + StreetAddress3: newAddress.StreetAddress3, + } return handler, move } @@ -338,6 +371,13 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { PostalCode: "62225", } + address3 := models.Address{ + StreetAddress1: "some address", + City: "city", + State: "VA", + PostalCode: "23435", + } + expectedPickupAddress := address1 pickupAddress = primev3messages.Address{ City: &expectedPickupAddress.City, @@ -358,6 +398,16 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { StreetAddress3: expectedSecondaryPickupAddress.StreetAddress3, } + expectedTertiaryPickupAddress := address3 + tertiaryDestinationAddress = primev3messages.Address{ + City: &expectedTertiaryPickupAddress.City, + PostalCode: &expectedTertiaryPickupAddress.PostalCode, + State: &expectedTertiaryPickupAddress.State, + StreetAddress1: &expectedTertiaryPickupAddress.StreetAddress1, + StreetAddress2: expectedTertiaryPickupAddress.StreetAddress2, + StreetAddress3: expectedTertiaryPickupAddress.StreetAddress3, + } + expectedDestinationAddress := address1 destinationAddress = primev3messages.Address{ City: &expectedDestinationAddress.City, @@ -386,6 +436,16 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { StreetAddress3: expectedSecondaryDestinationAddress.StreetAddress3, } + expectedTertiaryDestinationAddress := address3 + tertiaryDestinationAddress = primev3messages.Address{ + City: &expectedTertiaryDestinationAddress.City, + PostalCode: &expectedTertiaryDestinationAddress.PostalCode, + State: &expectedTertiaryDestinationAddress.State, + StreetAddress1: &expectedTertiaryDestinationAddress.StreetAddress1, + StreetAddress2: expectedTertiaryDestinationAddress.StreetAddress2, + StreetAddress3: expectedTertiaryDestinationAddress.StreetAddress3, + } + params := mtoshipmentops.CreateMTOShipmentParams{ HTTPRequest: req, Body: &primev3messages.CreateMTOShipment{ @@ -419,6 +479,12 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { mock.AnythingOfType("*models.PPMShipment")). Return(models.CentPointer(unit.Cents(estimatedIncentive)), models.CentPointer(unit.Cents(sitEstimatedCost)), nil).Once() + ppmEstimator.On("MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(nil, nil) + // Validate incoming payload suite.NoError(params.Body.Validate(strfmt.Default)) @@ -465,7 +531,13 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("models.PPMShipment"), mock.AnythingOfType("*models.PPMShipment")). - Return(models.CentPointer(unit.Cents(estimatedIncentive)), models.CentPointer(unit.Cents(sitEstimatedCost)), nil).Times(2) + Return(models.CentPointer(unit.Cents(estimatedIncentive)), models.CentPointer(unit.Cents(sitEstimatedCost)), nil).Times(4) + + ppmEstimator.On("MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(nil, nil) ppmEstimator.On("FinalIncentiveWithDefaultChecks", mock.AnythingOfType("*appcontext.appContext"), @@ -525,6 +597,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { suite.NoError(err) eTag = etag.GenerateEtag(mtoShipment.UpdatedAt) patchParams.IfMatch = eTag + patchParams.MtoShipmentID = createdPPM.ShipmentID patchParams.Body.PpmShipment = &primev3messages.UpdatePPMShipment{ HasProGear: &hasProGear, HasSecondaryPickupAddress: models.BoolPointer(false), @@ -545,6 +618,71 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { suite.Nil(updatedPPM.SecondaryDestinationAddress) suite.False(*models.BoolPointer(*updatedPPM.HasSecondaryPickupAddress)) suite.False(*models.BoolPointer(*updatedPPM.HasSecondaryDestinationAddress)) + + // ************************************************************************************* + // ************************************************************************************* + // Run it third time, but really add secondary addresses with has flags set to true + // ************************************************************************************* + eTag = etag.GenerateEtag(time.Time(updatedShipment.UpdatedAt)) + patchParams.IfMatch = eTag + patchParams.MtoShipmentID = createdPPM.ShipmentID + + patchParams.Body.PpmShipment = &primev3messages.UpdatePPMShipment{ + HasProGear: &hasProGear, + HasSecondaryPickupAddress: models.BoolPointer(true), + HasSecondaryDestinationAddress: models.BoolPointer(true), + SecondaryPickupAddress: struct{ primev3messages.Address }{secondaryPickupAddress}, + SecondaryDestinationAddress: struct{ primev3messages.Address }{secondaryDestinationAddress}, + } + + // Validate incoming payload + suite.NoError(patchParams.Body.Validate(strfmt.Default)) + + patchResponse = patchHandler.Handle(patchParams) + suite.IsType(&mtoshipmentops.UpdateMTOShipmentOK{}, patchResponse) + okPatchResponse = patchResponse.(*mtoshipmentops.UpdateMTOShipmentOK) + updatedShipment = okPatchResponse.Payload + + // Validate outgoing payload + suite.NoError(updatedShipment.Validate(strfmt.Default)) + + updatedPPM = updatedShipment.PpmShipment + suite.NotNil(updatedPPM.SecondaryPickupAddress) + suite.NotNil(updatedPPM.SecondaryDestinationAddress) + suite.True(*models.BoolPointer(*updatedPPM.HasSecondaryPickupAddress)) + suite.True(*models.BoolPointer(*updatedPPM.HasSecondaryDestinationAddress)) + + // ************************************************************************************* + // ************************************************************************************* + // Run it fourth time, but really add tertiary addresses with has flags set to true + // ************************************************************************************* + eTag = etag.GenerateEtag(time.Time(updatedShipment.UpdatedAt)) + patchParams.IfMatch = eTag + patchParams.MtoShipmentID = updatedPPM.ShipmentID + patchParams.Body.PpmShipment = &primev3messages.UpdatePPMShipment{ + HasProGear: &hasProGear, + HasTertiaryPickupAddress: models.BoolPointer(true), + HasTertiaryDestinationAddress: models.BoolPointer(true), + TertiaryPickupAddress: struct{ primev3messages.Address }{tertiaryPickupAddress}, + TertiaryDestinationAddress: struct{ primev3messages.Address }{tertiaryDestinationAddress}, + } + + // Validate incoming payload + suite.NoError(patchParams.Body.Validate(strfmt.Default)) + + patchResponse = patchHandler.Handle(patchParams) + suite.IsType(&mtoshipmentops.UpdateMTOShipmentOK{}, patchResponse) + okPatchResponse = patchResponse.(*mtoshipmentops.UpdateMTOShipmentOK) + updatedShipment = okPatchResponse.Payload + + // Validate outgoing payload + suite.NoError(updatedShipment.Validate(strfmt.Default)) + + updatedPPM = updatedShipment.PpmShipment + suite.NotNil(updatedPPM.TertiaryPickupAddress) + suite.NotNil(updatedPPM.TertiaryDestinationAddress) + suite.True(*models.BoolPointer(*updatedPPM.HasTertiaryPickupAddress)) + suite.True(*models.BoolPointer(*updatedPPM.HasTertiaryDestinationAddress)) }) suite.Run("Successful POST/PATCH - Integration Test (PPM) - Destination address street 1 OPTIONAL", func() { @@ -640,6 +778,12 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { mock.AnythingOfType("*models.PPMShipment")). Return(models.CentPointer(unit.Cents(estimatedIncentive)), models.CentPointer(unit.Cents(sitEstimatedCost)), nil).Once() + ppmEstimator.On("MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(nil, nil) + // Validate incoming payload suite.NoError(params.Body.Validate(strfmt.Default)) @@ -670,6 +814,12 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { mock.AnythingOfType("*models.PPMShipment")). Return(models.CentPointer(unit.Cents(estimatedIncentive)), models.CentPointer(unit.Cents(sitEstimatedCost)), nil).Times(2) + ppmEstimator.On("MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(nil, nil) + ppmEstimator.On("FinalIncentiveWithDefaultChecks", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("models.PPMShipment"), @@ -1205,4 +1355,420 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { suite.Contains(*errResponse.Payload.Detail, "Unaccompanied baggage shipments can't be created unless the unaccompanied_baggage feature flag is enabled.") }) + + suite.Run("POST failure - Error creating a mto shipment contains tertiary destination address no secondary destination address.", func() { + // Under Test: CreateMTOShipmentHandler + // Setup: If underlying CreateMTOShipment returns error, handler should return 422 response + // Expected: 422 Response returned + + handler, move := setupTestData(false, false) + + req := httptest.NewRequest("POST", "/mto-shipments", nil) + + newAddress := factory.BuildAddress(nil, []factory.Customization{ + { + Model: models.Address{ + ID: uuid.Must(uuid.NewV4()), + }, + }, + }, nil) + destinationAddress = primev3messages.Address{ + City: &newAddress.City, + PostalCode: &newAddress.PostalCode, + State: &newAddress.State, + StreetAddress1: &newAddress.StreetAddress1, + StreetAddress2: newAddress.StreetAddress2, + StreetAddress3: newAddress.StreetAddress3, + } + destinationAddress = primev3messages.Address{ + City: &newAddress.City, + PostalCode: &newAddress.PostalCode, + State: &newAddress.State, + StreetAddress1: &newAddress.StreetAddress1, + StreetAddress2: newAddress.StreetAddress2, + StreetAddress3: newAddress.StreetAddress3, + } + tertiaryDestinationAddress = primev3messages.Address{ + City: &newAddress.City, + PostalCode: &newAddress.PostalCode, + State: &newAddress.State, + StreetAddress1: &newAddress.StreetAddress1, + StreetAddress2: newAddress.StreetAddress2, + StreetAddress3: newAddress.StreetAddress3, + } + + params := mtoshipmentops.CreateMTOShipmentParams{ + HTTPRequest: req, + Body: &primev3messages.CreateMTOShipment{ + MoveTaskOrderID: handlers.FmtUUID(move.ID), + Agents: nil, + CustomerRemarks: nil, + PointOfContact: "John Doe", + PrimeEstimatedWeight: handlers.FmtInt64(1200), + RequestedPickupDate: handlers.FmtDatePtr(models.TimePointer(time.Now())), + ShipmentType: primev3messages.NewMTOShipmentType(primev3messages.MTOShipmentTypeHHG), + PickupAddress: struct{ primev3messages.Address }{pickupAddress}, + DestinationAddress: struct{ primev3messages.Address }{destinationAddress}, + TertiaryDestinationAddress: struct{ primev3messages.Address }{tertiaryDestinationAddress}, + }, + } + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.CreateMTOShipmentUnprocessableEntity{}, response) + errResponse := response.(*mtoshipmentops.CreateMTOShipmentUnprocessableEntity) + + suite.Contains(*errResponse.Payload.Detail, "Invalid input found while validating the MTO shipment") + suite.Contains(errResponse.Payload.InvalidFields["error validating mto shipment"][0], "Shipment cannot have a third address without a second address present") + }) + + suite.Run("POST failure - Error creating an mto shipment with ppm shipment contains tertiary pickup address no secondary pickup address.", func() { + // Under Test: UpdateMTOShipmentHandler + // Setup: If underlying UpdateMTOShipment returns error, handler should return 422 response + // Expected: 422 Response returned + + handler, move := setupTestData(false, false) + + req := httptest.NewRequest("POST", "/mto-shipments", nil) + + expectedDepartureDate := time.Now().AddDate(0, 0, 10) + sitExpected := true + estimatedWeight := unit.Pound(1500) + hasProGear := true + ppmShipmentDestinationAddress := primev3messages.PPMDestinationAddress{ + City: destinationAddress.City, + PostalCode: destinationAddress.PostalCode, + State: destinationAddress.State, + StreetAddress1: destinationAddress.StreetAddress1, + StreetAddress2: destinationAddress.StreetAddress2, + StreetAddress3: destinationAddress.StreetAddress3, + } + ppmShipmentParams := primev3messages.CreatePPMShipment{ + PickupAddress: struct{ primev3messages.Address }{pickupAddress}, + DestinationAddress: struct { + primev3messages.PPMDestinationAddress + }{ppmShipmentDestinationAddress}, + TertiaryPickupAddress: struct{ primev3messages.Address }{tertiaryPickupAddress}, + ExpectedDepartureDate: handlers.FmtDate(expectedDepartureDate), + EstimatedWeight: handlers.FmtPoundPtr(&estimatedWeight), + HasProGear: &hasProGear, + SitExpected: &sitExpected, + } + params := mtoshipmentops.CreateMTOShipmentParams{ + HTTPRequest: req, + Body: &primev3messages.CreateMTOShipment{ + MoveTaskOrderID: handlers.FmtUUID(move.ID), + Agents: nil, + CustomerRemarks: nil, + PointOfContact: "John Doe", + PrimeEstimatedWeight: handlers.FmtInt64(1200), + RequestedPickupDate: handlers.FmtDatePtr(models.TimePointer(time.Now())), + ShipmentType: primev3messages.NewMTOShipmentType(primev3messages.MTOShipmentTypePPM), + PickupAddress: struct{ primev3messages.Address }{pickupAddress}, + DestinationAddress: struct{ primev3messages.Address }{destinationAddress}, + PpmShipment: &ppmShipmentParams, + }, + } + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.CreateMTOShipmentUnprocessableEntity{}, response) + errResponse := response.(*mtoshipmentops.CreateMTOShipmentUnprocessableEntity) + + suite.Contains(*errResponse.Payload.Detail, "Invalid input found while validating the PPM shipment.") + suite.Contains(errResponse.Payload.InvalidFields["error validating ppm shipment"][0], "Shipment cannot have a third address without a second address present") + }) + + suite.Run("POST failure - Error creating mto shipment containing a ppm shipment contains tertiary destination address no secondary destination address.", func() { + // Under Test: UpdateMTOShipmentHandler + // Setup: If underlying UpdateMTOShipment returns error, handler should return 422 response + // Expected: 422 Response returned + + handler, move := setupTestData(false, false) + + req := httptest.NewRequest("POST", "/mto-shipments", nil) + + expectedDepartureDate := time.Now().AddDate(0, 0, 10) + sitExpected := true + estimatedWeight := unit.Pound(1500) + hasProGear := true + ppmShipmentDestinationAddress := primev3messages.PPMDestinationAddress{ + City: destinationAddress.City, + PostalCode: destinationAddress.PostalCode, + State: destinationAddress.State, + StreetAddress1: destinationAddress.StreetAddress1, + StreetAddress2: destinationAddress.StreetAddress2, + StreetAddress3: destinationAddress.StreetAddress3, + } + ppmShipmentParams := primev3messages.CreatePPMShipment{ + PickupAddress: struct{ primev3messages.Address }{pickupAddress}, + DestinationAddress: struct { + primev3messages.PPMDestinationAddress + }{ppmShipmentDestinationAddress}, + TertiaryDestinationAddress: struct{ primev3messages.Address }{tertiaryDestinationAddress}, + ExpectedDepartureDate: handlers.FmtDate(expectedDepartureDate), + EstimatedWeight: handlers.FmtPoundPtr(&estimatedWeight), + HasProGear: &hasProGear, + SitExpected: &sitExpected, + } + params := mtoshipmentops.CreateMTOShipmentParams{ + HTTPRequest: req, + Body: &primev3messages.CreateMTOShipment{ + MoveTaskOrderID: handlers.FmtUUID(move.ID), + Agents: nil, + CustomerRemarks: nil, + PointOfContact: "John Doe", + PrimeEstimatedWeight: handlers.FmtInt64(1200), + RequestedPickupDate: handlers.FmtDatePtr(models.TimePointer(time.Now())), + ShipmentType: primev3messages.NewMTOShipmentType(primev3messages.MTOShipmentTypePPM), + PickupAddress: struct{ primev3messages.Address }{pickupAddress}, + DestinationAddress: struct{ primev3messages.Address }{destinationAddress}, + PpmShipment: &ppmShipmentParams, + }, + } + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.CreateMTOShipmentUnprocessableEntity{}, response) + errResponse := response.(*mtoshipmentops.CreateMTOShipmentUnprocessableEntity) + + suite.Contains(*errResponse.Payload.Detail, "Invalid input found while validating the PPM shipment.") + suite.Contains(errResponse.Payload.InvalidFields["error validating ppm shipment"][0], "Shipment cannot have a third address without a second address present") + }) + suite.Run("PATCH failure - Error updating an mto shipment contains tertiary pickup address no secondary pickup address.", func() { + // Under Test: UpdateMTOShipmentHandler + // Setup: If underlying CreateMTOShipment returns error, handler should return 422 response + // Expected: 422 Response returned + + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + patchHandler := UpdateMTOShipmentHandler{ + suite.HandlerConfig(), + shipmentUpdater, + } + + now := time.Now() + mto_shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "some address", + City: "city", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some address", + City: "city", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.DeliveryAddress, + }, + }, nil) + move := factory.BuildMoveWithPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + AvailableToPrimeAt: &now, + ApprovedAt: &now, + Status: models.MoveStatusAPPROVED, + }, + }, + }, nil) + + var testMove models.Move + err := suite.DB().EagerPreload("MTOShipments.PPMShipment").Find(&testMove, move.ID) + suite.NoError(err) + var testMtoShipment models.MTOShipment + err = suite.DB().Find(&testMtoShipment, mto_shipment.ID) + suite.NoError(err) + testMtoShipment.MoveTaskOrderID = testMove.ID + testMtoShipment.MoveTaskOrder = testMove + err = suite.DB().Save(&testMtoShipment) + suite.NoError(err) + testMove.MTOShipments = append(testMove.MTOShipments, mto_shipment) + err = suite.DB().Save(&testMove) + suite.NoError(err) + + patchReq := httptest.NewRequest("PATCH", fmt.Sprintf("/mto-shipments/%s", testMove.MTOShipments[0].ID), nil) + + eTag := etag.GenerateEtag(testMove.MTOShipments[0].UpdatedAt) + patchParams := mtoshipmentops.UpdateMTOShipmentParams{ + HTTPRequest: patchReq, + MtoShipmentID: strfmt.UUID(testMove.MTOShipments[0].ID.String()), + IfMatch: eTag, + } + tertiaryAddress := GetTestAddress() + patchParams.Body = &primev3messages.UpdateMTOShipment{ + TertiaryDeliveryAddress: struct{ primev3messages.Address }{tertiaryAddress}, + } + patchResponse := patchHandler.Handle(patchParams) + errResponse := patchResponse.(*mtoshipmentops.UpdateMTOShipmentUnprocessableEntity) + suite.Contains(*errResponse.Payload.Detail, "Invalid input found while validating the MTO shipment") + suite.Contains(errResponse.Payload.InvalidFields["error validating mto shipment"][0], "Shipment cannot have a third address without a second address present") + + }) + suite.Run("PATCH failure - Error updating an ppm shipment contains tertiary destination address no secondary destination address", func() { + // Under Test: UpdateMTOShipmentHandler + // Mocked: UpdateMTOShipment creator + // Setup: If underlying UpdateMTOShipment returns error, handler should return 422 response + // Expected: 422 Response returned + + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + patchHandler := UpdateMTOShipmentHandler{ + suite.HandlerConfig(), + shipmentUpdater, + } + + move := factory.BuildAvailableToPrimeMove(suite.DB(), []factory.Customization{}, nil) + factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + }, nil) + + var testMove models.Move + err := suite.DB().EagerPreload("MTOShipments.PPMShipment").Find(&testMove, move.ID) + suite.NoError(err) + + patchReq := httptest.NewRequest("PATCH", fmt.Sprintf("/mto-shipments/%s", testMove.MTOShipments[0].ID.String()), nil) + + eTag := etag.GenerateEtag(testMove.MTOShipments[0].UpdatedAt) + patchParams := mtoshipmentops.UpdateMTOShipmentParams{ + HTTPRequest: patchReq, + MtoShipmentID: strfmt.UUID(testMove.MTOShipments[0].ID.String()), + IfMatch: eTag, + } + tertiaryAddress := GetTestAddress() + ppmShipmentParamSetup := primev3messages.UpdatePPMShipment{ + HasTertiaryDestinationAddress: models.BoolPointer(true), + TertiaryDestinationAddress: struct{ primev3messages.Address }{tertiaryAddress}, + } + mtoShipmentParamSetup := primev3messages.UpdateMTOShipment{ + PpmShipment: &ppmShipmentParamSetup, + } + + patchParams.Body = &mtoShipmentParamSetup + patchResponse := patchHandler.Handle(patchParams) + errResponse := patchResponse.(*mtoshipmentops.UpdateMTOShipmentUnprocessableEntity) + suite.Contains(*errResponse.Payload.Detail, "Invalid input found while validating the PPM shipment") + suite.Contains(errResponse.Payload.InvalidFields["error validating ppm shipment"][0], "Shipment cannot have a third address without a second address present") + + }) + + suite.Run("PATCH sucess - updating an mto shipment contains tertiary pickup and secondary pickup address.", func() { + // Under Test: UpdateMTOShipmentHandler + // Setup: If underlying CreateMTOShipment returns error, handler should return 422 response + // Expected: 422 Response returned + + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + patchHandler := UpdateMTOShipmentHandler{ + suite.HandlerConfig(), + shipmentUpdater, + } + + now := time.Now() + mto_shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "some address", + City: "city", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some address", + City: "city", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.SecondaryPickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some address", + City: "city", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.DeliveryAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some address", + City: "city", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.SecondaryDeliveryAddress, + }, + }, nil) + move := factory.BuildMoveWithPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + AvailableToPrimeAt: &now, + ApprovedAt: &now, + Status: models.MoveStatusAPPROVED, + }, + }, + }, nil) + + var testMove models.Move + err := suite.DB().EagerPreload("MTOShipments.PPMShipment").Find(&testMove, move.ID) + suite.NoError(err) + var testMtoShipment models.MTOShipment + err = suite.DB().Find(&testMtoShipment, mto_shipment.ID) + suite.NoError(err) + testMtoShipment.MoveTaskOrderID = testMove.ID + testMtoShipment.MoveTaskOrder = testMove + err = suite.DB().Save(&testMtoShipment) + suite.NoError(err) + testMove.MTOShipments = append(testMove.MTOShipments, mto_shipment) + err = suite.DB().Save(&testMove) + suite.NoError(err) + + patchReq := httptest.NewRequest("PATCH", fmt.Sprintf("/mto-shipments/%s", testMove.MTOShipments[0].ID), nil) + + eTag := etag.GenerateEtag(testMtoShipment.UpdatedAt) + patchParams := mtoshipmentops.UpdateMTOShipmentParams{ + HTTPRequest: patchReq, + MtoShipmentID: strfmt.UUID(testMtoShipment.ID.String()), + IfMatch: eTag, + } + tertiaryAddress := GetTestAddress() + patchParams.Body = &primev3messages.UpdateMTOShipment{ + TertiaryDeliveryAddress: struct{ primev3messages.Address }{tertiaryAddress}, + } + patchResponse := patchHandler.Handle(patchParams) + response := patchResponse.(*mtoshipmentops.UpdateMTOShipmentOK) + suite.IsType(&mtoshipmentops.UpdateMTOShipmentOK{}, response) + }) +} +func GetTestAddress() primev3messages.Address { + newAddress := factory.BuildAddress(nil, []factory.Customization{ + { + Model: models.Address{ + ID: uuid.Must(uuid.NewV4()), + }, + }, + }, nil) + return primev3messages.Address{ + City: &newAddress.City, + PostalCode: &newAddress.PostalCode, + State: &newAddress.State, + StreetAddress1: &newAddress.StreetAddress1, + StreetAddress2: newAddress.StreetAddress2, + StreetAddress3: newAddress.StreetAddress3, + } } diff --git a/pkg/handlers/primeapiv3/payloads/model_to_payload.go b/pkg/handlers/primeapiv3/payloads/model_to_payload.go index ab2655b1bd8..b8973d3c99d 100644 --- a/pkg/handlers/primeapiv3/payloads/model_to_payload.go +++ b/pkg/handlers/primeapiv3/payloads/model_to_payload.go @@ -9,14 +9,17 @@ import ( "github.com/gobuffalo/validate/v3" "github.com/gofrs/uuid" + "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/etag" "github.com/transcom/mymove/pkg/gen/primev3messages" "github.com/transcom/mymove/pkg/handlers" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" ) // MoveTaskOrder payload -func MoveTaskOrder(moveTaskOrder *models.Move) *primev3messages.MoveTaskOrder { +func MoveTaskOrder(appCtx appcontext.AppContext, moveTaskOrder *models.Move) *primev3messages.MoveTaskOrder { + db := appCtx.DB() if moveTaskOrder == nil { return nil } @@ -24,6 +27,17 @@ func MoveTaskOrder(moveTaskOrder *models.Move) *primev3messages.MoveTaskOrder { mtoServiceItems := MTOServiceItems(&moveTaskOrder.MTOServiceItems) mtoShipments := MTOShipmentsWithoutServiceItems(&moveTaskOrder.MTOShipments) + var destGbloc, destZip string + var err error + destGbloc, err = moveTaskOrder.GetDestinationGBLOC(db) + if err != nil { + destGbloc = "" + } + destZip, err = moveTaskOrder.GetDestinationPostalCode(db) + if err != nil { + destZip = "" + } + payload := &primev3messages.MoveTaskOrder{ ID: strfmt.UUID(moveTaskOrder.ID.String()), MoveCode: moveTaskOrder.Locator, @@ -35,6 +49,8 @@ func MoveTaskOrder(moveTaskOrder *models.Move) *primev3messages.MoveTaskOrder { ExcessWeightUploadID: handlers.FmtUUIDPtr(moveTaskOrder.ExcessWeightUploadID), OrderID: strfmt.UUID(moveTaskOrder.OrdersID.String()), Order: Order(&moveTaskOrder.Orders), + DestinationGBLOC: destGbloc, + DestinationPostalCode: destZip, ReferenceID: *moveTaskOrder.ReferenceID, PaymentRequests: *paymentRequests, MtoShipments: *mtoShipments, @@ -58,6 +74,31 @@ func MoveTaskOrder(moveTaskOrder *models.Move) *primev3messages.MoveTaskOrder { return payload } +func MoveTaskOrderWithShipmentOconusRateArea(appCtx appcontext.AppContext, moveTaskOrder *models.Move, shipmentRateArea *[]services.ShipmentPostalCodeRateArea) *primev3messages.MoveTaskOrder { + // create default payload + var payload = MoveTaskOrder(appCtx, moveTaskOrder) + + // decorate payload with oconus rateArea information + if payload != nil && shipmentRateArea != nil { + // build map from incoming rateArea list to simplify rateArea lookup by postal code + var shipmentPostalCodeRateAreaLookupMap = make(map[string]services.ShipmentPostalCodeRateArea) + for _, ra := range *shipmentRateArea { + shipmentPostalCodeRateAreaLookupMap[ra.PostalCode] = ra + } + // Origin/Destination RateArea will be present on root shipment level for all non-PPM shipment types + for _, shipment := range payload.MtoShipments { + if shipment.PpmShipment != nil { + shipment.PpmShipment.OriginRateArea = PostalCodeToRateArea(shipment.PpmShipment.PickupAddress.PostalCode, shipmentPostalCodeRateAreaLookupMap) + shipment.PpmShipment.DestinationRateArea = PostalCodeToRateArea(shipment.PpmShipment.DestinationAddress.PostalCode, shipmentPostalCodeRateAreaLookupMap) + } else { + shipment.OriginRateArea = PostalCodeToRateArea(shipment.PickupAddress.PostalCode, shipmentPostalCodeRateAreaLookupMap) + shipment.DestinationRateArea = PostalCodeToRateArea(shipment.DestinationAddress.PostalCode, shipmentPostalCodeRateAreaLookupMap) + } + } + } + return payload +} + // Customer payload func Customer(customer *models.ServiceMember) *primev3messages.Customer { if customer == nil { @@ -93,7 +134,7 @@ 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.Entitlement.SetWeightAllotment(string(*order.Grade), order.OrdersType) } var grade string @@ -152,9 +193,14 @@ func Entitlement(entitlement *models.Entitlement) *primev3messages.Entitlements if entitlement.TotalDependents != nil { totalDependents = int64(*entitlement.TotalDependents) } + var ubAllowance int64 + if entitlement.UBAllowance != nil { + ubAllowance = int64(*entitlement.UBAllowance) + } return &primev3messages.Entitlements{ ID: strfmt.UUID(entitlement.ID.String()), AuthorizedWeight: authorizedWeight, + UnaccompaniedBaggageAllowance: &ubAllowance, DependentsAuthorized: entitlement.DependentsAuthorized, NonTemporaryStorage: entitlement.NonTemporaryStorage, PrivatelyOwnedVehicle: entitlement.PrivatelyOwnedVehicle, @@ -1016,3 +1062,14 @@ func MTOShipment(mtoShipment *models.MTOShipment) *primev3messages.MTOShipment { return payload } + +// PostalCodeToRateArea converts postalCode into RateArea model to payload +func PostalCodeToRateArea(postalCode *string, shipmentPostalCodeRateAreaMap map[string]services.ShipmentPostalCodeRateArea) *primev3messages.RateArea { + if postalCode == nil { + return nil + } + if ra, ok := shipmentPostalCodeRateAreaMap[*postalCode]; ok { + return &primev3messages.RateArea{ID: handlers.FmtUUID(ra.RateArea.ID), RateAreaID: &ra.RateArea.Code, RateAreaName: &ra.RateArea.Name} + } + return nil +} diff --git a/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go b/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go index c22dea1582d..71455157028 100644 --- a/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go +++ b/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go @@ -1,6 +1,8 @@ package payloads import ( + "encoding/json" + "slices" "time" "github.com/go-openapi/strfmt" @@ -13,6 +15,7 @@ import ( "github.com/transcom/mymove/pkg/gen/primev3messages" "github.com/transcom/mymove/pkg/handlers" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" "github.com/transcom/mymove/pkg/unit" ) @@ -79,7 +82,7 @@ func (suite *PayloadsSuite) TestMoveTaskOrder() { } suite.Run("Success - Returns a basic move payload with no payment requests, service items or shipments", func() { - returnedModel := MoveTaskOrder(&basicMove) + returnedModel := MoveTaskOrder(suite.AppContextForTest(), &basicMove) suite.IsType(&primev3messages.MoveTaskOrder{}, returnedModel) suite.Equal(strfmt.UUID(basicMove.ID.String()), returnedModel.ID) @@ -104,7 +107,275 @@ func (suite *PayloadsSuite) TestMoveTaskOrder() { suite.Require().NotEmpty(returnedModel.MtoShipments) suite.Equal(basicMove.MTOShipments[0].PickupAddress.County, *returnedModel.MtoShipments[0].PickupAddress.County) }) + + suite.Run("Success - payload with RateArea", func() { + cloneMove := func(orig *models.Move) (*models.Move, error) { + origJSON, err := json.Marshal(orig) + if err != nil { + return nil, err + } + + clone := models.Move{} + if err = json.Unmarshal(origJSON, &clone); err != nil { + return nil, err + } + + return &clone, nil + } + + newMove, err := cloneMove(&basicMove) + suite.NotNil(newMove) + suite.Nil(err) + + const fairbanksAlaskaPostalCode = "99716" + const anchorageAlaskaPostalCode = "99521" + const wasillaAlaskaPostalCode = "99652" + + //clear MTOShipment and rebuild with specifics for test + newMove.MTOShipments = newMove.MTOShipments[:0] + + newMove.MTOShipments = append(newMove.MTOShipments, models.MTOShipment{ + PickupAddress: &models.Address{ + StreetAddress1: "123 Main St", + StreetAddress2: &streetAddress2, + StreetAddress3: &streetAddress3, + City: "Fairbanks", + State: "AK", + PostalCode: fairbanksAlaskaPostalCode, + }, + DestinationAddress: &models.Address{ + StreetAddress1: "123 Main St", + StreetAddress2: &streetAddress2, + StreetAddress3: &streetAddress3, + City: "Anchorage", + State: "AK", + PostalCode: anchorageAlaskaPostalCode, + }, + }) + newMove.MTOShipments = append(newMove.MTOShipments, models.MTOShipment{ + PickupAddress: &models.Address{ + StreetAddress1: "123 Main St", + StreetAddress2: &streetAddress2, + StreetAddress3: &streetAddress3, + City: "Wasilla", + State: "AK", + PostalCode: wasillaAlaskaPostalCode, + }, + DestinationAddress: &models.Address{ + StreetAddress1: "123 Main St", + StreetAddress2: &streetAddress2, + StreetAddress3: &streetAddress3, + City: "Wasilla", + State: "AK", + PostalCode: wasillaAlaskaPostalCode, + }, + }) + newMove.MTOShipments = append(newMove.MTOShipments, models.MTOShipment{ + ShipmentType: models.MTOShipmentTypePPM, + PPMShipment: &models.PPMShipment{ + ID: uuid.Must(uuid.NewV4()), + ApprovedAt: models.TimePointer(time.Now()), + Status: models.PPMShipmentStatusNeedsAdvanceApproval, + ActualMoveDate: models.TimePointer(time.Now()), + ActualPickupPostalCode: models.StringPointer("42444"), + ActualDestinationPostalCode: models.StringPointer("30813"), + HasReceivedAdvance: models.BoolPointer(true), + AdvanceAmountReceived: models.CentPointer(unit.Cents(340000)), + FinalIncentive: models.CentPointer(50000000), + PickupAddress: &models.Address{ + StreetAddress1: "123 Main St", + StreetAddress2: &streetAddress2, + StreetAddress3: &streetAddress3, + City: "Wasilla", + State: "AK", + PostalCode: wasillaAlaskaPostalCode, + }, + DestinationAddress: &models.Address{ + StreetAddress1: "123 Main St", + StreetAddress2: &streetAddress2, + StreetAddress3: &streetAddress3, + City: "Wasilla", + State: "AK", + PostalCode: wasillaAlaskaPostalCode, + }, + }, + }) + newMove.MTOShipments = append(newMove.MTOShipments, models.MTOShipment{ + PPMShipment: &models.PPMShipment{ + ID: uuid.Must(uuid.NewV4()), + ApprovedAt: models.TimePointer(time.Now()), + Status: models.PPMShipmentStatusNeedsAdvanceApproval, + ActualMoveDate: models.TimePointer(time.Now()), + ActualPickupPostalCode: models.StringPointer("42444"), + ActualDestinationPostalCode: models.StringPointer("30813"), + HasReceivedAdvance: models.BoolPointer(true), + AdvanceAmountReceived: models.CentPointer(unit.Cents(340000)), + FinalIncentive: models.CentPointer(50000000), + PickupAddress: &models.Address{ + StreetAddress1: "123 Main St", + StreetAddress2: &streetAddress2, + StreetAddress3: &streetAddress3, + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + DestinationAddress: &models.Address{ + StreetAddress1: "123 Main St", + StreetAddress2: &streetAddress2, + StreetAddress3: &streetAddress3, + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + }, + }) + newMove.MTOShipments = append(newMove.MTOShipments, models.MTOShipment{ + PickupAddress: &models.Address{ + StreetAddress1: "123 Main St", + StreetAddress2: &streetAddress2, + StreetAddress3: &streetAddress3, + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + DestinationAddress: &models.Address{ + StreetAddress1: "123 Main St", + StreetAddress2: &streetAddress2, + StreetAddress3: &streetAddress3, + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + }) + + // no ShipmentPostalCodeRateArea passed in + returnedModel := MoveTaskOrderWithShipmentOconusRateArea(suite.AppContextForTest(), newMove, nil) + + suite.IsType(&primev3messages.MoveTaskOrder{}, returnedModel) + suite.Equal(strfmt.UUID(newMove.ID.String()), returnedModel.ID) + suite.Equal(newMove.Locator, returnedModel.MoveCode) + suite.Equal(strfmt.DateTime(newMove.CreatedAt), returnedModel.CreatedAt) + suite.Equal(handlers.FmtDateTimePtr(newMove.AvailableToPrimeAt), returnedModel.AvailableToPrimeAt) + suite.Equal(strfmt.UUID(newMove.OrdersID.String()), returnedModel.OrderID) + suite.Equal(ordersType, returnedModel.Order.OrdersType) + suite.Equal(shipmentGBLOC, returnedModel.Order.OriginDutyLocationGBLOC) + suite.Equal(referenceID, returnedModel.ReferenceID) + suite.Equal(strfmt.DateTime(newMove.UpdatedAt), returnedModel.UpdatedAt) + suite.NotEmpty(returnedModel.ETag) + suite.True(returnedModel.ExcessWeightQualifiedAt.Equal(strfmt.DateTime(*newMove.ExcessWeightQualifiedAt))) + suite.True(returnedModel.ExcessWeightAcknowledgedAt.Equal(strfmt.DateTime(*newMove.ExcessWeightAcknowledgedAt))) + suite.Require().NotNil(returnedModel.ExcessWeightUploadID) + suite.Equal(strfmt.UUID(newMove.ExcessWeightUploadID.String()), *returnedModel.ExcessWeightUploadID) + suite.Equal(factory.DefaultContractNumber, returnedModel.ContractNumber) + suite.Equal(models.SupplyAndServicesCostEstimate, returnedModel.Order.SupplyAndServicesCostEstimate) + suite.Equal(models.MethodOfPayment, returnedModel.Order.MethodOfPayment) + suite.Equal(models.NAICS, returnedModel.Order.Naics) + suite.Equal(packingInstructions, returnedModel.Order.PackingAndShippingInstructions) + suite.Require().NotEmpty(returnedModel.MtoShipments) + suite.Equal(newMove.MTOShipments[0].PickupAddress.County, *returnedModel.MtoShipments[0].PickupAddress.County) + + // verify there are no RateArea set because no ShipmentPostalCodeRateArea passed in. + for _, shipment := range returnedModel.MtoShipments { + suite.Nil(shipment.OriginRateArea) + suite.Nil(shipment.DestinationRateArea) + if shipment.PpmShipment != nil { + suite.Nil(shipment.PpmShipment.OriginRateArea) + suite.Nil(shipment.PpmShipment.DestinationRateArea) + } + } + + // mock up ShipmentPostalCodeRateArea + shipmentPostalCodeRateArea := []services.ShipmentPostalCodeRateArea{ + { + PostalCode: fairbanksAlaskaPostalCode, + RateArea: &models.ReRateArea{ + ID: uuid.Must(uuid.NewV4()), + Code: fairbanksAlaskaPostalCode, + Name: fairbanksAlaskaPostalCode, + }, + }, + { + PostalCode: anchorageAlaskaPostalCode, + RateArea: &models.ReRateArea{ + ID: uuid.Must(uuid.NewV4()), + Code: anchorageAlaskaPostalCode, + Name: anchorageAlaskaPostalCode, + }, + }, + { + PostalCode: wasillaAlaskaPostalCode, + RateArea: &models.ReRateArea{ + ID: uuid.Must(uuid.NewV4()), + Code: wasillaAlaskaPostalCode, + Name: wasillaAlaskaPostalCode, + }, + }, + } + + returnedModel = MoveTaskOrderWithShipmentOconusRateArea(suite.AppContextForTest(), newMove, &shipmentPostalCodeRateArea) + + var shipmentPostalCodeRateAreaLookupMap = make(map[string]services.ShipmentPostalCodeRateArea) + for _, i := range shipmentPostalCodeRateArea { + shipmentPostalCodeRateAreaLookupMap[i.PostalCode] = i + } + + // test Alaska/Oconus PostCodes have associative RateArea for respective shipment + expectedAlaskaPostalCodes := []string{fairbanksAlaskaPostalCode, anchorageAlaskaPostalCode, wasillaAlaskaPostalCode} + for _, shipment := range returnedModel.MtoShipments { + if shipment.PpmShipment != nil { + suite.NotNil(shipment.PpmShipment.PickupAddress) + suite.NotNil(shipment.PpmShipment.DestinationAddress) + if slices.Contains(expectedAlaskaPostalCodes, *shipment.PpmShipment.PickupAddress.PostalCode) { + // verify mapping of RateArea is correct + ra, contains := shipmentPostalCodeRateAreaLookupMap[*shipment.PpmShipment.PickupAddress.PostalCode] + suite.True(contains) + suite.NotNil(shipment.PpmShipment.OriginRateArea) + // for testing purposes RateArea code/names are using postalCodes as value + suite.Equal(ra.PostalCode, *shipment.PpmShipment.PickupAddress.PostalCode) + suite.Equal(ra.PostalCode, *shipment.PpmShipment.OriginRateArea.RateAreaName) + } else { + suite.Nil(shipment.PpmShipment.OriginRateArea) + } + if slices.Contains(expectedAlaskaPostalCodes, *shipment.PpmShipment.DestinationAddress.PostalCode) { + ra, contains := shipmentPostalCodeRateAreaLookupMap[*shipment.PpmShipment.DestinationAddress.PostalCode] + suite.True(contains) + suite.NotNil(shipment.PpmShipment.DestinationRateArea) + suite.Equal(ra.PostalCode, *shipment.PpmShipment.DestinationAddress.PostalCode) + suite.Equal(ra.PostalCode, *shipment.PpmShipment.DestinationRateArea.RateAreaName) + } else { + suite.Nil(shipment.PpmShipment.DestinationRateArea) + } + // because it's PPM verify root doesnt have rateArea for org/dest + suite.Nil(shipment.OriginRateArea) + suite.Nil(shipment.DestinationRateArea) + } else { + suite.NotNil(shipment.PickupAddress) + suite.NotNil(shipment.DestinationAddress) + if slices.Contains(expectedAlaskaPostalCodes, *shipment.PickupAddress.PostalCode) { + ra, contains := shipmentPostalCodeRateAreaLookupMap[*shipment.PickupAddress.PostalCode] + suite.True(contains) + suite.NotNil(shipment.OriginRateArea) + suite.Equal(ra.PostalCode, *shipment.PickupAddress.PostalCode) + suite.Equal(ra.PostalCode, *shipment.OriginRateArea.RateAreaName) + suite.NotNil(shipment.OriginRateArea) + } else { + suite.Nil(shipment.OriginRateArea) + } + if slices.Contains(expectedAlaskaPostalCodes, *shipment.DestinationAddress.PostalCode) { + ra, contains := shipmentPostalCodeRateAreaLookupMap[*shipment.DestinationAddress.PostalCode] + suite.True(contains) + suite.NotNil(shipment.DestinationRateArea) + suite.Equal(ra.PostalCode, *shipment.DestinationAddress.PostalCode) + suite.Equal(ra.PostalCode, *shipment.DestinationRateArea.RateAreaName) + suite.NotNil(shipment.OriginRateArea) + } else { + suite.Nil(shipment.OriginRateArea) + } + } + } + }) } + func (suite *PayloadsSuite) TestReweigh() { id, _ := uuid.NewV4() shipmentID, _ := uuid.NewV4() @@ -240,6 +511,7 @@ func (suite *PayloadsSuite) TestEntitlement() { NonTemporaryStorage: nil, PrivatelyOwnedVehicle: nil, DBAuthorizedWeight: nil, + UBAllowance: nil, StorageInTransit: nil, RequiredMedicalEquipmentWeight: 0, OrganizationalClothingAndIndividualEquipment: false, @@ -268,6 +540,7 @@ func (suite *PayloadsSuite) TestEntitlement() { suite.Equal(int64(0), payload.StorageInTransit) suite.Equal(int64(0), payload.TotalDependents) suite.Equal(int64(0), payload.TotalWeight) + suite.Equal(int64(0), *payload.UnaccompaniedBaggageAllowance) }) suite.Run("Success - Returns the entitlement payload with all optional fields populated", func() { @@ -278,6 +551,7 @@ func (suite *PayloadsSuite) TestEntitlement() { NonTemporaryStorage: handlers.FmtBool(true), PrivatelyOwnedVehicle: handlers.FmtBool(true), DBAuthorizedWeight: handlers.FmtInt(10000), + UBAllowance: handlers.FmtInt(400), StorageInTransit: handlers.FmtInt(45), RequiredMedicalEquipmentWeight: 500, OrganizationalClothingAndIndividualEquipment: true, @@ -289,7 +563,7 @@ 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)) + entitlement.SetWeightAllotment(string(models.ServiceMemberGradeE5), internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) payload := Entitlement(&entitlement) @@ -299,6 +573,8 @@ func (suite *PayloadsSuite) TestEntitlement() { 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) @@ -317,6 +593,7 @@ func (suite *PayloadsSuite) TestEntitlement() { NonTemporaryStorage: handlers.FmtBool(true), PrivatelyOwnedVehicle: handlers.FmtBool(true), DBAuthorizedWeight: handlers.FmtInt(10000), + UBAllowance: handlers.FmtInt(400), StorageInTransit: handlers.FmtInt(45), RequiredMedicalEquipmentWeight: 500, OrganizationalClothingAndIndividualEquipment: true, @@ -328,7 +605,7 @@ 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)) + entitlement.SetWeightAllotment(string(models.ServiceMemberGradeE5), internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) payload := Entitlement(&entitlement) @@ -338,6 +615,7 @@ func (suite *PayloadsSuite) TestEntitlement() { 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) @@ -936,6 +1214,55 @@ func (suite *PayloadsSuite) TestBoatShipment() { suite.NotNil(result) } +func (suite *PayloadsSuite) TestDestinationPostalCodeAndGBLOC() { + moveID := uuid.Must(uuid.NewV4()) + moveLocator := "TESTTEST" + primeTime := time.Now() + ordersID := uuid.Must(uuid.NewV4()) + refID := "123456" + contractNum := "HTC-123-456" + address := models.Address{PostalCode: "35023"} + shipment := models.MTOShipment{ + ID: uuid.Must(uuid.NewV4()), + DestinationAddress: &address, + } + shipments := models.MTOShipments{shipment} + contractor := models.Contractor{ + ContractNumber: contractNum, + } + + basicMove := models.Move{ + ID: moveID, + Locator: moveLocator, + CreatedAt: primeTime, + ReferenceID: &refID, + AvailableToPrimeAt: &primeTime, + ApprovedAt: &primeTime, + OrdersID: ordersID, + Contractor: &contractor, + PaymentRequests: models.PaymentRequests{}, + SubmittedAt: &primeTime, + UpdatedAt: primeTime, + Status: models.MoveStatusAPPROVED, + SignedCertifications: models.SignedCertifications{}, + MTOServiceItems: models.MTOServiceItems{}, + MTOShipments: shipments, + } + + suite.Run("Returns values needed to get the destination postal code and GBLOC", func() { + returnedModel := MoveTaskOrder(suite.AppContextForTest(), &basicMove) + + suite.IsType(&primev3messages.MoveTaskOrder{}, returnedModel) + suite.Equal(strfmt.UUID(basicMove.ID.String()), returnedModel.ID) + suite.Equal(basicMove.Locator, returnedModel.MoveCode) + suite.Equal(strfmt.DateTime(basicMove.CreatedAt), returnedModel.CreatedAt) + suite.Equal(handlers.FmtDateTimePtr(basicMove.AvailableToPrimeAt), returnedModel.AvailableToPrimeAt) + suite.Equal(strfmt.UUID(basicMove.OrdersID.String()), returnedModel.OrderID) + suite.Equal(strfmt.DateTime(basicMove.UpdatedAt), returnedModel.UpdatedAt) + suite.NotEmpty(returnedModel.ETag) + }) +} + func (suite *PayloadsSuite) TestMarketCode() { suite.Run("returns nil when marketCode is nil", func() { var marketCode *models.MarketCode = nil diff --git a/pkg/handlers/primeapiv3/payloads/payload_to_model.go b/pkg/handlers/primeapiv3/payloads/payload_to_model.go index a5cac483787..7eb48d87ad7 100644 --- a/pkg/handlers/primeapiv3/payloads/payload_to_model.go +++ b/pkg/handlers/primeapiv3/payloads/payload_to_model.go @@ -557,6 +557,8 @@ func PPMShipmentModelFromUpdate(ppmShipment *primev3messages.UpdatePPMShipment) SpouseProGearWeight: handlers.PoundPtrFromInt64Ptr(ppmShipment.SpouseProGearWeight), HasSecondaryPickupAddress: ppmShipment.HasSecondaryPickupAddress, HasSecondaryDestinationAddress: ppmShipment.HasSecondaryDestinationAddress, + HasTertiaryPickupAddress: ppmShipment.HasTertiaryPickupAddress, + HasTertiaryDestinationAddress: ppmShipment.HasTertiaryDestinationAddress, } // Set up address models diff --git a/pkg/handlers/primeapiv3/payloads/payload_to_model_test.go b/pkg/handlers/primeapiv3/payloads/payload_to_model_test.go index cadf7aa9b99..b5a3b803388 100644 --- a/pkg/handlers/primeapiv3/payloads/payload_to_model_test.go +++ b/pkg/handlers/primeapiv3/payloads/payload_to_model_test.go @@ -779,6 +779,86 @@ func (suite *PayloadsSuite) TestPPMShipmentModelFromCreate() { suite.NotNil(model) } +func (suite *PayloadsSuite) TestPPMShipmentModelWithOptionalDestinationStreet1FromCreate() { + time := time.Now() + expectedDepartureDate := handlers.FmtDatePtr(&time) + + country := models.Country{ + Country: "US", + CountryName: "United States", + } + + address := models.Address{ + StreetAddress1: "some address", + City: "city", + State: "state", + PostalCode: "12345", + Country: &country, + } + + var pickupAddress primev3messages.Address + var destinationAddress primev3messages.PPMDestinationAddress + + pickupAddress = primev3messages.Address{ + City: &address.City, + Country: &address.Country.Country, + PostalCode: &address.PostalCode, + State: &address.State, + StreetAddress1: &address.StreetAddress1, + StreetAddress2: address.StreetAddress2, + StreetAddress3: address.StreetAddress3, + } + destinationAddress = primev3messages.PPMDestinationAddress{ + City: &address.City, + Country: &address.Country.Country, + PostalCode: &address.PostalCode, + State: &address.State, + StreetAddress1: models.StringPointer(""), // empty string + StreetAddress2: address.StreetAddress2, + StreetAddress3: address.StreetAddress3, + } + + ppmShipment := primev3messages.UpdatePPMShipment{ + ExpectedDepartureDate: expectedDepartureDate, + PickupAddress: struct{ primev3messages.Address }{pickupAddress}, + DestinationAddress: struct { + primev3messages.PPMDestinationAddress + }{destinationAddress}, + } + + model := PPMShipmentModelFromUpdate(&ppmShipment) + + suite.NotNil(model) + suite.Equal(model.DestinationAddress.StreetAddress1, models.STREET_ADDRESS_1_NOT_PROVIDED) + + // test when street address 1 contains white spaces + destinationAddress.StreetAddress1 = models.StringPointer(" ") + ppmShipmentWhiteSpaces := primev3messages.UpdatePPMShipment{ + ExpectedDepartureDate: expectedDepartureDate, + PickupAddress: struct{ primev3messages.Address }{pickupAddress}, + DestinationAddress: struct { + primev3messages.PPMDestinationAddress + }{destinationAddress}, + } + + model2 := PPMShipmentModelFromUpdate(&ppmShipmentWhiteSpaces) + suite.Equal(model2.DestinationAddress.StreetAddress1, models.STREET_ADDRESS_1_NOT_PROVIDED) + + // test with valid street address 2 + streetAddress1 := "1234 Street" + destinationAddress.StreetAddress1 = &streetAddress1 + ppmShipmentValidDestinatonStreet1 := primev3messages.UpdatePPMShipment{ + ExpectedDepartureDate: expectedDepartureDate, + PickupAddress: struct{ primev3messages.Address }{pickupAddress}, + DestinationAddress: struct { + primev3messages.PPMDestinationAddress + }{destinationAddress}, + } + + model3 := PPMShipmentModelFromUpdate(&ppmShipmentValidDestinatonStreet1) + suite.Equal(model3.DestinationAddress.StreetAddress1, streetAddress1) +} + func (suite *PayloadsSuite) TestPPMShipmentModelFromUpdate() { time := time.Now() expectedDepartureDate := handlers.FmtDatePtr(&time) @@ -1175,79 +1255,6 @@ func (suite *PayloadsSuite) TestMTOShipmentModelFromCreate_WithOptionalFields() suite.Equal("1010 Oak St", result.TertiaryDeliveryAddress.StreetAddress1) } -func (suite *PayloadsSuite) TestPPMShipmentModelWithOptionalDestinationStreet1FromCreate() { - time := time.Now() - expectedDepartureDate := handlers.FmtDatePtr(&time) - - address := models.Address{ - StreetAddress1: "some address", - City: "city", - State: "state", - PostalCode: "12345", - } - - var pickupAddress primev3messages.Address - var destinationAddress primev3messages.PPMDestinationAddress - - pickupAddress = primev3messages.Address{ - City: &address.City, - PostalCode: &address.PostalCode, - State: &address.State, - StreetAddress1: &address.StreetAddress1, - StreetAddress2: address.StreetAddress2, - StreetAddress3: address.StreetAddress3, - } - destinationAddress = primev3messages.PPMDestinationAddress{ - City: &address.City, - PostalCode: &address.PostalCode, - State: &address.State, - StreetAddress1: models.StringPointer(""), // empty string - StreetAddress2: address.StreetAddress2, - StreetAddress3: address.StreetAddress3, - } - - ppmShipment := primev3messages.CreatePPMShipment{ - ExpectedDepartureDate: expectedDepartureDate, - PickupAddress: struct{ primev3messages.Address }{pickupAddress}, - DestinationAddress: struct { - primev3messages.PPMDestinationAddress - }{destinationAddress}, - } - - model := PPMShipmentModelFromCreate(&ppmShipment) - - suite.NotNil(model) - suite.Equal(models.PPMShipmentStatusSubmitted, model.Status) - suite.Equal(model.DestinationAddress.StreetAddress1, models.STREET_ADDRESS_1_NOT_PROVIDED) - - // test when street address 1 contains white spaces - destinationAddress.StreetAddress1 = models.StringPointer(" ") - ppmShipmentWhiteSpaces := primev3messages.CreatePPMShipment{ - ExpectedDepartureDate: expectedDepartureDate, - PickupAddress: struct{ primev3messages.Address }{pickupAddress}, - DestinationAddress: struct { - primev3messages.PPMDestinationAddress - }{destinationAddress}, - } - - model2 := PPMShipmentModelFromCreate(&ppmShipmentWhiteSpaces) - suite.Equal(model2.DestinationAddress.StreetAddress1, models.STREET_ADDRESS_1_NOT_PROVIDED) - - // test with valid street address 2 - streetAddress1 := "1234 Street" - destinationAddress.StreetAddress1 = &streetAddress1 - ppmShipmentValidDestinatonStreet1 := primev3messages.CreatePPMShipment{ - ExpectedDepartureDate: expectedDepartureDate, - PickupAddress: struct{ primev3messages.Address }{pickupAddress}, - DestinationAddress: struct { - primev3messages.PPMDestinationAddress - }{destinationAddress}, - } - - model3 := PPMShipmentModelFromCreate(&ppmShipmentValidDestinatonStreet1) - suite.Equal(model3.DestinationAddress.StreetAddress1, streetAddress1) -} - func (suite *PayloadsSuite) TestPPMShipmentModelWithOptionalDestinationStreet1FromUpdate() { time := time.Now() expectedDepartureDate := handlers.FmtDatePtr(&time) diff --git a/pkg/handlers/routing/routing_init.go b/pkg/handlers/routing/routing_init.go index df74fc921b4..0abd6d434ca 100644 --- a/pkg/handlers/routing/routing_init.go +++ b/pkg/handlers/routing/routing_init.go @@ -222,6 +222,11 @@ func mountHealthRoute(appCtx appcontext.AppContext, redisPool *redis.Pool, } func mountLocalStorageRoute(appCtx appcontext.AppContext, routingConfig *Config, site chi.Router) { + appCtx.Logger().Info("Mounting local storage route", + zap.String("LocalStorageRoot", routingConfig.LocalStorageRoot), + zap.String("LocalStorageWebRoot", routingConfig.LocalStorageWebRoot), + ) + if routingConfig.LocalStorageRoot != "" && routingConfig.LocalStorageWebRoot != "" { localStorageHandlerFunc := storage.NewFilesystemHandler( routingConfig.FileSystem, routingConfig.LocalStorageRoot) diff --git a/pkg/handlers/supportapi/api.go b/pkg/handlers/supportapi/api.go index dd2a5df8b8f..1aba70b9c72 100644 --- a/pkg/handlers/supportapi/api.go +++ b/pkg/handlers/supportapi/api.go @@ -88,7 +88,7 @@ func NewSupportAPIHandler(handlerConfig handlers.HandlerConfig) http.Handler { mtoserviceitem.NewMTOServiceItemCreator(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()), handlerConfig.HHGPlanner()), } - supportAPI.MtoServiceItemUpdateMTOServiceItemStatusHandler = UpdateMTOServiceItemStatusHandler{handlerConfig, mtoserviceitem.NewMTOServiceItemUpdater(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, shipmentFetcher, addressCreator)} + supportAPI.MtoServiceItemUpdateMTOServiceItemStatusHandler = UpdateMTOServiceItemStatusHandler{handlerConfig, mtoserviceitem.NewMTOServiceItemUpdater(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, shipmentFetcher, addressCreator, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer(), ghcrateengine.NewDomesticDestinationSITDeliveryPricer(), ghcrateengine.NewDomesticOriginSITFuelSurchargePricer())} supportAPI.WebhookReceiveWebhookNotificationHandler = ReceiveWebhookNotificationHandler{handlerConfig} // Create TAC and LOA services diff --git a/pkg/handlers/supportapi/internal/payloads/model_to_payload.go b/pkg/handlers/supportapi/internal/payloads/model_to_payload.go index 6f234e1321b..e3637f112e3 100644 --- a/pkg/handlers/supportapi/internal/payloads/model_to_payload.go +++ b/pkg/handlers/supportapi/internal/payloads/model_to_payload.go @@ -86,7 +86,7 @@ func Order(order *models.Order) *supportmessages.Order { originDutyLocation := DutyLocation(order.OriginDutyLocation) uploadedOrders := Document(&order.UploadedOrders) if order.Grade != nil && order.Entitlement != nil { - order.Entitlement.SetWeightAllotment(string(*order.Grade)) + order.Entitlement.SetWeightAllotment(string(*order.Grade), order.OrdersType) } reportByDate := strfmt.Date(order.ReportByDate) @@ -147,9 +147,14 @@ func Entitlement(entitlement *models.Entitlement) *supportmessages.Entitlement { if entitlement.TotalDependents != nil { totalDependents = int64(*entitlement.TotalDependents) } + var ubAllowance int64 + if entitlement.UBAllowance != nil { + ubAllowance = int64(*entitlement.UBAllowance) + } return &supportmessages.Entitlement{ ID: strfmt.UUID(entitlement.ID.String()), AuthorizedWeight: authorizedWeight, + UnaccompaniedBaggageAllowance: &ubAllowance, DependentsAuthorized: entitlement.DependentsAuthorized, NonTemporaryStorage: entitlement.NonTemporaryStorage, PrivatelyOwnedVehicle: entitlement.PrivatelyOwnedVehicle, 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 21ab990bce2..05852f8b087 100644 --- a/pkg/handlers/supportapi/internal/payloads/model_to_payload_test.go +++ b/pkg/handlers/supportapi/internal/payloads/model_to_payload_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/assert" "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" ) @@ -28,6 +29,7 @@ func TestEntitlement(t *testing.T) { NonTemporaryStorage: nil, PrivatelyOwnedVehicle: nil, DBAuthorizedWeight: nil, + UBAllowance: nil, StorageInTransit: nil, RequiredMedicalEquipmentWeight: 0, OrganizationalClothingAndIndividualEquipment: false, @@ -56,6 +58,7 @@ func TestEntitlement(t *testing.T) { 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) }) t.Run("Success - Returns the entitlement payload with all optional fields populated", func(t *testing.T) { @@ -66,6 +69,7 @@ func TestEntitlement(t *testing.T) { NonTemporaryStorage: handlers.FmtBool(true), PrivatelyOwnedVehicle: handlers.FmtBool(true), DBAuthorizedWeight: handlers.FmtInt(10000), + UBAllowance: handlers.FmtInt(400), StorageInTransit: handlers.FmtInt(45), RequiredMedicalEquipmentWeight: 500, OrganizationalClothingAndIndividualEquipment: true, @@ -77,7 +81,7 @@ 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)) + entitlement.SetWeightAllotment(string(models.ServiceMemberGradeE5), internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) payload := Entitlement(&entitlement) @@ -87,6 +91,7 @@ func TestEntitlement(t *testing.T) { 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) @@ -105,6 +110,7 @@ func TestEntitlement(t *testing.T) { NonTemporaryStorage: handlers.FmtBool(true), PrivatelyOwnedVehicle: handlers.FmtBool(true), DBAuthorizedWeight: handlers.FmtInt(10000), + UBAllowance: handlers.FmtInt(400), StorageInTransit: handlers.FmtInt(45), RequiredMedicalEquipmentWeight: 500, OrganizationalClothingAndIndividualEquipment: true, @@ -116,7 +122,7 @@ 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)) + entitlement.SetWeightAllotment(string(models.ServiceMemberGradeE5), internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) payload := Entitlement(&entitlement) @@ -126,6 +132,7 @@ func TestEntitlement(t *testing.T) { 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) diff --git a/pkg/handlers/supportapi/mto_service_item_test.go b/pkg/handlers/supportapi/mto_service_item_test.go index 44a56716688..5db466e8d52 100644 --- a/pkg/handlers/supportapi/mto_service_item_test.go +++ b/pkg/handlers/supportapi/mto_service_item_test.go @@ -22,6 +22,7 @@ import ( "github.com/transcom/mymove/pkg/models" routemocks "github.com/transcom/mymove/pkg/route/mocks" "github.com/transcom/mymove/pkg/services/address" + "github.com/transcom/mymove/pkg/services/ghcrateengine" moverouter "github.com/transcom/mymove/pkg/services/move" mtoserviceitem "github.com/transcom/mymove/pkg/services/mto_service_item" mtoshipment "github.com/transcom/mymove/pkg/services/mto_shipment" @@ -85,7 +86,7 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemStatusHandlerApproveSuccess() mock.Anything, ).Return(400, nil) handler := UpdateMTOServiceItemStatusHandler{handlerConfig, - mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator), + mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer(), ghcrateengine.NewDomesticDestinationSITDeliveryPricer(), ghcrateengine.NewDomesticOriginSITFuelSurchargePricer()), } // CALL FUNCTION UNDER TEST @@ -141,7 +142,7 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemStatusHandlerRejectSuccess() mock.Anything, ).Return(400, nil) handler := UpdateMTOServiceItemStatusHandler{handlerConfig, - mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator), + mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer(), ghcrateengine.NewDomesticDestinationSITDeliveryPricer(), ghcrateengine.NewDomesticOriginSITFuelSurchargePricer()), } // CALL FUNCTION UNDER TEST @@ -197,7 +198,7 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemStatusHandlerRejectionFailedN mock.Anything, ).Return(400, nil) handler := UpdateMTOServiceItemStatusHandler{handlerConfig, - mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator), + mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer(), ghcrateengine.NewDomesticDestinationSITDeliveryPricer(), ghcrateengine.NewDomesticOriginSITFuelSurchargePricer()), } // CALL FUNCTION UNDER TEST diff --git a/pkg/models/address.go b/pkg/models/address.go index 5d34502e21c..df2cc66adaa 100644 --- a/pkg/models/address.go +++ b/pkg/models/address.go @@ -11,6 +11,8 @@ import ( "github.com/gofrs/uuid" "go.uber.org/zap" "go.uber.org/zap/zapcore" + + "github.com/transcom/mymove/pkg/utils" ) const STREET_ADDRESS_1_NOT_PROVIDED string = "n/a" @@ -155,6 +157,15 @@ func (a *Address) Copy() *Address { } return nil } +func IsAddressEmpty(a *Address) bool { + return a == nil || ((utils.IsNullOrWhiteSpace(&a.StreetAddress1) || IsDefaultAddressValue(a.StreetAddress1)) && + (utils.IsNullOrWhiteSpace(&a.City) || IsDefaultAddressValue(a.City)) && + (utils.IsNullOrWhiteSpace(&a.State) || IsDefaultAddressValue(a.State)) && + (utils.IsNullOrWhiteSpace(&a.PostalCode) || IsDefaultAddressValue(a.PostalCode))) +} +func IsDefaultAddressValue(s string) bool { + return s == "n/a" +} // Check if an address is CONUS or OCONUS func IsAddressOconus(db *pop.Connection, address Address) (bool, error) { diff --git a/pkg/models/address_test.go b/pkg/models/address_test.go index 6b5e016911d..a60b45a8114 100644 --- a/pkg/models/address_test.go +++ b/pkg/models/address_test.go @@ -128,3 +128,30 @@ func (suite *ModelSuite) TestAddressFormat() { suite.Equal("street 1, street 2, street 3, city, state, 90210, UNITED STATES", formattedAddress) } + +func (suite *ModelSuite) TestAddressIsEmpty() { + suite.Run("empty whitespace address", func() { + testAddress := m.Address{ + StreetAddress1: " ", + State: " ", + PostalCode: " ", + } + suite.True(m.IsAddressEmpty(&testAddress)) + }) + suite.Run("empty n/a address", func() { + testAddress := m.Address{ + StreetAddress1: "n/a", + State: "n/a", + PostalCode: "n/a", + } + suite.True(m.IsAddressEmpty(&testAddress)) + }) + suite.Run("nonempty address", func() { + testAddress := m.Address{ + StreetAddress1: "street 1", + State: "state", + PostalCode: "90210", + } + suite.False(m.IsAddressEmpty(&testAddress)) + }) +} diff --git a/pkg/models/duty_location.go b/pkg/models/duty_location.go index 16716a2e3ef..57feab234c1 100644 --- a/pkg/models/duty_location.go +++ b/pkg/models/duty_location.go @@ -113,27 +113,120 @@ func FindDutyLocationsExcludingStates(tx *pop.Connection, search string, exclusi } sql_builder := strings.Builder{} - sql_builder.WriteString(`with names as ( - (select id as duty_location_id, name, similarity(name, $1) as sim - from duty_locations - where name % $1 - order by sim desc - limit 5) + sql_builder.WriteString(`with names as + ( + ( + -- search against duty_locations table + ( + select + id as duty_location_id, + name, + similarity(name, $1) as sim + from + duty_locations + where + name % $1 + order by sim desc limit 5 + ) + -- exclude OCONUS locations that are not active + except + ( + select + d.id as duty_location_id, + d.name, + similarity(d.name, $1) as sim + from + duty_locations d, + addresses a, + re_oconus_rate_areas o, + re_rate_areas r + where d.name % $1 + and d.address_id = a.id + and a.us_post_region_cities_id = o.us_post_region_cities_id + and o.rate_area_id = r.id + and o.active = false + ) + ) union - (select duty_location_id, name, similarity(name, $1) as sim - from duty_location_names - where name % $1 - order by sim desc - limit 5) + ( + -- search against duty_location_names table for alternative names + ( + select + duty_location_id, + name, + similarity(name, $1) as sim + from + duty_location_names + where + name % $1 + order by sim desc limit 5 + ) + -- exclude OCONUS locations that are not active + except + ( + select + dn.duty_location_id, + dn.name, + similarity(dn.name, $1) as sim + from + duty_location_names dn, + duty_locations d, + addresses a, + re_oconus_rate_areas o, + re_rate_areas r + where + dn.name % $1 + and dn.duty_location_id = d.id + and d.address_id = a.id + and a.us_post_region_cities_id = o.us_post_region_cities_id + and o.rate_area_id = r.id + and o.active = false + order by sim desc limit 5 + ) + ) union - (select dl.id as duty_location_id, dl.name as name, 1 as sim - from duty_locations as dl - inner join addresses a2 on dl.address_id = a2.id and dl.affiliation is null - where a2.postal_code ILIKE $1 - limit 5) + ( + -- search against duty_locations table if postal code + ( + select + dl.id as duty_location_id, + dl.name as name, + 1 as sim + from + duty_locations as dl + inner join addresses a2 on dl.address_id = a2.id + and dl.affiliation is null + where + a2.postal_code ILIKE $1 + limit 5 + ) + -- exclude OCONUS locations that are not active + except + ( + select + dl.id as duty_location_id, + dl.name as name, + 1 as sim + from + duty_locations dl, + addresses a, + re_oconus_rate_areas o, + re_rate_areas r + where + dl.address_id = a.id + and a.us_post_region_cities_id = o.us_post_region_cities_id + and o.rate_area_id = r.id + and o.active = false + and a.postal_code ILIKE $1 + and dl.affiliation is null + limit 5 + ) ) - select dl.* - from names n + ) + select + dl.* + from + names n inner join duty_locations dl on n.duty_location_id = dl.id`) // apply filter to exclude specific states if provided diff --git a/pkg/models/duty_location_test.go b/pkg/models/duty_location_test.go index 24f96af44ad..a368efec9ac 100644 --- a/pkg/models/duty_location_test.go +++ b/pkg/models/duty_location_test.go @@ -1,8 +1,12 @@ package models_test import ( + "fmt" + "slices" + "github.com/gofrs/uuid" + "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services/address" @@ -210,3 +214,365 @@ func (suite *ModelSuite) Test_FetchDutyLocationWithTransportationOffice() { suite.Nil(dutyLocationFromDB.TransportationOfficeID) }) } + +func (suite *ModelSuite) Test_SearchDutyLocations_Exclude_Not_Active_Oconus() { + createContract := func(appCtx appcontext.AppContext, contractCode string, contractName string) (*models.ReContract, error) { + // See if contract code already exists. + exists, err := appCtx.DB().Where("code = ?", contractCode).Exists(&models.ReContract{}) + if err != nil { + return nil, fmt.Errorf("could not determine if contract code [%s] existed: %w", contractCode, err) + } + if exists { + return nil, fmt.Errorf("the provided contract code [%s] already exists", contractCode) + } + + // Contract code is new; insert it. + contract := models.ReContract{ + Code: contractCode, + Name: contractName, + } + verrs, err := appCtx.DB().ValidateAndSave(&contract) + if verrs.HasAny() { + return nil, fmt.Errorf("validation errors when saving contract [%+v]: %w", contract, verrs) + } + if err != nil { + return nil, fmt.Errorf("could not save contract [%+v]: %w", contract, err) + } + return &contract, nil + } + + setupDataForOconusSearchCounselingOffice := func(contract models.ReContract, postalCode string, gbloc string, dutyLocationName string, transportationName string, isOconusRateAreaActive bool) (models.ReRateArea, models.OconusRateArea, models.UsPostRegionCity, models.DutyLocation) { + rateAreaCode := uuid.Must(uuid.NewV4()).String()[0:5] + rateArea := models.ReRateArea{ + ID: uuid.Must(uuid.NewV4()), + ContractID: contract.ID, + IsOconus: true, + Code: rateAreaCode, + Name: fmt.Sprintf("Alaska-%s", rateAreaCode), + Contract: contract, + } + verrs, err := suite.DB().ValidateAndCreate(&rateArea) + if verrs.HasAny() { + suite.Fail(verrs.Error()) + } + if err != nil { + suite.Fail(err.Error()) + } + + us_country, err := models.FetchCountryByCode(suite.DB(), "US") + suite.NotNil(us_country) + suite.Nil(err) + + usprc, err := models.FindByZipCode(suite.AppContextForTest().DB(), postalCode) + suite.NotNil(usprc) + suite.FatalNoError(err) + + oconusRateArea := models.OconusRateArea{ + ID: uuid.Must(uuid.NewV4()), + RateAreaId: rateArea.ID, + CountryId: us_country.ID, + UsPostRegionCityId: usprc.ID, + Active: isOconusRateAreaActive, + } + verrs, err = suite.DB().ValidateAndCreate(&oconusRateArea) + if verrs.HasAny() { + suite.Fail(verrs.Error()) + } + if err != nil { + suite.Fail(err.Error()) + } + + address := models.Address{ + StreetAddress1: "n/a", + City: "SomeCity", + State: "AK", + PostalCode: postalCode, + County: "SomeCounty", + IsOconus: models.BoolPointer(true), + UsPostRegionCityId: &usprc.ID, + CountryId: models.UUIDPointer(us_country.ID), + } + suite.MustSave(&address) + + origDutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + Name: dutyLocationName, + AddressID: address.ID, + ProvidesServicesCounseling: true, + }, + }, + { + Model: models.TransportationOffice{ + Name: transportationName, + Gbloc: gbloc, + ProvidesCloseout: true, + }, + }, + }, nil) + suite.MustSave(&origDutyLocation) + + found_duty_location, _ := models.FetchDutyLocation(suite.DB(), origDutyLocation.ID) + + return rateArea, oconusRateArea, *usprc, found_duty_location + } + + const fairbanksAlaskaPostalCode = "99790" + const anchorageAlaskaPostalCode = "99502" + testContractName := "Test_search_duty_location" + testContractCode := "Test_search_duty_location_Code" + testGbloc := "ABCD" + testTransportationName := "TEST - PPO" + testDutyLocationName := "TEST Duty Location" + testTransportationName2 := "TEST - PPO 2" + testDutyLocationName2 := "TEST Duty Location 2" + + suite.Run("one active onconus rateArea duty location and one not active oconus rate area duty location should return 1", func() { + contract, err := createContract(suite.AppContextForTest(), testContractCode, testContractName) + suite.NotNil(contract) + suite.FatalNoError(err) + + // active duty location + _, oconusRateArea, _, dutyLocation := setupDataForOconusSearchCounselingOffice(*contract, fairbanksAlaskaPostalCode, testGbloc, testDutyLocationName, testTransportationName, true) + + // not active duty location + _, oconusRateArea2, _, _ := setupDataForOconusSearchCounselingOffice(*contract, anchorageAlaskaPostalCode, testGbloc, testDutyLocationName2, testTransportationName2, false) + + suite.True(oconusRateArea.Active) + suite.False(oconusRateArea2.Active) + + tests := []struct { + query string + dutyLocations []string + }{ + {query: "search oconus rate area duty locations test", dutyLocations: []string{testDutyLocationName}}, + } + + expectedDutyLocationNames := []string{dutyLocation.Name} + + for _, ts := range tests { + dutyLocations, err := models.FindDutyLocationsExcludingStates(suite.DB(), ts.query, []string{}) + suite.NoError(err) + suite.Require().Equal(1, len(dutyLocations), "Wrong number of duty locations returned from query: %s", ts.query) + for _, o := range dutyLocations { + suite.True(slices.Contains(expectedDutyLocationNames, o.Name)) + } + } + }) + + suite.Run("two active onconus rateArea duty locations should return 2", func() { + contract, err := createContract(suite.AppContextForTest(), testContractCode, testContractName) + suite.NotNil(contract) + suite.FatalNoError(err) + + // active duty location + _, oconusRateArea, _, dutyLocation1 := setupDataForOconusSearchCounselingOffice(*contract, fairbanksAlaskaPostalCode, testGbloc, testDutyLocationName, testTransportationName, true) + + // active duty location + _, oconusRateArea2, _, dutyLocation2 := setupDataForOconusSearchCounselingOffice(*contract, anchorageAlaskaPostalCode, testGbloc, testDutyLocationName2, testTransportationName2, true) + + suite.True(oconusRateArea.Active) + suite.True(oconusRateArea2.Active) + + tests := []struct { + query string + dutyLocations []string + }{ + {query: "search oconus rate area duty locations test", dutyLocations: []string{testDutyLocationName}}, + } + + expectedDutyLocationNames := []string{dutyLocation1.Name, dutyLocation2.Name} + + for _, ts := range tests { + dutyLocations, err := models.FindDutyLocationsExcludingStates(suite.DB(), ts.query, []string{}) + suite.NoError(err) + suite.Require().Equal(2, len(dutyLocations), "Wrong number of duty locations returned from query: %s", ts.query) + for _, o := range dutyLocations { + suite.True(slices.Contains(expectedDutyLocationNames, o.Name)) + } + } + }) + + suite.Run("two inactive onconus rateArea duty locations should return 0", func() { + contract, err := createContract(suite.AppContextForTest(), testContractCode, testContractName) + suite.NotNil(contract) + suite.FatalNoError(err) + + // active duty location + _, oconusRateArea, _, dutyLocation1 := setupDataForOconusSearchCounselingOffice(*contract, fairbanksAlaskaPostalCode, testGbloc, testDutyLocationName, testTransportationName, false) + + // active duty location + _, oconusRateArea2, _, dutyLocation2 := setupDataForOconusSearchCounselingOffice(*contract, anchorageAlaskaPostalCode, testGbloc, testDutyLocationName2, testTransportationName2, false) + + suite.False(oconusRateArea.Active) + suite.False(oconusRateArea2.Active) + + tests := []struct { + query string + dutyLocations []string + }{ + {query: "search oconus rate area duty locations test", dutyLocations: []string{testDutyLocationName}}, + } + + expectedDutyLocationNames := []string{dutyLocation1.Name, dutyLocation2.Name} + + for _, ts := range tests { + dutyLocations, err := models.FindDutyLocationsExcludingStates(suite.DB(), ts.query, []string{}) + suite.NoError(err) + suite.Require().Equal(0, len(dutyLocations), "Wrong number of duty locations returned from query: %s", ts.query) + for _, o := range dutyLocations { + suite.True(slices.Contains(expectedDutyLocationNames, o.Name)) + } + } + }) + + suite.Run("match on alternative name but exclude", func() { + alternativeDutyLocationName := "Foobar" + + contract, err := createContract(suite.AppContextForTest(), testContractCode, testContractName) + suite.NotNil(contract) + suite.FatalNoError(err) + + // not active duty location + _, oconusRateArea, _, dutyLocation1 := setupDataForOconusSearchCounselingOffice(*contract, fairbanksAlaskaPostalCode, testGbloc, testDutyLocationName, testTransportationName, false) + + suite.False(oconusRateArea.Active) + + dutyLocationName := models.DutyLocationName{ + Name: alternativeDutyLocationName, + DutyLocationID: dutyLocation1.ID, + DutyLocation: dutyLocation1, + } + verrs, err := suite.DB().ValidateAndCreate(&dutyLocationName) + if verrs.HasAny() { + suite.Fail(verrs.Error()) + } + if err != nil { + suite.Fail(err.Error()) + } + + tests := []struct { + query string + dutyLocations []string + }{ + {query: "search oconus rate area duty locations", dutyLocations: []string{alternativeDutyLocationName}}, //search on alt name + } + + for _, ts := range tests { + dutyLocations, err := models.FindDutyLocationsExcludingStates(suite.DB(), ts.query, []string{}) + suite.NoError(err) + suite.Require().Equal(0, len(dutyLocations), "Wrong number of duty locations returned from query: %s", ts.query) + } + }) + + suite.Run("match on alternative name when active oconus rateArea", func() { + alternativeDutyLocationName := "Foobar" + + contract, err := createContract(suite.AppContextForTest(), testContractCode, testContractName) + suite.NotNil(contract) + suite.FatalNoError(err) + + // active duty location + _, oconusRateArea, _, dutyLocation1 := setupDataForOconusSearchCounselingOffice(*contract, fairbanksAlaskaPostalCode, testGbloc, testDutyLocationName, testTransportationName, true) + + suite.True(oconusRateArea.Active) + + dutyLocationName := models.DutyLocationName{ + Name: alternativeDutyLocationName, + DutyLocationID: dutyLocation1.ID, + DutyLocation: dutyLocation1, + } + verrs, err := suite.DB().ValidateAndCreate(&dutyLocationName) + if verrs.HasAny() { + suite.Fail(verrs.Error()) + } + if err != nil { + suite.Fail(err.Error()) + } + + tests := []struct { + query string + dutyLocations []string + }{ + {query: "search oconus rate area duty locations", dutyLocations: []string{alternativeDutyLocationName}}, //search on alt name + } + + expectedDutyLocationNames := []string{dutyLocation1.Name} + + for _, ts := range tests { + dutyLocations, err := models.FindDutyLocationsExcludingStates(suite.DB(), ts.query, []string{}) + suite.NoError(err) + suite.Require().Equal(1, len(dutyLocations), "Wrong number of duty locations returned from query: %s", ts.query) + for _, o := range dutyLocations { + suite.True(slices.Contains(expectedDutyLocationNames, o.Name)) + } + } + }) + + suite.Run("one active and one inactive onconus rateArea duty locations - search by zip - return match", func() { + contract, err := createContract(suite.AppContextForTest(), testContractCode, testContractName) + suite.NotNil(contract) + suite.FatalNoError(err) + + // active duty location + _, oconusRateArea, _, dutyLocation1 := setupDataForOconusSearchCounselingOffice(*contract, fairbanksAlaskaPostalCode, testGbloc, testDutyLocationName, testTransportationName, true) + + // not active duty location + _, oconusRateArea2, _, _ := setupDataForOconusSearchCounselingOffice(*contract, anchorageAlaskaPostalCode, testGbloc, testDutyLocationName2, testTransportationName2, false) + + suite.True(oconusRateArea.Active) + suite.False(oconusRateArea2.Active) + + tests := []struct { + query string + dutyLocations []string + }{ + {query: "search oconus rate area duty locations test", dutyLocations: []string{ + fairbanksAlaskaPostalCode, anchorageAlaskaPostalCode}}, //search by zip + } + + expectedDutyLocationNames := []string{dutyLocation1.Name} + + for i, ts := range tests { + dutyLocations, err := models.FindDutyLocationsExcludingStates(suite.DB(), ts.query, []string{}) + suite.NoError(err) + if i == 0 { + suite.Require().Equal(1, len(dutyLocations), "Wrong number of duty locations returned from query: %s", ts.query) + for _, o := range dutyLocations { + suite.True(slices.Contains(expectedDutyLocationNames, o.Name)) + } + } else { + suite.Require().Equal(0, len(dutyLocations), "Wrong number of duty locations returned from query: %s", ts.query) + } + } + }) + + suite.Run("two non active onconus rateArea duty locations - search by zip - return none", func() { + contract, err := createContract(suite.AppContextForTest(), testContractCode, testContractName) + suite.NotNil(contract) + suite.FatalNoError(err) + + // not active duty location + _, oconusRateArea, _, _ := setupDataForOconusSearchCounselingOffice(*contract, fairbanksAlaskaPostalCode, testGbloc, testDutyLocationName, testTransportationName, false) + + // not active duty location + _, oconusRateArea2, _, _ := setupDataForOconusSearchCounselingOffice(*contract, anchorageAlaskaPostalCode, testGbloc, testDutyLocationName2, testTransportationName2, false) + + suite.False(oconusRateArea.Active) + suite.False(oconusRateArea2.Active) + + tests := []struct { + query string + dutyLocations []string + }{ + {query: "search oconus rate area duty locations test", dutyLocations: []string{ + fairbanksAlaskaPostalCode, anchorageAlaskaPostalCode}}, //search by zip + } + + for _, ts := range tests { + dutyLocations, err := models.FindDutyLocationsExcludingStates(suite.DB(), ts.query, []string{}) + suite.NoError(err) + suite.Require().Equal(0, len(dutyLocations), "Wrong number of duty locations returned from query: %s", ts.query) + } + }) +} diff --git a/pkg/models/entitlements.go b/pkg/models/entitlements.go index a2aaebf4424..66c80a9fafd 100644 --- a/pkg/models/entitlements.go +++ b/pkg/models/entitlements.go @@ -3,6 +3,9 @@ package models import ( "fmt" + "github.com/pkg/errors" + + "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/gen/internalmessages" ) @@ -12,6 +15,7 @@ type WeightAllotment struct { TotalWeightSelfPlusDependents int ProGearWeight int ProGearWeightSpouse int + UnaccompaniedBaggageAllowance int } // the midshipman entitlement is shared with service academy cadet @@ -177,6 +181,14 @@ var civilianEmployee = WeightAllotment{ 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, @@ -209,6 +221,10 @@ var entitlements = map[internalmessages.OrderPayGrade]WeightAllotment{ 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 @@ -216,16 +232,131 @@ func getEntitlement(grade internalmessages.OrderPayGrade) (WeightAllotment, erro 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. -func GetWeightAllotment(grade internalmessages.OrderPayGrade) WeightAllotment { - entitlement, err := getEntitlement(grade) +// 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 + ubAllowance := 0 + + 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 index ac4ee972114..22d82ca5ca7 100644 --- a/pkg/models/entitlements_test.go +++ b/pkg/models/entitlements_test.go @@ -1,29 +1,270 @@ 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) + E1FullLoad := models.GetWeightAllotment(E1, ordersType) suite.Assertions.Equal(8000, E1FullLoad.TotalWeightSelfPlusDependents) }) suite.Run("E1 without dependents", func() { - E1Solo := models.GetWeightAllotment(E1) + E1Solo := models.GetWeightAllotment(E1, ordersType) suite.Assertions.Equal(5000, E1Solo.TotalWeightSelf) }) suite.Run("E1 Pro Gear", func() { - E1ProGear := models.GetWeightAllotment(E1) + E1ProGear := models.GetWeightAllotment(E1, ordersType) suite.Assertions.Equal(2000, E1ProGear.ProGearWeight) }) suite.Run("E1 Pro Gear Spouse", func() { - E1ProGearSpouse := models.GetWeightAllotment(E1) + 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) + }) +} + +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/errors.go b/pkg/models/errors.go index 130432a4008..2a1cdeeff0e 100644 --- a/pkg/models/errors.go +++ b/pkg/models/errors.go @@ -51,3 +51,15 @@ const UniqueConstraintViolationOfficeUserOtherUniqueIDErrorString = "pq: duplica // ErrInvalidMoveID is used if a argument is provided in cases where a move ID is provided, but may be malformed, empty, or nonexistent var ErrInvalidMoveID = errors.New("INVALID_MOVE_ID") + +// ErrInvalidOrderID is used if a argument is provided in cases where a order ID is provided, but may be malformed, empty, or nonexistent +var ErrInvalidOrderID = errors.New("INVALID_ORDER_ID") + +// ErrSqlRecordNotFound is used if an error is returned from the database indicating that no record was found +var ErrSqlRecordNotFound = errors.New("RECORD_NOT_FOUND") + +// ErrMissingDestinationAddress is used if destination address is missing from the shipment +var ErrMissingDestinationAddress = errors.New("DESTINATION_ADDRESS_MISSING") + +// ErrUnsupportedShipmentType is used if the shipment type is not supported by a method +var ErrUnsupportedShipmentType = errors.New("UNSUPPORTED_SHIPMENT_TYPE") diff --git a/pkg/models/gbloc_aors.go b/pkg/models/gbloc_aors.go new file mode 100644 index 00000000000..e4fa1612355 --- /dev/null +++ b/pkg/models/gbloc_aors.go @@ -0,0 +1,21 @@ +package models + +import ( + "time" + + "github.com/gofrs/uuid" +) + +type GblocAors struct { + ID uuid.UUID `json:"id" db:"id"` + JppsoRegionID uuid.UUID `json:"jppso_regions_id" db:"jppso_regions_id"` + OconusRateAreaID uuid.UUID `json:"oconus_rate_area_id" db:"oconus_rate_area_id"` + DepartmentIndicator *string `json:"department_indicator" db:"department_indicator"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + UpdatedAt time.Time `json:"updated_at" db:"updated_at"` +} + +// TableName overrides the table name used by Pop. +func (c GblocAors) TableName() string { + return "gbloc_aors" +} diff --git a/pkg/models/ghc_entitlements.go b/pkg/models/ghc_entitlements.go index 86e1f9d3c18..383caada3a7 100644 --- a/pkg/models/ghc_entitlements.go +++ b/pkg/models/ghc_entitlements.go @@ -15,13 +15,17 @@ import ( type Entitlement struct { ID uuid.UUID `db:"id"` DependentsAuthorized *bool `db:"dependents_authorized"` - TotalDependents *int `db:"total_dependents"` + TotalDependents *int `db:"total_dependents" rw:"r"` // DB generated column NonTemporaryStorage *bool `db:"non_temporary_storage"` PrivatelyOwnedVehicle *bool `db:"privately_owned_vehicle"` //DBAuthorizedWeight is AuthorizedWeight when not null DBAuthorizedWeight *int `db:"authorized_weight"` WeightAllotted *WeightAllotment `db:"-"` StorageInTransit *int `db:"storage_in_transit"` + AccompaniedTour *bool `db:"accompanied_tour"` + DependentsUnderTwelve *int `db:"dependents_under_twelve"` + DependentsTwelveAndOver *int `db:"dependents_twelve_and_over"` + UBAllowance *int `db:"ub_allowance"` GunSafe bool `db:"gun_safe"` RequiredMedicalEquipmentWeight int `db:"required_medical_equipment_weight"` OrganizationalClothingAndIndividualEquipment bool `db:"organizational_clothing_and_individual_equipment"` @@ -38,20 +42,36 @@ func (e Entitlement) TableName() string { // Validate gets run every time you call a "pop.Validate*" (pop.ValidateAndSave, pop.ValidateAndCreate, pop.ValidateAndUpdate) method. func (e *Entitlement) Validate(*pop.Connection) (*validate.Errors, error) { - return validate.Validate( + var vs []validate.Validator + + vs = append(vs, &validators.IntIsGreaterThan{Field: e.ProGearWeight, Compared: -1, Name: "ProGearWeight"}, &validators.IntIsLessThan{Field: e.ProGearWeight, Compared: 2001, Name: "ProGearWeight"}, &validators.IntIsGreaterThan{Field: e.ProGearWeightSpouse, Compared: -1, Name: "ProGearWeightSpouse"}, &validators.IntIsLessThan{Field: e.ProGearWeightSpouse, Compared: 501, Name: "ProGearWeightSpouse"}, - ), nil + ) + + if e.DependentsUnderTwelve != nil { + vs = append(vs, &validators.IntIsGreaterThan{Field: *e.DependentsUnderTwelve, Compared: -1, Name: "DependentsUnderTwelve"}) + } + + if e.DependentsTwelveAndOver != nil { + vs = append(vs, &validators.IntIsGreaterThan{Field: *e.DependentsTwelveAndOver, Compared: -1, Name: "DependentsTwelveAndOver"}) + } + + if e.UBAllowance != nil { + vs = append(vs, &validators.IntIsGreaterThan{Field: *e.UBAllowance, Compared: -1, Name: "UBAllowance"}) + } + + 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) { - wa := GetWeightAllotment(internalmessages.OrderPayGrade(grade)) +func (e *Entitlement) SetWeightAllotment(grade string, ordersType internalmessages.OrdersType) { + wa := GetWeightAllotment(internalmessages.OrderPayGrade(grade), ordersType) e.WeightAllotted = &wa } @@ -60,6 +80,16 @@ func (e *Entitlement) WeightAllotment() *WeightAllotment { return e.WeightAllotted } +// UBWeightAllotment returns the UB weight allotment +func (e *Entitlement) UBWeightAllotment() *int { + if e.WeightAllotment() != nil { + if e.WeightAllotment().UnaccompaniedBaggageAllowance >= 0 { + return &e.WeightAllotment().UnaccompaniedBaggageAllowance + } + } + return nil +} + // AuthorizedWeight returns authorized weight. If authorized weight has not been // stored in DBAuthorizedWeight use either TotalWeightSelf with no dependents or TotalWeightSelfPlusDependents // with dependents. @@ -89,3 +119,13 @@ func (e *Entitlement) WeightAllowance() *int { return nil } + +// UBWeightAllowance returns authorized weight for UB shipments +func (e *Entitlement) UBWeightAllowance() *int { + switch { + case e.UBWeightAllotment() != nil: + return e.UBAllowance + default: + return nil + } +} diff --git a/pkg/models/ghc_entitlements_test.go b/pkg/models/ghc_entitlements_test.go index e6092b9c66c..d5e56ca96b7 100644 --- a/pkg/models/ghc_entitlements_test.go +++ b/pkg/models/ghc_entitlements_test.go @@ -1,6 +1,7 @@ package models_test import ( + "github.com/transcom/mymove/pkg/gen/internalmessages" "github.com/transcom/mymove/pkg/models" ) @@ -16,7 +17,7 @@ func (suite *ModelSuite) TestAuthorizedWeightWhenExistsInDB() { func (suite *ModelSuite) TestAuthorizedWeightWhenNotInDBAndHaveWeightAllotment() { suite.Run("with no dependents authorized, TotalWeightSelf is AuthorizedWeight", func() { entitlement := models.Entitlement{} - entitlement.SetWeightAllotment("E_1") + entitlement.SetWeightAllotment("E_1", internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) suite.Equal(entitlement.WeightAllotment().TotalWeightSelf, *entitlement.AuthorizedWeight()) }) @@ -24,7 +25,7 @@ func (suite *ModelSuite) TestAuthorizedWeightWhenNotInDBAndHaveWeightAllotment() suite.Run("with dependents authorized, TotalWeightSelfPlusDependents is AuthorizedWeight", func() { dependentsAuthorized := true entitlement := models.Entitlement{DependentsAuthorized: &dependentsAuthorized} - entitlement.SetWeightAllotment("E_1") + entitlement.SetWeightAllotment("E_1", internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) suite.Equal(entitlement.WeightAllotment().TotalWeightSelfPlusDependents, *entitlement.AuthorizedWeight()) }) @@ -62,3 +63,92 @@ func (suite *ModelSuite) TestProGearAndProGearSpouseWeight() { suite.NotNil(verrs.Get("pro_gear_weight_spouse")) }) } + +func (suite *ModelSuite) TestOconusFields() { + suite.Run("no validation errors for valid DependentsUnderTwelve, DependentsTwelveAndOver, and UBAllowance", func() { + entitlement := models.Entitlement{ + ProGearWeight: 2000, + ProGearWeightSpouse: 500, + DependentsUnderTwelve: models.IntPointer(1), + DependentsTwelveAndOver: models.IntPointer(2), + UBAllowance: models.IntPointer(100), + } + verrs, _ := entitlement.Validate(suite.DB()) + suite.False(verrs.HasAny()) + }) + + suite.Run("validation errors for DependentsUnderTwelve and DependentsTwelveAndOver less than 0", func() { + entitlement := models.Entitlement{ + DependentsTwelveAndOver: models.IntPointer(-1), + DependentsUnderTwelve: models.IntPointer(-1), + } + verrs, _ := entitlement.Validate(suite.DB()) + suite.True(verrs.HasAny()) + suite.NotNil(verrs.Get("dependents_under_twelve")) + suite.NotNil(verrs.Get("dependents_twelve_and_over")) + }) +} + +func (suite *ModelSuite) TestTotalDependentsCalculation() { + suite.Run("calculates total dependents correctly when both fields are set", func() { + entitlement := models.Entitlement{ + DependentsUnderTwelve: models.IntPointer(2), + DependentsTwelveAndOver: models.IntPointer(3), + } + verrs, err := suite.DB().ValidateAndCreate(&entitlement) + suite.NoError(err) + suite.False(verrs.HasAny()) + var fetchedEntitlement models.Entitlement + err = suite.DB().Find(&fetchedEntitlement, entitlement.ID) + suite.NoError(err) + suite.Equal(2, *fetchedEntitlement.DependentsUnderTwelve) + suite.Equal(3, *fetchedEntitlement.DependentsTwelveAndOver) + suite.NotNil(fetchedEntitlement.TotalDependents) + suite.Equal(5, *fetchedEntitlement.TotalDependents) // sum of 2 + 3 + }) + suite.Run("calculates total dependents correctly when DependentsUnderTwelve is nil", func() { + entitlement := models.Entitlement{ + DependentsTwelveAndOver: models.IntPointer(3), + } + verrs, err := suite.DB().ValidateAndCreate(&entitlement) + suite.NoError(err) + suite.False(verrs.HasAny()) + var fetchedEntitlement models.Entitlement + err = suite.DB().Find(&fetchedEntitlement, entitlement.ID) + suite.NoError(err) + suite.Nil(fetchedEntitlement.DependentsUnderTwelve) + suite.Equal(3, *fetchedEntitlement.DependentsTwelveAndOver) + suite.NotNil(fetchedEntitlement.TotalDependents) + suite.Equal(3, *fetchedEntitlement.TotalDependents) // sum of 0 + 3 + }) + suite.Run("calculates total dependents correctly when DependentsTwelveAndOver is nil", func() { + entitlement := models.Entitlement{ + DependentsUnderTwelve: models.IntPointer(2), + } + verrs, err := suite.DB().ValidateAndCreate(&entitlement) + suite.NoError(err) + suite.False(verrs.HasAny()) + var fetchedEntitlement models.Entitlement + err = suite.DB().Find(&fetchedEntitlement, entitlement.ID) + suite.NoError(err) + suite.Equal(2, *fetchedEntitlement.DependentsUnderTwelve) + suite.Nil(fetchedEntitlement.DependentsTwelveAndOver) + suite.NotNil(fetchedEntitlement.TotalDependents) + suite.Equal(2, *fetchedEntitlement.TotalDependents) // sum of 2 + 0 + }) + suite.Run("sets total dependents to nil when both fields are nil", func() { + entitlement := models.Entitlement{ + DependentsUnderTwelve: nil, + DependentsTwelveAndOver: nil, + } + verrs, err := suite.DB().ValidateAndCreate(&entitlement) + suite.NoError(err) + suite.False(verrs.HasAny()) + var fetchedEntitlement models.Entitlement + err = suite.DB().Find(&fetchedEntitlement, entitlement.ID) + suite.NoError(err) + suite.Nil(fetchedEntitlement.DependentsUnderTwelve) + suite.Nil(fetchedEntitlement.DependentsTwelveAndOver) + suite.Nil(fetchedEntitlement.TotalDependents) // NOT 0, NOT A SUM, nil + nil is NULL + }) +} diff --git a/pkg/models/jppso_regions.go b/pkg/models/jppso_regions.go new file mode 100644 index 00000000000..0788c78abb2 --- /dev/null +++ b/pkg/models/jppso_regions.go @@ -0,0 +1,20 @@ +package models + +import ( + "time" + + "github.com/gofrs/uuid" +) + +type JppsoRegions struct { + ID uuid.UUID `json:"id" db:"id"` + Code string `json:"code" db:"code"` + Name string `json:"name" db:"name"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + UpdatedAt time.Time `json:"updated_at" db:"updated_at"` +} + +// TableName overrides the table name used by Pop. +func (c JppsoRegions) TableName() string { + return "jppso_regions" +} diff --git a/pkg/models/move.go b/pkg/models/move.go index 23c1dd7c479..8201a7f0dc6 100644 --- a/pkg/models/move.go +++ b/pkg/models/move.go @@ -196,6 +196,50 @@ func FetchMove(db *pop.Connection, session *auth.Session, id uuid.UUID) (*Move, return &move, nil } +// GetDestinationPostalCode returns the postal code for the move. This ensures that business logic is centralized. +func (m Move) GetDestinationPostalCode(db *pop.Connection) (string, error) { + // Since this requires looking up the move in the DB, the move must have an ID. This means, the move has to have been created first. + if uuid.UUID.IsNil(m.ID) { + return "", errors.WithMessage(ErrInvalidOrderID, "You must created the move in the DB before getting the destination Postal Code.") + } + + err := db.Load(&m, "Orders") + if err != nil { + if err.Error() == RecordNotFoundErrorString { + return "", errors.WithMessage(err, "No Orders found in the DB associated with moveID "+m.ID.String()) + } + return "", err + } + + var gblocsMap map[uuid.UUID]string + gblocsMap, err = m.Orders.GetDestinationPostalCodeForAssociatedMoves(db) + if err != nil { + return "", err + } + return gblocsMap[m.ID], nil +} + +// GetDestinationGBLOC returns the GBLOC for the move. This ensures that business logic is centralized. +func (m Move) GetDestinationGBLOC(db *pop.Connection) (string, error) { + // Since this requires looking up the move in the DB, the move must have an ID. This means, the move has to have been created first. + if uuid.UUID.IsNil(m.ID) { + return "", errors.WithMessage(ErrInvalidOrderID, "You must created the move in the DB before getting the destination GBLOC.") + } + + postalCode, err := m.GetDestinationPostalCode(db) + if err != nil { + return "", err + } + + var gblocResult PostalCodeToGBLOC + gblocResult, err = FetchGBLOCForPostalCode(db, postalCode) + if err != nil { + return "", err + } + + return gblocResult.GBLOC, err +} + // CreateSignedCertification creates a new SignedCertification associated with this move func (m Move) CreateSignedCertification(db *pop.Connection, submittingUserID uuid.UUID, diff --git a/pkg/models/mto_service_items.go b/pkg/models/mto_service_items.go index b20600cd0a4..822e568f227 100644 --- a/pkg/models/mto_service_items.go +++ b/pkg/models/mto_service_items.go @@ -68,6 +68,10 @@ type MTOServiceItem struct { StandaloneCrate *bool `db:"standalone_crate"` ExternalCrate *bool `db:"external_crate"` LockedPriceCents *unit.Cents `db:"locked_price_cents"` + POELocation *PortLocation `belongs_to:"port_locations" fk_id:"poe_location_id"` + POELocationID *uuid.UUID `db:"poe_location_id"` + PODLocation *PortLocation `belongs_to:"port_locations" fk_id:"pod_location_id"` + PODLocationID *uuid.UUID `db:"pod_location_id"` ServiceLocation *ServiceLocationType `db:"service_location"` } @@ -101,6 +105,8 @@ type MTOServiceItemSingle struct { CustomerExpenseReason *string `db:"customer_expense_reason"` SITDeliveryMiles *unit.Miles `db:"sit_delivery_miles"` PricingEstimate *unit.Cents `db:"pricing_estimate"` + POELocationID *uuid.UUID `db:"poe_location_id"` + PODLocationID *uuid.UUID `db:"pod_location_id"` } // TableName overrides the table name used by Pop. @@ -170,3 +176,75 @@ func FetchRelatedDestinationSITFuelCharge(tx *pop.Connection, mtoServiceItemID u SELECT mto_shipment_id FROM mto_service_items WHERE id = ?)`, ReServiceCodeDDSFSC, mtoServiceItemID).First(&serviceItem) return serviceItem, err } + +type MTOServiceItemType struct { + ID *uuid.UUID `json:"id"` + MoveID *uuid.UUID `json:"move_Id"` + ReServiceID *uuid.UUID `json:"re_service_id"` + CreatedAt *time.Time `json:"created_at"` + UpdatedAt *time.Time `json:"updated_at"` + Reason *string `json:"reason"` + PickupPostalCode *string `json:"pickup_postal_code"` + Description *string `json:"description"` + Status *string `json:"status"` + RejectionReason *string `json:"rejected_reason"` + ApprovedAt *time.Time `json:"approved_at"` + SITPostalCode *string `json:"sit_postal_code"` + SITEntryDate *time.Time `json:"sit_entry_date"` + SITDepartureDate *time.Time `json:"sit_departure_date"` + SITDestinationFinalAddressID *uuid.UUID `json:"sit_destination_final_address_id"` + SITOriginHHGOriginalAddressID *uuid.UUID `json:"sit_origin_hhg_original_address_id"` + SITOriginHHGActualAddressID *uuid.UUID `json:"sit_origin_hhg_actual_address_id"` + EstimatedWeight *unit.Pound `json:"estimated_weight"` + ActualWeight *unit.Pound `json:"actual_weight"` + SITDestinationOriginalAddressID *uuid.UUID `json:"sit_destination_original_address_id"` + SITCustomerContacted *time.Time `json:"sit_customer_contacted"` + SITRequestedDelivery *time.Time `json:"sit_requested_delivery"` + RequestedApprovalsRequestedStatus *bool `json:"requested_approvals_requested_status"` + CustomerExpense *bool `json:"customer_expense"` + CustomerExpenseReason *string `json:"customer_expense_reason"` + SITDeliveryMiles *int `json:"sit_delivery_miles"` + PricingEstimate *unit.Cents `json:"pricing_estimate"` + StandaloneCrate *bool `json:"standalone_crate"` + LockedPriceCents *unit.Cents `json:"locked_price_cents"` + ServiceLocation *ServiceLocationType `json:"service_location"` + POELocationID *uuid.UUID `json:"poe_location_id"` + PODLocationID *uuid.UUID `json:"pod_location_id"` +} + +func (m MTOServiceItem) GetMTOServiceItemTypeFromServiceItem() MTOServiceItemType { + return MTOServiceItemType{ + ID: &m.ID, + MoveID: &m.MoveTaskOrderID, + ReServiceID: &m.ReServiceID, + CreatedAt: &m.CreatedAt, + UpdatedAt: &m.UpdatedAt, + Reason: m.Reason, + PickupPostalCode: m.PickupPostalCode, + Description: m.Description, + Status: (*string)(&m.Status), + RejectionReason: m.RejectionReason, + ApprovedAt: m.ApprovedAt, + SITPostalCode: m.SITPostalCode, + SITEntryDate: m.SITEntryDate, + SITDepartureDate: m.SITDepartureDate, + SITDestinationFinalAddressID: m.SITDestinationFinalAddressID, + SITOriginHHGOriginalAddressID: m.SITOriginHHGOriginalAddressID, + SITOriginHHGActualAddressID: m.SITOriginHHGActualAddressID, + EstimatedWeight: m.EstimatedWeight, + ActualWeight: m.ActualWeight, + SITDestinationOriginalAddressID: m.SITDestinationOriginalAddressID, + SITCustomerContacted: m.SITCustomerContacted, + SITRequestedDelivery: m.SITRequestedDelivery, + RequestedApprovalsRequestedStatus: m.RequestedApprovalsRequestedStatus, + CustomerExpense: &m.CustomerExpense, + CustomerExpenseReason: m.CustomerExpenseReason, + SITDeliveryMiles: m.SITDeliveryMiles, + PricingEstimate: m.PricingEstimate, + StandaloneCrate: m.StandaloneCrate, + LockedPriceCents: m.LockedPriceCents, + ServiceLocation: m.ServiceLocation, + POELocationID: m.POELocationID, + PODLocationID: m.PODLocationID, + } +} diff --git a/pkg/models/mto_service_items_test.go b/pkg/models/mto_service_items_test.go index c45ff4216b9..720ef5c9226 100644 --- a/pkg/models/mto_service_items_test.go +++ b/pkg/models/mto_service_items_test.go @@ -12,12 +12,16 @@ func (suite *ModelSuite) TestMTOServiceItemValidation() { moveTaskOrderID := uuid.Must(uuid.NewV4()) mtoShipmentID := uuid.Must(uuid.NewV4()) reServiceID := uuid.Must(uuid.NewV4()) + poeLocationID := uuid.Must(uuid.NewV4()) + podLocationID := uuid.Must(uuid.NewV4()) validMTOServiceItem := models.MTOServiceItem{ MoveTaskOrderID: moveTaskOrderID, MTOShipmentID: &mtoShipmentID, ReServiceID: reServiceID, Status: models.MTOServiceItemStatusSubmitted, + POELocationID: &poeLocationID, + PODLocationID: &podLocationID, } expErrors := map[string][]string{} suite.verifyValidationErrors(&validMTOServiceItem, expErrors) diff --git a/pkg/models/mto_shipments.go b/pkg/models/mto_shipments.go index dcd99dcdc48..0d0885ded6a 100644 --- a/pkg/models/mto_shipments.go +++ b/pkg/models/mto_shipments.go @@ -8,6 +8,7 @@ import ( "github.com/gobuffalo/validate/v3" "github.com/gobuffalo/validate/v3/validators" "github.com/gofrs/uuid" + "github.com/pkg/errors" "github.com/transcom/mymove/pkg/unit" ) @@ -275,6 +276,38 @@ func GetCustomerFromShipment(db *pop.Connection, shipmentID uuid.UUID) (*Service return &serviceMember, nil } +func (m *MTOShipment) UpdateOrdersDestinationGBLOC(db *pop.Connection) error { + // Since this requires looking up the order in the DB, the order must have an ID. This means, the order has to have been created first. + if uuid.UUID.IsNil(m.ID) { + return fmt.Errorf("error updating orders destination GBLOC for shipment due to no shipment ID provided") + } + + var err error + var order Order + + err = db.Load(&m, "MoveTaskOrder.OrdersID") + if err != nil { + return fmt.Errorf("error loading orders for shipment ID: %s with error %w", m.ID, err) + } + + order, err = FetchOrder(db, m.MoveTaskOrder.OrdersID) + if err != nil { + return fmt.Errorf("error fetching order for shipment ID: %s with error %w", m.ID, err) + } + + err = order.UpdateDestinationGBLOC(db) + if err != nil { + return fmt.Errorf("error fetching GBLOC for postal code with error %w", err) + } + + return nil +} + +// Helper function to check that an MTO Shipment contains a PPM Shipment +func (m MTOShipment) ContainsAPPMShipment() bool { + return m.PPMShipment != nil +} + // determining the market code for a shipment based off of address isOconus value // this function takes in a shipment and returns the same shipment with the updated MarketCode value func DetermineShipmentMarketCode(shipment *MTOShipment) *MTOShipment { @@ -336,6 +369,35 @@ func DetermineShipmentMarketCode(shipment *MTOShipment) *MTOShipment { return shipment } +func (s MTOShipment) GetDestinationAddress(db *pop.Connection) (*Address, error) { + if uuid.UUID.IsNil(s.ID) { + return nil, errors.New("MTOShipment ID is required to fetch destination address.") + } + + err := db.Load(&s, "DestinationAddress", "PPMShipment.DestinationAddress") + if err != nil { + if err.Error() == RecordNotFoundErrorString { + return nil, errors.WithMessage(ErrSqlRecordNotFound, string(s.ShipmentType)+" ShipmentID: "+s.ID.String()) + } + return nil, err + } + + if s.ShipmentType == MTOShipmentTypePPM { + if s.PPMShipment.DestinationAddress != nil { + return s.PPMShipment.DestinationAddress, nil + } else if s.DestinationAddress != nil { + return s.DestinationAddress, nil + } + return nil, errors.WithMessage(ErrMissingDestinationAddress, string(s.ShipmentType)) + } + + if s.DestinationAddress != nil { + return s.DestinationAddress, nil + } + + return nil, errors.WithMessage(ErrMissingDestinationAddress, string(s.ShipmentType)) +} + // this function takes in two addresses and determines the market code string func DetermineMarketCode(address1 *Address, address2 *Address) (MarketCode, error) { if address1 == nil || address2 == nil { @@ -355,3 +417,12 @@ func DetermineMarketCode(address1 *Address, address2 *Address) (MarketCode, erro return MarketCodeInternational, nil } } + +func CreateApprovedServiceItemsForShipment(db *pop.Connection, shipment *MTOShipment) error { + err := db.RawQuery("CALL create_approved_service_items_for_shipment($1)", shipment.ID).Exec() + if err != nil { + return fmt.Errorf("error creating approved service items: %w", err) + } + + return nil +} diff --git a/pkg/models/mto_shipments_test.go b/pkg/models/mto_shipments_test.go index 9050d042d44..5f2a5f78864 100644 --- a/pkg/models/mto_shipments_test.go +++ b/pkg/models/mto_shipments_test.go @@ -3,6 +3,7 @@ package models_test import ( "github.com/gofrs/uuid" + "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/unit" ) @@ -91,6 +92,15 @@ func (suite *ModelSuite) TestMTOShipmentValidation() { } suite.verifyValidationErrors(&invalidMTOShipment, expErrors) }) + suite.Run("test MTO Shipment has a PPM Shipment", func() { + ppmShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) + mtoShipment := factory.BuildMTOShipmentMinimal(suite.DB(), nil, nil) + + mtoShipment.PPMShipment = &ppmShipment + result := mtoShipment.ContainsAPPMShipment() + + suite.True(result, "Expected mtoShipment to cotain a PPM Shipment") + }) } func (suite *ModelSuite) TestDetermineShipmentMarketCode() { @@ -272,3 +282,45 @@ func (suite *ModelSuite) TestDetermineMarketCode() { suite.EqualError(err, "both address1 and address2 must be provided") }) } +func (suite *ModelSuite) TestCreateApprovedServiceItemsForShipment() { + suite.Run("test creating approved service items for shipment", func() { + + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + + Model: models.Address{ + StreetAddress1: "some address", + City: "city", + State: "CA", + PostalCode: "90210", + IsOconus: models.BoolPointer(false), + }, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: models.MTOShipment{ + MarketCode: "i", + }, + }, + { + Model: models.Address{ + StreetAddress1: "some address", + City: "city", + State: "AK", + PostalCode: "98765", + IsOconus: models.BoolPointer(true), + }, + Type: &factory.Addresses.DeliveryAddress, + }, + }, nil) + err := models.CreateApprovedServiceItemsForShipment(suite.DB(), &shipment) + suite.NoError(err) + }) + + suite.Run("test error handling for invalid shipment", func() { + invalidShipment := models.MTOShipment{} + + err := models.CreateApprovedServiceItemsForShipment(suite.DB(), &invalidShipment) + suite.Error(err) + }) +} diff --git a/pkg/models/office_user.go b/pkg/models/office_user.go index 1857e10ed84..bfbe6529123 100644 --- a/pkg/models/office_user.go +++ b/pkg/models/office_user.go @@ -24,23 +24,24 @@ const ( // OfficeUser is someone who works in one of the TransportationOffices type OfficeUser struct { - ID uuid.UUID `json:"id" db:"id"` - UserID *uuid.UUID `json:"user_id" db:"user_id"` - User User `belongs_to:"user" fk_id:"user_id"` - LastName string `json:"last_name" db:"last_name"` - FirstName string `json:"first_name" db:"first_name"` - MiddleInitials *string `json:"middle_initials" db:"middle_initials"` - Email string `json:"email" db:"email"` - Telephone string `json:"telephone" db:"telephone"` - TransportationOfficeID uuid.UUID `json:"transportation_office_id" db:"transportation_office_id"` - TransportationOffice TransportationOffice `belongs_to:"transportation_office" fk_id:"transportation_office_id"` - CreatedAt time.Time `json:"created_at" db:"created_at"` - UpdatedAt time.Time `json:"updated_at" db:"updated_at"` - Active bool `json:"active" db:"active"` - Status *OfficeUserStatus `json:"status" db:"status"` - EDIPI *string `json:"edipi" db:"edipi"` - OtherUniqueID *string `json:"other_unique_id" db:"other_unique_id"` - RejectionReason *string `json:"rejection_reason" db:"rejection_reason"` + ID uuid.UUID `json:"id" db:"id"` + UserID *uuid.UUID `json:"user_id" db:"user_id"` + User User `belongs_to:"user" fk_id:"user_id"` + LastName string `json:"last_name" db:"last_name"` + FirstName string `json:"first_name" db:"first_name"` + MiddleInitials *string `json:"middle_initials" db:"middle_initials"` + Email string `json:"email" db:"email"` + Telephone string `json:"telephone" db:"telephone"` + TransportationOfficeID uuid.UUID `json:"transportation_office_id" db:"transportation_office_id"` + TransportationOffice TransportationOffice `belongs_to:"transportation_office" fk_id:"transportation_office_id"` + TransportationOfficeAssignments TransportationOfficeAssignments `has_many:"transportation_office_assignments" fk_id:"id" order_by:"created_at asc"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + UpdatedAt time.Time `json:"updated_at" db:"updated_at"` + Active bool `json:"active" db:"active"` + Status *OfficeUserStatus `json:"status" db:"status"` + EDIPI *string `json:"edipi" db:"edipi"` + OtherUniqueID *string `json:"other_unique_id" db:"other_unique_id"` + RejectionReason *string `json:"rejection_reason" db:"rejection_reason"` } // TableName overrides the table name used by Pop. @@ -80,3 +81,12 @@ func FetchOfficeUserByID(tx *pop.Connection, id uuid.UUID) (*OfficeUser, error) err := tx.Find(&user, id) return &user, err } + +// FetchOfficeUserByID fetches an office user by ID +func GetAssignedGBLOCs(o OfficeUser) []string { + var assignedGblocs []string + for _, toa := range o.TransportationOfficeAssignments { + assignedGblocs = append(assignedGblocs, toa.TransportationOffice.Gbloc) + } + return assignedGblocs +} diff --git a/pkg/models/order.go b/pkg/models/order.go index add1e705938..da23b6f6c6e 100644 --- a/pkg/models/order.go +++ b/pkg/models/order.go @@ -1,6 +1,7 @@ package models import ( + "sort" "time" "github.com/gobuffalo/pop/v6" @@ -328,6 +329,203 @@ func (o *Order) IsComplete() bool { return true } +// FetchAllShipmentsExcludingRejected returns all the shipments associated with an order excluding rejected shipments +func (o Order) FetchAllShipmentsExcludingRejected(db *pop.Connection) (map[uuid.UUID]MTOShipments, error) { + // Since this requires looking up the order in the DB, the order must have an ID. This means, the order has to have been created first. + if uuid.UUID.IsNil(o.ID) { + return nil, errors.WithMessage(ErrInvalidOrderID, "You must created the order in the DB before fetching associated shipments.") + } + + var err error + + err = db.Load(&o, "Moves") + if err != nil { + if err.Error() == RecordNotFoundErrorString { + return nil, errors.WithMessage(err, "No Moves were found for the order ID "+o.ID.String()) + } + return nil, errors.WithMessage(err, "Could not load moves for order ID "+o.ID.String()) + } + + // shipmentsMap is a map of key, value pairs where the key is the move id and the value is a list of associated MTOShipments + shipmentsMap := make(map[uuid.UUID]MTOShipments) + + for _, m := range o.Moves { + var shipments MTOShipments + err = db.Load(&m, "MTOShipments") + if err != nil { + return nil, errors.WithMessage(err, "Could not load shipments for move "+m.ID.String()) + } + + for _, s := range m.MTOShipments { + err = db.Load(&s, "Status", "DeletedAt", "CreatedAt", "DestinationAddress") + if err != nil { + return nil, errors.WithMessage(err, "Could not load shipment with ID of "+s.ID.String()+" for move ID "+m.ID.String()) + } + + if s.Status != MTOShipmentStatusRejected && s.Status != MTOShipmentStatusCanceled && s.DeletedAt == nil { + shipments = append(shipments, s) + } + } + + sort.Slice(shipments, func(i, j int) bool { + return shipments[i].CreatedAt.Before(shipments[j].CreatedAt) + }) + + shipmentsMap[m.ID] = shipments + } + + return shipmentsMap, nil +} + +/* + * GetDestinationGBLOC returns a map of destination GBLOCs for the first shipments from all of + * the moves that are associated with an order. If there are no shipments returned on a particular move, + * it will return the GBLOC of the new duty station address for that move. + */ +func (o Order) GetDestinationGBLOC(db *pop.Connection) (map[uuid.UUID]string, error) { + // Since this requires looking up the order in the DB, the order must have an ID. This means, the order has to have been created first. + if uuid.UUID.IsNil(o.ID) { + return nil, errors.WithMessage(ErrInvalidOrderID, "You must created the order in the DB before getting the destination GBLOC.") + } + + destinationPostalCodesMap, err := o.GetDestinationPostalCodeForAssociatedMoves(db) + if err != nil { + return nil, err + } + + destinationGBLOCsMap := make(map[uuid.UUID]string) + for k, v := range destinationPostalCodesMap { + var gblocResult PostalCodeToGBLOC + gblocResult, err = FetchGBLOCForPostalCode(db, v) + if err != nil { + return nil, errors.WithMessage(err, "Could not get GBLOC for postal code "+v+" for move ID "+k.String()) + } + destinationGBLOCsMap[k] = gblocResult.GBLOC + } + + return destinationGBLOCsMap, nil +} + +/* +* GetDestinationPostalCodeForAssociatedMove returns a map of Postal Codes of the destination address for the first shipments from each of +* the moves that are associated with an order. If there are no shipments returned, it will return the +* Postal Code of the new duty station addresses. + */ +func (o Order) GetDestinationPostalCodeForAssociatedMoves(db *pop.Connection) (map[uuid.UUID]string, error) { + if uuid.UUID.IsNil(o.ID) { + return nil, errors.WithMessage(ErrInvalidOrderID, "You must created the order in the DB before getting the destination Postal Code.") + } + + err := db.Load(&o, "Moves", "NewDutyLocation.Address.PostalCode") + if err != nil { + if err.Error() == RecordNotFoundErrorString { + return nil, errors.WithMessage(err, "No Moves were found for the order ID "+o.ID.String()) + } + return nil, err + } + + // zipsMap is a map of key, value pairs where the key is the move id and the value is the destination postal code + zipsMap := make(map[uuid.UUID]string) + for i, m := range o.Moves { + err = db.Load(&o.Moves[i], "MTOShipments") + if err != nil { + if err.Error() == RecordNotFoundErrorString { + return nil, errors.WithMessage(err, "Could not find shipments for move "+m.ID.String()) + } + return nil, err + } + + var shipments MTOShipments + for j, s := range o.Moves[i].MTOShipments { + err = db.Load(&o.Moves[i].MTOShipments[j], "CreatedAt", "Status", "DeletedAt", "DestinationAddress", "ShipmentType", "PPMShipment", "PPMShipment.Status", "PPMShipment.DestinationAddress") + if err != nil { + if err.Error() == RecordNotFoundErrorString { + return nil, errors.WithMessage(err, "Could not load shipment with ID of "+s.ID.String()+" for move ID "+m.ID.String()) + } + return nil, err + } + + if o.Moves[i].MTOShipments[j].Status != MTOShipmentStatusRejected && + o.Moves[i].MTOShipments[j].Status != MTOShipmentStatusCanceled && + o.Moves[i].MTOShipments[j].ShipmentType != MTOShipmentTypeHHGIntoNTSDom && + o.Moves[i].MTOShipments[j].DeletedAt == nil { + shipments = append(shipments, o.Moves[i].MTOShipments[j]) + } + } + + // If we have valid shipments, use the first one's destination address + if len(shipments) > 0 { + sort.Slice(shipments, func(i, j int) bool { + return shipments[i].CreatedAt.Before(shipments[j].CreatedAt) + }) + + var addressResult *Address + addressResult, err = shipments[0].GetDestinationAddress(db) + if err != nil { + if err == ErrMissingDestinationAddress || err == ErrUnsupportedShipmentType { + zipsMap[o.Moves[i].ID] = o.NewDutyLocation.Address.PostalCode + } + return nil, err + } + + if addressResult != nil { + zipsMap[o.Moves[i].ID] = addressResult.PostalCode + } else { + return nil, errors.WithMessage(ErrMissingDestinationAddress, "No destination address was able to be found for the order ID "+o.ID.String()) + } + } else { + // No valid shipments, use new duty location + zipsMap[o.Moves[i].ID] = o.NewDutyLocation.Address.PostalCode + } + } + + if len(zipsMap) == 0 { + return nil, errors.New("No destination postal codes were found for the order ID " + o.ID.String()) + } + + return zipsMap, nil +} + +// UpdateDestinationGBLOC updates the destination GBLOC for the associated Order in the DB +func (o Order) UpdateDestinationGBLOC(db *pop.Connection) error { + // Since this requires looking up the order in the DB, the order must have an ID. This means, the order has to have been created first. + if uuid.UUID.IsNil(o.ID) { + return errors.WithMessage(ErrInvalidOrderID, "You must created the order in the DB before updating the destination GBLOC.") + } + + var dbOrder Order + err := db.Find(&dbOrder, o.ID) + if err != nil { + if err.Error() == RecordNotFoundErrorString { + return errors.WithMessage(err, "No Order was found for the order ID "+o.ID.String()) + } + return err + } + + err = db.Load(&o, "NewDutyLocation.Address.PostalCode") + if err != nil { + if err.Error() == RecordNotFoundErrorString { + return errors.WithMessage(err, "No New Duty Location Address Postal Code was found for the order ID "+o.ID.String()) + } + return err + } + + var gblocResult PostalCodeToGBLOC + gblocResult, err = FetchGBLOCForPostalCode(db, o.NewDutyLocation.Address.PostalCode) + if err != nil { + return errors.WithMessage(err, "Could not get GBLOC for postal code "+o.NewDutyLocation.Address.PostalCode) + } + + dbOrder.DestinationGBLOC = &gblocResult.GBLOC + + err = db.Save(&dbOrder) + if err != nil { + return errors.WithMessage(err, "Could not save the updated destination GBLOC for order ID "+o.ID.String()) + } + + return nil +} + // IsCompleteForGBL checks if orders have all fields necessary to generate a GBL func (o *Order) IsCompleteForGBL() bool { diff --git a/pkg/models/port.go b/pkg/models/port.go new file mode 100644 index 00000000000..aa1887003b3 --- /dev/null +++ b/pkg/models/port.go @@ -0,0 +1,34 @@ +package models + +import ( + "time" + + "github.com/gofrs/uuid" +) + +// PortType represents the type of port +type PortType string + +// String is a string PortType +func (p PortType) String() string { + return string(p) +} + +const ( + PortTypeAir PortType = "A" + PortTypeSurface PortType = "S" + PortTypeBoth PortType = "B" +) + +type Port struct { + ID uuid.UUID `json:"id" db:"id" rw:"r"` + PortCode string `json:"port_code" db:"port_code" rw:"r"` + PortType PortType `json:"port_type" db:"port_type" rw:"r"` + PortName string `json:"port_name" db:"port_name" rw:"r"` + CreatedAt time.Time `json:"created_at" db:"created_at" rw:"r"` + UpdatedAt time.Time `json:"updated_at" db:"updated_at" rw:"r"` +} + +func (p Port) TableName() string { + return "ports" +} diff --git a/pkg/models/port_location.go b/pkg/models/port_location.go new file mode 100644 index 00000000000..65f8b59f9e9 --- /dev/null +++ b/pkg/models/port_location.go @@ -0,0 +1,35 @@ +package models + +import ( + "time" + + "github.com/gobuffalo/pop/v6" + "github.com/gobuffalo/validate/v3" + "github.com/gobuffalo/validate/v3/validators" + "github.com/gofrs/uuid" +) + +type PortLocation struct { + ID uuid.UUID `json:"id" db:"id"` + PortId uuid.UUID `json:"port_id" db:"port_id"` + CitiesId uuid.UUID `json:"cities_id" db:"cities_id"` + UsPostRegionCitiesId uuid.UUID `json:"us_post_region_cities_id" db:"us_post_region_cities_id"` + CountryId uuid.UUID `json:"country_id" db:"country_id"` + IsActive *bool `json:"is_active" db:"is_active"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + UpdatedAt time.Time `json:"updated_at" db:"updated_at"` +} + +func (l PortLocation) TableName() string { + return "port_locations" +} + +// Validate gets run every time you call a "pop.Validate*" (pop.ValidateAndSave, pop.ValidateAndCreate, pop.ValidateAndUpdate) method. +func (p *PortLocation) Validate(_ *pop.Connection) (*validate.Errors, error) { + return validate.Validate( + &validators.UUIDIsPresent{Field: p.PortId, Name: "PortID"}, + &validators.UUIDIsPresent{Field: p.CitiesId, Name: "CitiesID"}, + &validators.UUIDIsPresent{Field: p.UsPostRegionCitiesId, Name: "UsPostRegionCitiesID"}, + &validators.UUIDIsPresent{Field: p.CountryId, Name: "CountryID"}, + ), nil +} diff --git a/pkg/models/port_location_test.go b/pkg/models/port_location_test.go new file mode 100644 index 00000000000..4fc7738e245 --- /dev/null +++ b/pkg/models/port_location_test.go @@ -0,0 +1,42 @@ +package models_test + +import ( + "time" + + "github.com/gofrs/uuid" + + "github.com/transcom/mymove/pkg/models" +) + +func (suite *ModelSuite) TestPortLocationValidation() { + suite.Run("test valid PortLocation", func() { + validPortLocation := models.PortLocation{ + ID: uuid.Must(uuid.NewV4()), + PortId: uuid.Must(uuid.NewV4()), + CitiesId: uuid.Must(uuid.NewV4()), + UsPostRegionCitiesId: uuid.Must(uuid.NewV4()), + CountryId: uuid.Must(uuid.NewV4()), + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + expErrors := map[string][]string{} + suite.verifyValidationErrors(&validPortLocation, expErrors) + }) + + suite.Run("test missing required fields", func() { + invalidPortLocation := models.PortLocation{ + ID: uuid.Must(uuid.NewV4()), + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + + expErrors := map[string][]string{ + "port_id": {"PortID can not be blank."}, + "cities_id": {"CitiesID can not be blank."}, + "us_post_region_cities_id": {"UsPostRegionCitiesID can not be blank."}, + "country_id": {"CountryID can not be blank."}, + } + + suite.verifyValidationErrors(&invalidPortLocation, expErrors) + }) +} diff --git a/pkg/models/ppm_shipment.go b/pkg/models/ppm_shipment.go index 46bcf3af14d..0737417207c 100644 --- a/pkg/models/ppm_shipment.go +++ b/pkg/models/ppm_shipment.go @@ -211,6 +211,7 @@ type PPMShipment struct { HasTertiaryDestinationAddress *bool `db:"has_tertiary_destination_address"` ActualDestinationPostalCode *string `json:"actual_destination_postal_code" db:"actual_destination_postal_code"` EstimatedWeight *unit.Pound `json:"estimated_weight" db:"estimated_weight"` + AllowableWeight *unit.Pound `json:"allowable_weight" db:"allowable_weight"` HasProGear *bool `json:"has_pro_gear" db:"has_pro_gear"` ProGearWeight *unit.Pound `json:"pro_gear_weight" db:"pro_gear_weight"` SpouseProGearWeight *unit.Pound `json:"spouse_pro_gear_weight" db:"spouse_pro_gear_weight"` @@ -274,6 +275,7 @@ func (p PPMShipment) Validate(_ *pop.Connection) (*validate.Errors, error) { &OptionalUUIDIsPresent{Name: "SecondaryDestinationAddressID", Field: p.SecondaryDestinationAddressID}, &StringIsNilOrNotBlank{Name: "ActualDestinationPostalCode", Field: p.ActualDestinationPostalCode}, &OptionalPoundIsNonNegative{Name: "EstimatedWeight", Field: p.EstimatedWeight}, + &OptionalPoundIsNonNegative{Name: "AllowableWeight", Field: p.AllowableWeight}, &OptionalPoundIsNonNegative{Name: "ProGearWeight", Field: p.ProGearWeight}, &OptionalPoundIsNonNegative{Name: "SpouseProGearWeight", Field: p.SpouseProGearWeight}, &OptionalCentIsNotNegative{Name: "EstimatedIncentive", Field: p.EstimatedIncentive}, diff --git a/pkg/models/ppm_shipment_test.go b/pkg/models/ppm_shipment_test.go index db00f31ecda..4def2567968 100644 --- a/pkg/models/ppm_shipment_test.go +++ b/pkg/models/ppm_shipment_test.go @@ -68,6 +68,7 @@ func (suite *ModelSuite) TestPPMShipmentValidation() { ActualPickupPostalCode: models.StringPointer(""), ActualDestinationPostalCode: models.StringPointer(""), EstimatedWeight: models.PoundPointer(unit.Pound(-1)), + AllowableWeight: models.PoundPointer(unit.Pound(-1)), ProGearWeight: models.PoundPointer(unit.Pound(-1)), SpouseProGearWeight: models.PoundPointer(unit.Pound(-1)), EstimatedIncentive: models.CentPointer(unit.Cents(-1)), @@ -96,6 +97,7 @@ func (suite *ModelSuite) TestPPMShipmentValidation() { "actual_pickup_postal_code": {"ActualPickupPostalCode can not be blank."}, "actual_destination_postal_code": {"ActualDestinationPostalCode can not be blank."}, "estimated_weight": {"-1 is less than zero."}, + "allowable_weight": {"-1 is less than zero."}, "pro_gear_weight": {"-1 is less than zero."}, "spouse_pro_gear_weight": {"-1 is less than zero."}, "estimated_incentive": {"EstimatedIncentive cannot be negative, got: -1."}, diff --git a/pkg/models/queue.go b/pkg/models/queue.go index a0aecdab552..d616eb8d94a 100644 --- a/pkg/models/queue.go +++ b/pkg/models/queue.go @@ -19,7 +19,7 @@ type MoveQueueItem struct { Locator string `json:"locator" db:"locator"` Status string `json:"status" db:"status"` PpmStatus *string `json:"ppm_status" db:"ppm_status"` - OrdersType string `json:"orders_type" db:"orders_type"` + OrdersType *internalmessages.OrdersType `json:"orders_type" db:"orders_type"` MoveDate *time.Time `json:"move_date" db:"move_date"` SubmittedDate *time.Time `json:"submitted_date" db:"submitted_date"` LastModifiedDate time.Time `json:"last_modified_date" db:"last_modified_date"` diff --git a/pkg/models/re_contract_year.go b/pkg/models/re_contract_year.go index 42ad4be73bd..e24801e44a3 100644 --- a/pkg/models/re_contract_year.go +++ b/pkg/models/re_contract_year.go @@ -1,6 +1,7 @@ package models import ( + "fmt" "time" "github.com/gobuffalo/pop/v6" @@ -9,6 +10,29 @@ import ( "github.com/gofrs/uuid" ) +const ( + BasePeriodYear1 string = "Base Period Year 1" + BasePeriodYear2 string = "Base Period Year 2" + BasePeriodYear3 string = "Base Period Year 3" + OptionPeriod1 string = "Option Period 1" + OptionPeriod2 string = "Option Period 2" + OptionPeriod3 string = "Option Period 3" + AwardTerm1 string = "Award Term 1" + AwardTerm2 string = "Award Term 2" + AwardTerm string = "Award Term" + OptionPeriod string = "Option Period" + BasePeriodYear string = "Base Period Year" +) + +type ExpectedEscalationPriceContractsCount struct { + ExpectedAmountOfContractYearsForCalculation int + ExpectedAmountOfBasePeriodYearsForCalculation int + ExpectedAmountOfOptionPeriodYearsForCalculation int + ExpectedAmountOfAwardTermsForCalculation int +} + +var ContractYears = []string{AwardTerm1, AwardTerm2, OptionPeriod1, OptionPeriod2, OptionPeriod3, BasePeriodYear2, BasePeriodYear3} + // ReContractYear represents a single "year" of a contract type ReContractYear struct { ID uuid.UUID `json:"id" db:"id"` @@ -46,3 +70,67 @@ func (r *ReContractYear) Validate(_ *pop.Connection) (*validate.Errors, error) { &Float64IsGreaterThan{Field: r.EscalationCompounded, Name: "EscalationCompounded", Compared: 0}, ), nil } + +func GetExpectedEscalationPriceContractsCount(contractYearName string) (ExpectedEscalationPriceContractsCount, error) { + switch contractYearName { + case BasePeriodYear1: + return ExpectedEscalationPriceContractsCount{ + ExpectedAmountOfContractYearsForCalculation: 1, + ExpectedAmountOfBasePeriodYearsForCalculation: 1, + ExpectedAmountOfOptionPeriodYearsForCalculation: 0, + ExpectedAmountOfAwardTermsForCalculation: 0, + }, nil + case BasePeriodYear2: + return ExpectedEscalationPriceContractsCount{ + ExpectedAmountOfContractYearsForCalculation: 2, + ExpectedAmountOfBasePeriodYearsForCalculation: 2, + ExpectedAmountOfOptionPeriodYearsForCalculation: 0, + ExpectedAmountOfAwardTermsForCalculation: 0, + }, nil + case BasePeriodYear3: + return ExpectedEscalationPriceContractsCount{ + ExpectedAmountOfContractYearsForCalculation: 3, + ExpectedAmountOfBasePeriodYearsForCalculation: 3, + ExpectedAmountOfOptionPeriodYearsForCalculation: 0, + ExpectedAmountOfAwardTermsForCalculation: 0, + }, nil + case OptionPeriod1: + return ExpectedEscalationPriceContractsCount{ + ExpectedAmountOfContractYearsForCalculation: 4, + ExpectedAmountOfBasePeriodYearsForCalculation: 3, + ExpectedAmountOfOptionPeriodYearsForCalculation: 1, + ExpectedAmountOfAwardTermsForCalculation: 0, + }, nil + case OptionPeriod2: + return ExpectedEscalationPriceContractsCount{ + ExpectedAmountOfContractYearsForCalculation: 5, + ExpectedAmountOfBasePeriodYearsForCalculation: 3, + ExpectedAmountOfOptionPeriodYearsForCalculation: 2, + ExpectedAmountOfAwardTermsForCalculation: 0, + }, nil + case AwardTerm1: + return ExpectedEscalationPriceContractsCount{ + ExpectedAmountOfContractYearsForCalculation: 6, + ExpectedAmountOfBasePeriodYearsForCalculation: 3, + ExpectedAmountOfOptionPeriodYearsForCalculation: 2, + ExpectedAmountOfAwardTermsForCalculation: 1, + }, nil + case AwardTerm2: + return ExpectedEscalationPriceContractsCount{ + ExpectedAmountOfContractYearsForCalculation: 7, + ExpectedAmountOfBasePeriodYearsForCalculation: 3, + ExpectedAmountOfOptionPeriodYearsForCalculation: 2, + ExpectedAmountOfAwardTermsForCalculation: 2, + }, nil + case OptionPeriod3: + return ExpectedEscalationPriceContractsCount{ + ExpectedAmountOfContractYearsForCalculation: 8, + ExpectedAmountOfBasePeriodYearsForCalculation: 3, + ExpectedAmountOfOptionPeriodYearsForCalculation: 3, + ExpectedAmountOfAwardTermsForCalculation: 2, + }, nil + } + + err := fmt.Errorf("unexpected contract year %s", contractYearName) + return ExpectedEscalationPriceContractsCount{}, err +} diff --git a/pkg/models/re_fsc_multipliers.go b/pkg/models/re_fsc_multipliers.go new file mode 100644 index 00000000000..8dec5e708ad --- /dev/null +++ b/pkg/models/re_fsc_multipliers.go @@ -0,0 +1,22 @@ +package models + +import ( + "time" + + "github.com/gofrs/uuid" + "google.golang.org/genproto/googleapis/type/decimal" +) + +// Note: Multiplier is a pointer to avoid copying a struct that contains a sync.Mutex. +type FscMultiplier struct { + ID uuid.UUID `json:"id" db:"id" rw:"r"` + LowWeight int `json:"low_weight" rw:"r"` + HighWeight int `json:"high_weight" rw:"r"` + Multiplier *decimal.Decimal `json:"multiplier" rw:"r"` + CreatedAt time.Time `json:"created_at" db:"created_at" rw:"r"` + UpdatedAt time.Time `json:"updated_at" db:"updated_at" rw:"r"` +} + +func (f FscMultiplier) TableName() string { + return "re_fsc_multipliers" +} diff --git a/pkg/models/re_oconus_rate_areas.go b/pkg/models/re_oconus_rate_areas.go index 632aa6b36f1..72b85773159 100644 --- a/pkg/models/re_oconus_rate_areas.go +++ b/pkg/models/re_oconus_rate_areas.go @@ -7,13 +7,13 @@ import ( ) type OconusRateArea struct { - ID uuid.UUID `json:"id" db:"id" rw:"r"` - RateAreaId uuid.UUID `json:"rate_area_id" db:"rate_area_id" rw:"r"` - CountryId uuid.UUID `json:"country_id" db:"country_id" rw:"r"` - UsPostRegionCityId uuid.UUID `json:"us_post_region_city_id" db:"us_post_region_city_id" rw:"r"` - Active bool `json:"active" db:"active" rw:"r"` - CreatedAt time.Time `json:"created_at" db:"created_at" rw:"r"` - UpdatedAt time.Time `json:"updated_at" db:"updated_at" rw:"r"` + ID uuid.UUID `json:"id" db:"id"` + RateAreaId uuid.UUID `json:"rate_area_id" db:"rate_area_id"` + CountryId uuid.UUID `json:"country_id" db:"country_id"` + UsPostRegionCityId uuid.UUID `json:"us_post_region_cities_id" db:"us_post_region_cities_id"` + Active bool `json:"active" db:"active"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + UpdatedAt time.Time `json:"updated_at" db:"updated_at"` } func (o OconusRateArea) TableName() string { diff --git a/pkg/models/re_service_item.go b/pkg/models/re_service_item.go index 91dcd826485..f06ee0990a2 100644 --- a/pkg/models/re_service_item.go +++ b/pkg/models/re_service_item.go @@ -13,6 +13,7 @@ type ReServiceItem struct { ShipmentType MTOShipmentType `db:"shipment_type" rw:"r"` MarketCode MarketCode `db:"market_code" rw:"r"` IsAutoApproved bool `db:"is_auto_approved" rw:"r"` + Sort *string `db:"sort" rw:"r"` CreatedAt time.Time `db:"created_at" rw:"r"` UpdatedAt time.Time `db:"updated_at" rw:"r"` } @@ -20,3 +21,6 @@ type ReServiceItem struct { func (r ReServiceItem) TableName() string { return "re_service_items" } + +// ReServiceItems is a slice of ReServiceItem +type ReServiceItems []ReServiceItem diff --git a/pkg/models/service_member.go b/pkg/models/service_member.go index ff0da40de2c..ac70f00306a 100644 --- a/pkg/models/service_member.go +++ b/pkg/models/service_member.go @@ -153,7 +153,8 @@ func FetchServiceMemberForUser(db *pop.Connection, session *auth.Session, id uui "Orders.OriginDutyLocation", "Orders.UploadedOrders.UserUploads.Upload", "Orders.Moves", - "ResidentialAddress").Find(&serviceMember, id) + "ResidentialAddress", + "Affiliation").Find(&serviceMember, id) if err != nil { if errors.Cause(err).Error() == RecordNotFoundErrorString { @@ -365,7 +366,7 @@ func (s ServiceMember) CreateOrder(appCtx appcontext.AppContext, entitlement *Entitlement, originDutyLocationGBLOC *string, packingAndShippingInstructions string, - newDutyLocationGBLOC *string) (Order, *validate.Errors, error) { + destinationGBLOC *string) (Order, *validate.Errors, error) { var newOrders Order responseVErrors := validate.NewErrors() @@ -394,7 +395,7 @@ func (s ServiceMember) CreateOrder(appCtx appcontext.AppContext, SpouseHasProGear: spouseHasProGear, NewDutyLocationID: newDutyLocation.ID, NewDutyLocation: newDutyLocation, - DestinationGBLOC: newDutyLocationGBLOC, + DestinationGBLOC: destinationGBLOC, UploadedOrders: uploadedOrders, UploadedOrdersID: uploadedOrders.ID, Status: OrderStatusDRAFT, diff --git a/pkg/models/transportation_office_assignments.go b/pkg/models/transportation_office_assignments.go index 99960ff48b8..9c77ff7f500 100644 --- a/pkg/models/transportation_office_assignments.go +++ b/pkg/models/transportation_office_assignments.go @@ -10,25 +10,25 @@ import ( ) // TransportationAssignment is the transportation office the OfficeUser is assigned to -type TransportationAssignment struct { +type TransportationOfficeAssignment struct { ID uuid.UUID `json:"id" db:"id"` TransportationOfficeID uuid.UUID `json:"transportation_office_id" db:"transportation_office_id"` TransportationOffice TransportationOffice `belongs_to:"transportation_office" fk_id:"transportation_office_id"` CreatedAt time.Time `json:"created_at" db:"created_at"` UpdatedAt time.Time `json:"updated_at" db:"updated_at"` - PrimaryOffice bool `json:"primary_office" db:"primary_office"` + PrimaryOffice *bool `json:"primary_office" db:"primary_office"` } // TableName overrides the table name used by Pop. -func (o TransportationAssignment) TableName() string { +func (t TransportationOfficeAssignment) TableName() string { return "transportation_office_assignments" } -type TransportationAssignments []TransportationAssignment +type TransportationOfficeAssignments []TransportationOfficeAssignment // Validate gets run every time you call a "pop.Validate*" (pop.ValidateAndSave, pop.ValidateAndCreate, pop.ValidateAndUpdate) method. -func (o *TransportationAssignment) Validate(_ *pop.Connection) (*validate.Errors, error) { +func (t *TransportationOfficeAssignment) Validate(_ *pop.Connection) (*validate.Errors, error) { return validate.Validate( - &validators.UUIDIsPresent{Field: o.TransportationOfficeID, Name: "TransportationOfficeID"}, + &validators.UUIDIsPresent{Field: t.TransportationOfficeID, Name: "TransportationOfficeID"}, ), nil } diff --git a/pkg/models/ub_allowances.go b/pkg/models/ub_allowances.go new file mode 100644 index 00000000000..53cf1549e0a --- /dev/null +++ b/pkg/models/ub_allowances.go @@ -0,0 +1,19 @@ +package models + +import "github.com/gofrs/uuid" + +// UBAllowances represents the UB weight allowance for a branch, grade, order type, dependents authorized, and accompanied tour variables on an order +type UBAllowances struct { + ID uuid.UUID `db:"id" rw:"r"` + BranchOfService *string `db:"branch" rw:"r"` + OrderPayGrade *string `db:"grade" rw:"r"` + OrdersType *string `db:"orders_type" rw:"r"` + HasDependents *bool `db:"dependents_authorized" rw:"r"` + AccompaniedTour *bool `db:"accompanied_tour" rw:"r"` + UBAllowance *int `db:"ub_weight_allowance" rw:"r"` +} + +// TableName overrides the table name used by Pop. +func (m UBAllowances) TableName() string { + return "ub_allowances" +} diff --git a/pkg/models/us_post_region_city.go b/pkg/models/us_post_region_city.go index 88751234ce6..6644c7b9c40 100644 --- a/pkg/models/us_post_region_city.go +++ b/pkg/models/us_post_region_city.go @@ -58,3 +58,17 @@ func FindCountyByZipCode(db *pop.Connection, zipCode string) (string, error) { } return usprc.UsprcCountyNm, nil } + +func FindByZipCode(db *pop.Connection, zipCode string) (*UsPostRegionCity, error) { + var usprc UsPostRegionCity + err := db.Where("uspr_zip_id = ?", zipCode).First(&usprc) + if err != nil { + switch err { + case sql.ErrNoRows: + return nil, apperror.NewEventError("No UsPostRegionCity found for provided zip code "+zipCode+".", err) + default: + return nil, err + } + } + return &usprc, nil +} diff --git a/pkg/models/us_post_region_city_test.go b/pkg/models/us_post_region_city_test.go index 2c181e8ff8d..41909b5a491 100644 --- a/pkg/models/us_post_region_city_test.go +++ b/pkg/models/us_post_region_city_test.go @@ -17,3 +17,12 @@ func (suite *ModelSuite) TestFindCountyByZipCode() { _, err = models.FindCountyByZipCode(suite.DB(), "99999") suite.Error(err) } + +func (suite *ModelSuite) TestFindByZipCode() { + + // Attempt to gather 90210's County from the 90210 zip code + usPostRegionCity, err := models.FindByZipCode(suite.DB(), "90210") + suite.NotNil(usPostRegionCity) + suite.NoError(err) + suite.Equal("LOS ANGELES", usPostRegionCity.UsprcCountyNm) +} diff --git a/pkg/models/user.go b/pkg/models/user.go index e895fe01fcd..fec1509f958 100644 --- a/pkg/models/user.go +++ b/pkg/models/user.go @@ -103,26 +103,27 @@ func UpdateUserOktaID(db *pop.Connection, user *User, oktaID string) error { // UserIdentity is summary of the information about a user from the database type UserIdentity struct { - ID uuid.UUID `db:"id"` - Active bool `db:"active"` - Email string `db:"email"` - ServiceMemberID *uuid.UUID `db:"sm_id"` - ServiceMemberFirstName *string `db:"sm_fname"` - ServiceMemberLastName *string `db:"sm_lname"` - ServiceMemberMiddle *string `db:"sm_middle"` - ServiceMemberCacValidated *bool `db:"sm_cac_validated"` - OfficeUserID *uuid.UUID `db:"ou_id"` - OfficeUserFirstName *string `db:"ou_fname"` - OfficeUserLastName *string `db:"ou_lname"` - OfficeUserMiddle *string `db:"ou_middle"` - OfficeActive *bool `db:"ou_active"` - AdminUserID *uuid.UUID `db:"au_id"` - AdminUserRole *AdminRole `db:"au_role"` - AdminUserFirstName *string `db:"au_fname"` - AdminUserLastName *string `db:"au_lname"` - AdminUserActive *bool `db:"au_active"` - Roles roles.Roles `many_to_many:"users_roles" primary_id:"user_id"` - Privileges Privileges `many_to_many:"users_privileges" primary_id:"user_id"` + ID uuid.UUID `db:"id"` + Active bool `db:"active"` + Email string `db:"email"` + ServiceMemberID *uuid.UUID `db:"sm_id"` + ServiceMemberFirstName *string `db:"sm_fname"` + ServiceMemberLastName *string `db:"sm_lname"` + ServiceMemberMiddle *string `db:"sm_middle"` + ServiceMemberCacValidated *bool `db:"sm_cac_validated"` + OfficeUserID *uuid.UUID `db:"ou_id"` + OfficeUserFirstName *string `db:"ou_fname"` + OfficeUserLastName *string `db:"ou_lname"` + OfficeUserMiddle *string `db:"ou_middle"` + OfficeActive *bool `db:"ou_active"` + AdminUserID *uuid.UUID `db:"au_id"` + AdminUserRole *AdminRole `db:"au_role"` + AdminUserFirstName *string `db:"au_fname"` + AdminUserLastName *string `db:"au_lname"` + AdminUserActive *bool `db:"au_active"` + Roles roles.Roles `many_to_many:"users_roles" primary_id:"user_id"` + Privileges Privileges `many_to_many:"users_privileges" primary_id:"user_id"` + TransportationOfficeAssignments TransportationOfficeAssignments `many_to_many:"transportation_office_assignmentss" primary_id:"id"` } // FetchUserIdentity queries the database for information about the logged in user @@ -170,6 +171,12 @@ func FetchUserIdentity(db *pop.Connection, oktaID string) (*UserIdentity, error) if privilegeError != nil { return nil, privilegeError } + transportationOfficeAssignmentError := db.EagerPreload("TransportationOffice"). + Join("transportation_offices", "transportation_office_assignments.transportation_office_id = transportation_offices.id"). + Where("transportation_office_assignments.id = ?", identity.OfficeUserID).All(&identity.TransportationOfficeAssignments) + if transportationOfficeAssignmentError != nil { + return nil, transportationOfficeAssignmentError + } return identity, nil } diff --git a/pkg/models/users_privileges.go b/pkg/models/users_privileges.go index 89a25cc67af..f69c6a5f77a 100644 --- a/pkg/models/users_privileges.go +++ b/pkg/models/users_privileges.go @@ -12,7 +12,7 @@ type UsersPrivileges struct { UserID uuid.UUID `db:"user_id"` PrivilegeID uuid.UUID `db:"privilege_id"` CreatedAt time.Time `db:"created_at"` - UpdateAt time.Time `db:"updated_at"` + UpdatedAt time.Time `db:"updated_at"` DeletedAt *time.Time `db:"deleted_at"` } diff --git a/pkg/models/weight_ticket.go b/pkg/models/weight_ticket.go index 2ca17ebdb9e..fa7ecefb8d1 100644 --- a/pkg/models/weight_ticket.go +++ b/pkg/models/weight_ticket.go @@ -41,7 +41,6 @@ type WeightTicket struct { Reason *string `json:"reason" db:"reason"` AdjustedNetWeight *unit.Pound `json:"adjusted_net_weight" db:"adjusted_net_weight"` NetWeightRemarks *string `json:"net_weight_remarks" db:"net_weight_remarks"` - AllowableWeight *unit.Pound `json:"allowable_weight" db:"allowable_weight"` } // TableName overrides the table name used by Pop. @@ -82,7 +81,6 @@ func (w *WeightTicket) Validate(_ *pop.Connection) (*validate.Errors, error) { &StringIsNilOrNotBlank{Name: "Reason", Field: w.Reason}, &OptionalPoundIsNonNegative{Name: "AdjustedNetWeight", Field: w.AdjustedNetWeight}, &StringIsNilOrNotBlank{Name: "NetWeightRemarks", Field: w.NetWeightRemarks}, - &OptionalPoundIsNonNegative{Name: "AllowableWeight", Field: w.AllowableWeight}, ), nil } func GetWeightTicketNetWeight(w WeightTicket) unit.Pound { diff --git a/pkg/models/weight_ticket_test.go b/pkg/models/weight_ticket_test.go index 4ede7f32f50..88da13bc621 100644 --- a/pkg/models/weight_ticket_test.go +++ b/pkg/models/weight_ticket_test.go @@ -49,7 +49,6 @@ func (suite *ModelSuite) TestWeightTicketValidation() { Reason: models.StringPointer(""), AdjustedNetWeight: models.PoundPointer(unit.Pound(-1)), NetWeightRemarks: models.StringPointer(""), - AllowableWeight: models.PoundPointer(unit.Pound(-1)), }, expectedErrs: map[string][]string{ "deleted_at": {"DeletedAt can not be blank."}, @@ -60,7 +59,6 @@ func (suite *ModelSuite) TestWeightTicketValidation() { "reason": {"Reason can not be blank."}, "adjusted_net_weight": {"-1 is less than zero."}, "net_weight_remarks": {"NetWeightRemarks can not be blank."}, - "allowable_weight": {"-1 is less than zero."}, }, }, } diff --git a/pkg/notifications/constants.go b/pkg/notifications/constants.go index 3829e3945f8..78ac14d1255 100644 --- a/pkg/notifications/constants.go +++ b/pkg/notifications/constants.go @@ -7,7 +7,7 @@ const MyMoveLink = "https://my.move.mil/" const WashingtonHQServicesLink = "https://www.esd.whs.mil" const SmartVoucherLink = "https://smartvoucher.dfas.mil/" -const DTODFailureErrorMessage = "We are unable to calculate your distance. It may be that you have entered an invalid ZIP Code, or the system that calculates distance (DTOD) may be down. Please check your ZIP Code to ensure it was entered correctly and is not a PO Box." +const DTODFailureErrorMessage = "We are unable to calculate your distance. It may be that you have entered an invalid ZIP Code. Please check your ZIP Code to ensure it was entered correctly and is not a PO Box." const DTODDownErrorMessage = "We are having an issue with the system we use to calculate mileage (DTOD) and cannot proceed." var affiliationDisplayValue = map[models.ServiceMemberAffiliation]string{ diff --git a/pkg/notifications/move_counseled_test.go b/pkg/notifications/move_counseled_test.go index 047d7f9ba30..df617f2ea41 100644 --- a/pkg/notifications/move_counseled_test.go +++ b/pkg/notifications/move_counseled_test.go @@ -47,9 +47,12 @@ func (suite *NotificationSuite) TestMoveCounseledHTMLTemplateRender() { } expectedHTMLContent := `

*** DO NOT REPLY directly to this email ***

+

This is a confirmation that your counselor has approved move details for the assigned move code abc123 from origDutyLocation to destDutyLocation in the MilMove system.

+

What this means to you:
If you are doing a Personally Procured Move (PPM), you can start moving your personal property.

+

Next steps for a PPM:

+

Next steps for government arranged shipments:

Thank you,
USTRANSCOM MilMove Team

+

The information contained in this email may contain Privacy Act information and is therefore protected under the Privacy Act of 1974. Failure to protect Privacy Act information could result in a $5,000 fine.

` htmlContent, err := notification.RenderHTML(suite.AppContextWithSessionForTest(&auth.Session{ @@ -99,9 +104,12 @@ func (suite *NotificationSuite) TestMoveCounseledTextTemplateRender() { } expectedTextContent := `*** DO NOT REPLY directly to this email *** + This is a confirmation that your counselor has approved move details for the assigned move code abc123 from origDutyLocation to destDutyLocation in the MilMove system. + What this means to you: If you are doing a Personally Procured Move (PPM), you can start moving your personal property. + Next steps for a PPM: * Remember to get legible certified weight tickets for both the empty and full weights for every trip you perform. If you do not upload legible certified weight tickets, your PPM incentive (or Actual Expense Reimbursement for Civilians) could be affected. Failure to obtain weight tickets will result in losing eligibility to receive your incentive. Note: To receive allowance for Pro-Gear, you must identify allowable items and provide weight tickets separately for Pro-Gear. @@ -110,12 +118,15 @@ Note: To receive allowance for Pro-Gear, you must identify allowable items and p * Storage costs cannot be paid in advance. * If your counselor approved an Advance Operating Allowance (AOA, or cash advance) for a PPM, log into MilMove to download your AOA Packet, and submit it to finance according to the instructions provided by your counselor. If you have been directed to use your government travel charge card (GTCC) for expenses no further action is required. * Once you complete your PPM, log into MilMove , upload your receipts and weight tickets, and submit your PPM for review. + Next steps for government arranged shipments: * Your move request will be reviewed by the responsible personal property shipping office and a move task order for services will be placed with HomeSafe Alliance. * Once this order is placed, you will receive an e-mail invitation to create an account in HomeSafe Connect (check your spam or junk folder). This is the system you will use to schedule your pre-move survey. * HomeSafe is required to contact you within one Government Business Day. Once contact has been established, HomeSafe is your primary point of contact. If any information about your move changes at any point during the move, immediately notify your HomeSafe Customer Care Representative of the changes. Remember to keep your contact information updated in MilMove. + Thank you, USTRANSCOM MilMove Team + The information contained in this email may contain Privacy Act information and is therefore protected under the Privacy Act of 1974. Failure to protect Privacy Act information could result in a $5,000 fine.` textContent, err := notification.RenderText(suite.AppContextWithSessionForTest(&auth.Session{ diff --git a/pkg/notifications/move_payment_reminder.go b/pkg/notifications/move_payment_reminder.go index 1c3f5d7712b..6a7e33b02cd 100644 --- a/pkg/notifications/move_payment_reminder.go +++ b/pkg/notifications/move_payment_reminder.go @@ -11,6 +11,7 @@ import ( "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/assets" + "github.com/transcom/mymove/pkg/gen/internalmessages" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/unit" ) @@ -46,14 +47,21 @@ type PaymentReminderEmailInfos []PaymentReminderEmailInfo // PaymentReminderEmailInfo contains payment reminder data for rendering a template type PaymentReminderEmailInfo struct { - ServiceMemberID uuid.UUID `db:"id"` - Email *string `db:"personal_email"` - NewDutyLocationName string `db:"new_duty_location_name"` - OriginDutyLocationName string `db:"origin_duty_location_name"` - MoveDate string `db:"move_date"` - Locator string `db:"locator"` - WeightEstimate *unit.Pound `db:"weight_estimate"` - IncentiveEstimate *unit.Cents `db:"incentive_estimate"` + ServiceMemberID uuid.UUID `db:"id"` + Email *string `db:"personal_email"` + NewDutyLocationName string `db:"new_duty_location_name"` + OriginDutyLocationName string `db:"origin_duty_location_name"` + MoveDate string `db:"move_date"` + Locator string `db:"locator"` + WeightEstimate *unit.Pound `db:"weight_estimate"` + IncentiveEstimate *unit.Cents `db:"incentive_estimate"` + DestinationStreet1 *string `db:"destination_street_address_1"` + DestinationStreet2 *string `db:"destination_street_address_2"` + DestinationStreet3 *string `db:"destination_street_address_3"` + DestinationCity *string `db:"destination_city"` + DestinationState *string `db:"destination_state"` + DestinationPostalCode *string `db:"destination_postal_code"` + OrdersType internalmessages.OrdersType `db:"orders_type"` } // GetEmailInfo fetches payment email information @@ -65,7 +73,14 @@ func (m PaymentReminder) GetEmailInfo(appCtx appcontext.AppContext) (PaymentRemi ps.expected_departure_date as move_date, dln.name AS new_duty_location_name, dln2.name AS origin_duty_location_name, - m.locator + m.locator, + da.street_address_1 AS destination_street_address_1, + da.street_address_2 AS destination_street_address_2, + da.street_address_3 AS destination_street_address_3, + da.city AS destination_city, + da.state AS destination_state, + da.postal_code AS destination_postal_code, + o.orders_type FROM ppm_shipments ps JOIN mto_shipments ms on ms.id = ps.shipment_id JOIN moves m ON ms.move_id = m.id @@ -73,6 +88,7 @@ FROM ppm_shipments ps JOIN service_members sm ON o.service_member_id = sm.id JOIN duty_locations dln ON o.new_duty_location_id = dln.id JOIN duty_locations dln2 ON o.origin_duty_location_id = dln2.id + JOIN addresses da ON ps.destination_postal_address_id = da.id WHERE ps.status = 'WAITING_ON_CUSTOMER'::public."ppm_shipment_status" AND ms.status = 'APPROVED'::public."mto_shipment_status" AND ps.expected_departure_date <= now() - ($1)::interval @@ -109,11 +125,11 @@ func (m PaymentReminder) formatEmails(appCtx appcontext.AppContext, PaymentRemin var emails []emailContent for _, PaymentReminderEmailInfo := range PaymentReminderEmailInfos { htmlBody, textBody, err := m.renderTemplates(appCtx, PaymentReminderEmailData{ - OriginDutyLocation: PaymentReminderEmailInfo.OriginDutyLocationName, - DestinationDutyLocation: PaymentReminderEmailInfo.NewDutyLocationName, - Locator: PaymentReminderEmailInfo.Locator, - OneSourceLink: OneSourceTransportationOfficeLink, - MyMoveLink: MyMoveLink, + OriginDutyLocation: PaymentReminderEmailInfo.OriginDutyLocationName, + DestinationLocation: getDestinationLocation(PaymentReminderEmailInfo), + Locator: PaymentReminderEmailInfo.Locator, + OneSourceLink: OneSourceTransportationOfficeLink, + MyMoveLink: MyMoveLink, }) if err != nil { appCtx.Logger().Error("error rendering template", zap.Error(err)) @@ -152,6 +168,33 @@ func (m PaymentReminder) renderTemplates(appCtx appcontext.AppContext, data Paym return htmlBody, textBody, nil } +func getDestinationLocation(PaymentReminderEmailInfo PaymentReminderEmailInfo) string { + destinationLocation := PaymentReminderEmailInfo.NewDutyLocationName + ordersType := PaymentReminderEmailInfo.OrdersType + street1 := PaymentReminderEmailInfo.DestinationStreet1 + isSeparateeOrRetireeOrder := ordersType == internalmessages.OrdersTypeRETIREMENT || ordersType == internalmessages.OrdersTypeSEPARATION + if isSeparateeOrRetireeOrder && street1 != nil { + street2, street3, city, state, postalCode := "", "", "", "", "" + if PaymentReminderEmailInfo.DestinationStreet2 != nil { + street2 = " " + *PaymentReminderEmailInfo.DestinationStreet2 + } + if PaymentReminderEmailInfo.DestinationStreet3 != nil { + street3 = " " + *PaymentReminderEmailInfo.DestinationStreet3 + } + if PaymentReminderEmailInfo.DestinationCity != nil { + city = ", " + *PaymentReminderEmailInfo.DestinationCity + } + if PaymentReminderEmailInfo.DestinationState != nil { + state = ", " + *PaymentReminderEmailInfo.DestinationState + } + if PaymentReminderEmailInfo.DestinationPostalCode != nil { + postalCode = " " + *PaymentReminderEmailInfo.DestinationPostalCode + } + destinationLocation = fmt.Sprintf("%s%s%s%s%s%s", *street1, street2, street3, city, state, postalCode) + } + return destinationLocation +} + // OnSuccess callback passed to be invoked by NewNotificationSender when an email successfully sent // saves the svs the email info along with the SES mail id to the notifications table func (m PaymentReminder) OnSuccess(appCtx appcontext.AppContext, PaymentReminderEmailInfo PaymentReminderEmailInfo) func(string) error { @@ -174,11 +217,11 @@ func (m PaymentReminder) OnSuccess(appCtx appcontext.AppContext, PaymentReminder // PaymentReminderEmailData is used to render an email template type PaymentReminderEmailData struct { - OriginDutyLocation string - DestinationDutyLocation string - Locator string - OneSourceLink string - MyMoveLink string + OriginDutyLocation string + DestinationLocation string + Locator string + OneSourceLink string + MyMoveLink string } // RenderHTML renders the html for the email diff --git a/pkg/notifications/move_payment_reminder_test.go b/pkg/notifications/move_payment_reminder_test.go index 2da424994f3..2b30b22b139 100644 --- a/pkg/notifications/move_payment_reminder_test.go +++ b/pkg/notifications/move_payment_reminder_test.go @@ -4,6 +4,7 @@ import ( "time" "github.com/transcom/mymove/pkg/factory" + "github.com/transcom/mymove/pkg/gen/internalmessages" "github.com/transcom/mymove/pkg/models" ) @@ -77,6 +78,13 @@ func (suite *NotificationSuite) TestPaymentReminderFetchSomeFound() { suite.Equal(ppms[i].EstimatedWeight, emailInfo[j].WeightEstimate) suite.Equal(ppms[i].EstimatedIncentive, emailInfo[j].IncentiveEstimate) suite.Equal(ppms[i].Shipment.MoveTaskOrder.Locator, emailInfo[j].Locator) + suite.Equal(ppms[i].DestinationAddress.StreetAddress1, *emailInfo[j].DestinationStreet1) + suite.Equal(ppms[i].DestinationAddress.StreetAddress2, emailInfo[j].DestinationStreet2) + suite.Equal(ppms[i].DestinationAddress.StreetAddress3, emailInfo[j].DestinationStreet3) + suite.Equal(ppms[i].DestinationAddress.City, *emailInfo[j].DestinationCity) + suite.Equal(ppms[i].DestinationAddress.State, *emailInfo[j].DestinationState) + suite.Equal(ppms[i].DestinationAddress.PostalCode, *emailInfo[j].DestinationPostalCode) + suite.Equal(ppms[i].Shipment.MoveTaskOrder.Orders.OrdersType, emailInfo[j].OrdersType) } } } @@ -136,16 +144,16 @@ func (suite *NotificationSuite) TestPaymentReminderHTMLTemplateRender() { suite.NoError(err) paymentReminderData := PaymentReminderEmailData{ - OriginDutyLocation: "OriginDutyLocation", - DestinationDutyLocation: "DestDutyLocation", - Locator: "abc123", - OneSourceLink: OneSourceTransportationOfficeLink, - MyMoveLink: MyMoveLink, + OriginDutyLocation: "OriginDutyLocation", + DestinationLocation: "DestDutyLocation", + Locator: "abc123", + OneSourceLink: OneSourceTransportationOfficeLink, + MyMoveLink: MyMoveLink, } expectedHTMLContent := `

*** DO NOT REPLY directly to this email ***

This is a reminder that your PPM with the assigned move code ` + paymentReminderData.Locator + ` from -` + paymentReminderData.OriginDutyLocation + ` to ` + paymentReminderData.DestinationDutyLocation + ` is awaiting action in MilMove.

+` + paymentReminderData.OriginDutyLocation + ` to ` + paymentReminderData.DestinationLocation + ` is awaiting action in MilMove.

Next steps:

@@ -193,17 +201,17 @@ func (suite *NotificationSuite) TestPaymentReminderTextTemplateRender() { pr, err := NewPaymentReminder() suite.NoError(err) paymentReminderData := PaymentReminderEmailData{ - OriginDutyLocation: "OriginDutyLocation", - DestinationDutyLocation: "DestDutyLocation", - Locator: "abc123", - OneSourceLink: OneSourceTransportationOfficeLink, - MyMoveLink: MyMoveLink, + OriginDutyLocation: "OriginDutyLocation", + DestinationLocation: "DestDutyLocation", + Locator: "abc123", + OneSourceLink: OneSourceTransportationOfficeLink, + MyMoveLink: MyMoveLink, } expectedTextContent := `*** DO NOT REPLY directly to this email *** This is a reminder that your PPM with the assigned move code ` + paymentReminderData.Locator + ` from ` + paymentReminderData.OriginDutyLocation + ` -to ` + paymentReminderData.DestinationDutyLocation + ` is awaiting action in MilMove. +to ` + paymentReminderData.DestinationLocation + ` is awaiting action in MilMove. Next steps: @@ -280,10 +288,10 @@ func (suite *NotificationSuite) TestFormatPaymentRequestedEmails() { emailInfo := emailInfos[i] data := PaymentReminderEmailData{ - DestinationDutyLocation: emailInfo.NewDutyLocationName, - Locator: emailInfo.Locator, - OneSourceLink: OneSourceTransportationOfficeLink, - MyMoveLink: MyMoveLink, + DestinationLocation: emailInfo.NewDutyLocationName, + Locator: emailInfo.Locator, + OneSourceLink: OneSourceTransportationOfficeLink, + MyMoveLink: MyMoveLink, } htmlBody, err := pr.RenderHTML(suite.AppContextForTest(), data) suite.NoError(err) @@ -299,8 +307,134 @@ func (suite *NotificationSuite) TestFormatPaymentRequestedEmails() { suite.Equal(expectedEmailContent.recipientEmail, actualEmailContent.recipientEmail) suite.Equal(expectedEmailContent.subject, actualEmailContent.subject) suite.Equal(expectedEmailContent.textBody, actualEmailContent.textBody) + suite.Equal(expectedEmailContent.htmlBody, actualEmailContent.htmlBody) } } // only expect the three moves with non-nil email addresses to get added to formattedEmails suite.Len(formattedEmails, 3) } + +func (suite *NotificationSuite) TestFormatPaymentRequestedEmailsForRetireeSeparation() { + pr, err := NewPaymentReminder() + suite.NoError(err) + + email1 := "email1" + streetOne1 := "100 Street Rd" + streetTwo1 := "STE 1" + streetThree1 := "Floor 1" + city1 := "Alpha City" + state1 := "Alabama" + postalCode1 := "11111" + expectedDestination1 := "100 Street Rd STE 1 Floor 1, Alpha City, Alabama 11111" + + email2 := "email2" + expectedDestination2 := "nd2" // no street address, fall back to duty station + + email3 := "email3" + streetOne3 := "300 Highway Ln" + city3 := "Charlie City" + state3 := "California" + postalCode3 := "33333" + expectedDestination3 := "300 Highway Ln, Charlie City, California 33333" + + email4 := "email4" + streetOne4 := "400 Unused Blvd" + city4 := "Delta City" + state4 := "Delaware" + postalCode4 := "44444" + expectedDestination4 := "nd4" // Permanent Change of Station, ignore street address + + email5 := "email5" + streetOne5 := "500 Parkway Dr" + expectedDestination5 := "500 Parkway Dr" // Tolerate other nil address fields + + expectedDestinations := []string{ + expectedDestination1, + expectedDestination2, + expectedDestination3, + expectedDestination4, + expectedDestination5, + } + + emailInfos := PaymentReminderEmailInfos{ + { + Email: &email1, + NewDutyLocationName: "nd1", + Locator: "abc123", + OrdersType: internalmessages.OrdersTypeRETIREMENT, + DestinationStreet1: &streetOne1, + DestinationStreet2: &streetTwo1, + DestinationStreet3: &streetThree1, + DestinationCity: &city1, + DestinationState: &state1, + DestinationPostalCode: &postalCode1, + }, + { + Email: &email2, + NewDutyLocationName: "nd2", + Locator: "abc456", + OrdersType: internalmessages.OrdersTypeRETIREMENT, + }, + { + Email: &email3, + NewDutyLocationName: "nd3", + Locator: "def123", + OrdersType: internalmessages.OrdersTypeSEPARATION, + DestinationStreet1: &streetOne3, + DestinationCity: &city3, + DestinationState: &state3, + DestinationPostalCode: &postalCode3, + }, + { + Email: &email4, + NewDutyLocationName: "nd4", + Locator: "def456", + OrdersType: internalmessages.OrdersTypePERMANENTCHANGEOFSTATION, + DestinationStreet1: &streetOne4, + DestinationCity: &city4, + DestinationState: &state4, + DestinationPostalCode: &postalCode4, + }, + { + Email: &email5, + NewDutyLocationName: "nd5", + Locator: "ghi123", + OrdersType: internalmessages.OrdersTypeRETIREMENT, + DestinationStreet1: &streetOne5, + DestinationCity: nil, + DestinationState: nil, + DestinationPostalCode: nil, + }, + } + formattedEmails, err := pr.formatEmails(suite.AppContextForTest(), emailInfos) + suite.NoError(err) + + for i, actualEmailContent := range formattedEmails { + emailInfo := emailInfos[i] + expectedDestination := expectedDestinations[i] + + data := PaymentReminderEmailData{ + DestinationLocation: expectedDestination, + Locator: emailInfo.Locator, + OneSourceLink: OneSourceTransportationOfficeLink, + MyMoveLink: MyMoveLink, + } + htmlBody, err := pr.RenderHTML(suite.AppContextForTest(), data) + suite.NoError(err) + textBody, err := pr.RenderText(suite.AppContextForTest(), data) + suite.NoError(err) + expectedEmailContent := emailContent{ + recipientEmail: *emailInfo.Email, + subject: "Complete your Personally Procured Move (PPM)", + htmlBody: htmlBody, + textBody: textBody, + } + if emailInfo.Email != nil { + suite.Equal(expectedEmailContent.recipientEmail, actualEmailContent.recipientEmail) + suite.Equal(expectedEmailContent.subject, actualEmailContent.subject) + suite.Equal(expectedEmailContent.textBody, actualEmailContent.textBody) + suite.Equal(expectedEmailContent.htmlBody, actualEmailContent.htmlBody) + } + } + suite.Len(formattedEmails, 5) +} diff --git a/pkg/notifications/move_submitted.go b/pkg/notifications/move_submitted.go index 44bc484d46a..826be4fa6ba 100644 --- a/pkg/notifications/move_submitted.go +++ b/pkg/notifications/move_submitted.go @@ -92,7 +92,11 @@ func (m MoveSubmitted) emails(appCtx appcontext.AppContext) ([]emailContent, err originDutyLocationName = &originDutyLocation.Name } - totalEntitlement := models.GetWeightAllotment(*orders.Grade) + 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 + } weight := totalEntitlement.TotalWeightSelf if orders.HasDependents { diff --git a/pkg/services/address/address_creator_test.go b/pkg/services/address/address_creator_test.go index 084f00f3739..c05ff6adca0 100644 --- a/pkg/services/address/address_creator_test.go +++ b/pkg/services/address/address_creator_test.go @@ -177,4 +177,22 @@ func (suite *AddressSuite) TestAddressCreator() { suite.Nil(err) suite.NotNil(address.Country) }) + + suite.Run("Successfully creates a CONUS address", func() { + country := &models.Country{} + country.Country = "US" + addressCreator := NewAddressCreator() + address, err := addressCreator.CreateAddress(suite.AppContextForTest(), &models.Address{ + StreetAddress1: "7645 Ballinshire N", + City: "Indianapolis", + State: "IN", + PostalCode: "46254", + Country: country, + }) + + suite.False(*address.IsOconus) + suite.NotNil(address.ID) + suite.Nil(err) + suite.NotNil(address.Country) + }) } diff --git a/pkg/services/ghcrateengine/domestic_origin_pricer_test.go b/pkg/services/ghcrateengine/domestic_origin_pricer_test.go index 8e43cdaf246..aa5d927e279 100644 --- a/pkg/services/ghcrateengine/domestic_origin_pricer_test.go +++ b/pkg/services/ghcrateengine/domestic_origin_pricer_test.go @@ -69,7 +69,7 @@ func (suite *GHCRateEngineServiceSuite) TestPriceDomesticOriginWithServiceItemPa suite.Equal(expectedCost, cost) expectedParams := services.PricingDisplayParams{ - {Key: models.ServiceItemParamNameContractYearName, Value: "Test Contract Year"}, + {Key: models.ServiceItemParamNameContractYearName, Value: "Base Period Year 1"}, {Key: models.ServiceItemParamNameEscalationCompounded, Value: "1.04070"}, {Key: models.ServiceItemParamNameIsPeak, Value: "true"}, {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: "1.46"}, @@ -126,7 +126,7 @@ func (suite *GHCRateEngineServiceSuite) TestPriceDomesticOrigin() { suite.Equal(expectedCost, cost) expectedParams := services.PricingDisplayParams{ - {Key: models.ServiceItemParamNameContractYearName, Value: "Test Contract Year"}, + {Key: models.ServiceItemParamNameContractYearName, Value: "Base Period Year 1"}, {Key: models.ServiceItemParamNameEscalationCompounded, Value: "1.04070"}, {Key: models.ServiceItemParamNameIsPeak, Value: "true"}, {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: "1.46"}, @@ -153,7 +153,7 @@ func (suite *GHCRateEngineServiceSuite) TestPriceDomesticOrigin() { suite.Equal(expectedCost, cost) expectedParams := services.PricingDisplayParams{ - {Key: models.ServiceItemParamNameContractYearName, Value: "Test Contract Year"}, + {Key: models.ServiceItemParamNameContractYearName, Value: "Base Period Year 1"}, {Key: models.ServiceItemParamNameEscalationCompounded, Value: "1.04070"}, {Key: models.ServiceItemParamNameIsPeak, Value: "false"}, {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: "1.27"}, @@ -286,7 +286,7 @@ func (suite *GHCRateEngineServiceSuite) TestPriceDomesticOrigin() { suite.Equal(basePriceCents/5, fifthPriceCents) expectedParams := services.PricingDisplayParams{ - {Key: models.ServiceItemParamNameContractYearName, Value: "Test Contract Year"}, + {Key: models.ServiceItemParamNameContractYearName, Value: "Base Period Year 1"}, {Key: models.ServiceItemParamNameEscalationCompounded, Value: "1.04070"}, {Key: models.ServiceItemParamNameIsPeak, Value: "true"}, {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: "1.46"}, diff --git a/pkg/services/ghcrateengine/pricer_helpers.go b/pkg/services/ghcrateengine/pricer_helpers.go index 450d8e7fdc6..faa802afb0b 100644 --- a/pkg/services/ghcrateengine/pricer_helpers.go +++ b/pkg/services/ghcrateengine/pricer_helpers.go @@ -3,6 +3,8 @@ package ghcrateengine import ( "fmt" "math" + "slices" + "strconv" "time" "github.com/gofrs/uuid" @@ -493,14 +495,98 @@ func escalatePriceForContractYear(appCtx appcontext.AppContext, contractID uuid. } escalatedPrice = roundToPrecision(escalatedPrice, precision) - escalatedPrice = escalatedPrice * contractYear.EscalationCompounded + + if slices.Contains(models.ContractYears, contractYear.Name) { + escalatedPrice, err = compoundEscalationFactors(appCtx, contractID, contractYear, escalatedPrice) + if err != nil { + return 0, contractYear, err + } + } else { + escalatedPrice = escalatedPrice * contractYear.EscalationCompounded + } + escalatedPrice = roundToPrecision(escalatedPrice, precision) return escalatedPrice, contractYear, nil } +func compoundEscalationFactors(appCtx appcontext.AppContext, contractID uuid.UUID, contractYear models.ReContractYear, escalatedPrice float64) (float64, error) { + // Get all contracts based on contract Id + contractYearsFromDB, err := fetchContractsByContractId(appCtx, contractID) + if err != nil { + return escalatedPrice, fmt.Errorf("could not lookup contracts by Id: %w", err) + } + + // A contract may have Option Year 3 but it is not guaranteed. Need to know if it does or not + contractsYearsFromDBMap := make(map[string]models.ReContractYear) + for _, contract := range contractYearsFromDB { + // Add re_contract_years record to map + contractsYearsFromDBMap[contract.Name] = contract + } + + // Get expectations for price escalations calculations + expectations, err := models.GetExpectedEscalationPriceContractsCount(contractYear.Name) + if err != nil { + return escalatedPrice, err + } + + // Adding contracts that are expected to be in the calculations based on the contract year to a map + contractYearsForCalculation := make(map[string]models.ReContractYear) + if expectations.ExpectedAmountOfAwardTermsForCalculation > 0 { + contractYearsForCalculation, err = addContractsForEscalationCalculation(contractYearsForCalculation, contractsYearsFromDBMap, expectations.ExpectedAmountOfAwardTermsForCalculation, models.AwardTerm) + if err != nil { + return escalatedPrice, err + } + } + if expectations.ExpectedAmountOfOptionPeriodYearsForCalculation > 0 { + contractYearsForCalculation, err = addContractsForEscalationCalculation(contractYearsForCalculation, contractsYearsFromDBMap, expectations.ExpectedAmountOfOptionPeriodYearsForCalculation, models.OptionPeriod) + if err != nil { + return escalatedPrice, err + } + } + if expectations.ExpectedAmountOfBasePeriodYearsForCalculation > 0 { + contractYearsForCalculation, err = addContractsForEscalationCalculation(contractYearsForCalculation, contractsYearsFromDBMap, expectations.ExpectedAmountOfBasePeriodYearsForCalculation, models.BasePeriodYear) + if err != nil { + return escalatedPrice, err + } + } + + // Make sure the expected amount of contracts are being used in the escalated Price calculation + if expectations.ExpectedAmountOfContractYearsForCalculation > 0 && len(contractYearsForCalculation) != expectations.ExpectedAmountOfContractYearsForCalculation { + err := apperror.NewInternalServerError("Unexpected amount of contract years being used in escalated price calculation") + return escalatedPrice, err + } + + // Multiply the escalated price by each re_contract_years record escalation factor. EscalatedPrice = EscalatedPrice * ContractEscalationFactor + var compoundedEscalatedPrice = escalatedPrice + + if expectations.ExpectedAmountOfContractYearsForCalculation > 0 { + for _, contract := range contractYearsForCalculation { + compoundedEscalatedPrice = compoundedEscalatedPrice * contract.Escalation + } + } + + return compoundedEscalatedPrice, nil +} + // roundToPrecision rounds a float64 value to the number of decimal points indicated by the precision. // TODO: Future cleanup could involve moving this function to a math/utility package with some simple tests func roundToPrecision(value float64, precision int) float64 { ratio := math.Pow(10, float64(precision)) return math.Round(value*ratio) / ratio } + +func addContractsForEscalationCalculation(contractsMap map[string]models.ReContractYear, contractsMapDB map[string]models.ReContractYear, contractsAmount int, contractName string) (map[string]models.ReContractYear, error) { + if contractsAmount > 0 { + for i := contractsAmount; i != 0; i-- { + name := fmt.Sprintf("%s %s", contractName, strconv.FormatInt(int64(i), 10)) + // If a contract that is expected to be used in the calculations is not found then return error + if _, exist := contractsMapDB[name]; exist { + contractsMap[contractsMapDB[name].Name] = contractsMapDB[name] + } else { + err := fmt.Errorf("expected contract %s not found", name) + return contractsMap, err + } + } + } + return contractsMap, nil +} diff --git a/pkg/services/ghcrateengine/pricer_helpers_test.go b/pkg/services/ghcrateengine/pricer_helpers_test.go index 80f7057931c..9adfa70f58e 100644 --- a/pkg/services/ghcrateengine/pricer_helpers_test.go +++ b/pkg/services/ghcrateengine/pricer_helpers_test.go @@ -712,3 +712,196 @@ func (suite *GHCRateEngineServiceSuite) Test_escalatePriceForContractYear() { suite.Contains(err.Error(), "could not lookup contract year") }) } + +func (suite *GHCRateEngineServiceSuite) Test_escalatedPriceForContractYearCompounded() { + + setUpContracts := func() map[string]models.ReContractYear { + escalationCompounded := 1.04071 + basePeriodYear1 := testdatagen.MakeReContractYear(suite.DB(), + testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + EscalationCompounded: escalationCompounded, + Name: models.BasePeriodYear1, + }, + }) + basePeriodYear2 := testdatagen.MakeReContractYear(suite.DB(), + testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + EscalationCompounded: escalationCompounded, + Name: models.BasePeriodYear2, + StartDate: basePeriodYear1.StartDate.AddDate(1, 0, 0), + EndDate: basePeriodYear1.EndDate.AddDate(1, 0, 0), + }, + }) + basePeriodYear3 := testdatagen.MakeReContractYear(suite.DB(), + testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + EscalationCompounded: escalationCompounded, + Name: models.BasePeriodYear3, + StartDate: basePeriodYear2.StartDate.AddDate(1, 0, 0), + EndDate: basePeriodYear2.EndDate.AddDate(1, 0, 0), + }, + }) + optionPeriod1 := testdatagen.MakeReContractYear(suite.DB(), + testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + EscalationCompounded: escalationCompounded, + Name: models.OptionPeriod1, + StartDate: basePeriodYear3.StartDate.AddDate(1, 0, 0), + EndDate: basePeriodYear3.EndDate.AddDate(1, 0, 0), + }, + }) + optionPeriod2 := testdatagen.MakeReContractYear(suite.DB(), + testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + EscalationCompounded: escalationCompounded, + Name: models.OptionPeriod2, + StartDate: optionPeriod1.StartDate.AddDate(1, 0, 0), + EndDate: optionPeriod1.EndDate.AddDate(1, 0, 0), + }, + }) + awardTerm1 := testdatagen.MakeReContractYear(suite.DB(), + testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + EscalationCompounded: escalationCompounded, + Name: models.AwardTerm1, + StartDate: optionPeriod2.StartDate.AddDate(1, 0, 0), + EndDate: optionPeriod2.EndDate.AddDate(1, 0, 0), + }, + }) + awardTerm2 := testdatagen.MakeReContractYear(suite.DB(), + testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + EscalationCompounded: escalationCompounded, + Name: models.AwardTerm2, + StartDate: awardTerm1.StartDate.AddDate(1, 0, 0), + EndDate: awardTerm1.EndDate.AddDate(1, 0, 0), + }, + }) + + optionPeriod3 := testdatagen.MakeReContractYear(suite.DB(), + testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + EscalationCompounded: escalationCompounded, + Name: models.OptionPeriod3, + StartDate: awardTerm2.StartDate.AddDate(1, 0, 0), + EndDate: awardTerm2.EndDate.AddDate(1, 0, 0), + }, + }) + + contractsYearsMap := make(map[string]models.ReContractYear) + contractsYearsMap[optionPeriod3.Name] = optionPeriod3 + contractsYearsMap[awardTerm2.Name] = awardTerm2 + contractsYearsMap[awardTerm1.Name] = awardTerm1 + contractsYearsMap[optionPeriod2.Name] = optionPeriod2 + contractsYearsMap[optionPeriod1.Name] = optionPeriod1 + contractsYearsMap[basePeriodYear3.Name] = basePeriodYear3 + contractsYearsMap[basePeriodYear2.Name] = basePeriodYear2 + contractsYearsMap[basePeriodYear1.Name] = basePeriodYear1 + return contractsYearsMap + } + + setUpContractsWithMissingContracts := func() map[string]models.ReContractYear { + escalationCompounded := 1.04071 + basePeriodYear1 := testdatagen.MakeReContractYear(suite.DB(), + testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + EscalationCompounded: escalationCompounded, + Name: models.BasePeriodYear1, + }, + }) + basePeriodYear2 := testdatagen.MakeReContractYear(suite.DB(), + testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + EscalationCompounded: escalationCompounded, + Name: models.BasePeriodYear2, + StartDate: basePeriodYear1.StartDate.AddDate(1, 0, 0), + EndDate: basePeriodYear1.EndDate.AddDate(1, 0, 0), + }, + }) + basePeriodYear3 := testdatagen.MakeReContractYear(suite.DB(), + testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + EscalationCompounded: escalationCompounded, + Name: models.BasePeriodYear3, + StartDate: basePeriodYear2.StartDate.AddDate(1, 0, 0), + EndDate: basePeriodYear2.EndDate.AddDate(1, 0, 0), + }, + }) + optionPeriod2 := testdatagen.MakeReContractYear(suite.DB(), + testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + EscalationCompounded: escalationCompounded, + Name: models.OptionPeriod2, + StartDate: basePeriodYear2.StartDate.AddDate(2, 0, 0), + EndDate: basePeriodYear2.EndDate.AddDate(2, 0, 0), + }, + }) + awardTerm1 := testdatagen.MakeReContractYear(suite.DB(), + testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + EscalationCompounded: escalationCompounded, + Name: models.AwardTerm1, + StartDate: optionPeriod2.StartDate.AddDate(1, 0, 0), + EndDate: optionPeriod2.EndDate.AddDate(1, 0, 0), + }, + }) + awardTerm2 := testdatagen.MakeReContractYear(suite.DB(), + testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + EscalationCompounded: escalationCompounded, + Name: models.AwardTerm2, + StartDate: awardTerm1.StartDate.AddDate(1, 0, 0), + EndDate: awardTerm1.EndDate.AddDate(1, 0, 0), + }, + }) + + optionPeriod3 := testdatagen.MakeReContractYear(suite.DB(), + testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + EscalationCompounded: escalationCompounded, + Name: models.OptionPeriod3, + StartDate: awardTerm2.StartDate.AddDate(1, 0, 0), + EndDate: awardTerm2.EndDate.AddDate(1, 0, 0), + }, + }) + + contractsYearsMap := make(map[string]models.ReContractYear) + contractsYearsMap[optionPeriod3.Name] = optionPeriod3 + contractsYearsMap[awardTerm2.Name] = awardTerm2 + contractsYearsMap[awardTerm1.Name] = awardTerm1 + contractsYearsMap[optionPeriod2.Name] = optionPeriod2 + contractsYearsMap[basePeriodYear3.Name] = basePeriodYear3 + contractsYearsMap[basePeriodYear2.Name] = basePeriodYear2 + contractsYearsMap[basePeriodYear1.Name] = basePeriodYear1 + return contractsYearsMap + } + + suite.Run("should correctly calculate escalated price based on the escalation factors of each contract year", func() { + contracts := setUpContracts() + + isLinehaul := false + basePrice := 5117.0 + + contract := contracts[models.OptionPeriod3] + + escalatedPrice, contractYear, err := escalatePriceForContractYear(suite.AppContextForTest(), contract.ContractID, contract.StartDate.AddDate(0, 0, 1), isLinehaul, basePrice) + + suite.Nil(err) + suite.Equal(contract.ID, contractYear.ID) + suite.Equal(5981.0, escalatedPrice) + }) + suite.Run("should error if an expected contract needed for the escalation price calculation is not found", func() { + contracts := setUpContractsWithMissingContracts() + isLinehaul := false + basePrice := 5117.0 + + contract := contracts[models.OptionPeriod3] + escalatedPrice, contractYear, err := escalatePriceForContractYear(suite.AppContextForTest(), contract.ContractID, contract.StartDate.AddDate(0, 0, 1), isLinehaul, basePrice) + + suite.Error(err) + suite.Equal("expected contract Option Period 1 not found", err.Error()) + suite.Equal(contract.ID, contractYear.ID) + suite.NotNil(escalatedPrice) + }) +} diff --git a/pkg/services/ghcrateengine/pricer_query_helpers.go b/pkg/services/ghcrateengine/pricer_query_helpers.go index e2b6f91810a..5fc88dd7d5e 100644 --- a/pkg/services/ghcrateengine/pricer_query_helpers.go +++ b/pkg/services/ghcrateengine/pricer_query_helpers.go @@ -93,6 +93,16 @@ func fetchContractYear(appCtx appcontext.AppContext, contractID uuid.UUID, date return contractYear, nil } +func fetchContractsByContractId(appCtx appcontext.AppContext, contractID uuid.UUID) (models.ReContractYears, error) { + var contracts models.ReContractYears + err := appCtx.DB().Where("contract_id = $1", contractID).All(&contracts) + if err != nil { + return models.ReContractYears{}, err + } + + return contracts, nil +} + func fetchShipmentTypePrice(appCtx appcontext.AppContext, contractCode string, serviceCode models.ReServiceCode, market models.Market) (models.ReShipmentTypePrice, error) { var shipmentTypePrice models.ReShipmentTypePrice err := appCtx.DB().Q(). diff --git a/pkg/services/ghcrateengine/pricer_query_helpers_test.go b/pkg/services/ghcrateengine/pricer_query_helpers_test.go index 87396cc0278..349f0bdeae4 100644 --- a/pkg/services/ghcrateengine/pricer_query_helpers_test.go +++ b/pkg/services/ghcrateengine/pricer_query_helpers_test.go @@ -52,7 +52,7 @@ func (suite *GHCRateEngineServiceSuite) Test_fetchDomServiceAreaPrice() { testCents := unit.Cents(353) suite.Run("golden path", func() { - suite.setupDomesticServiceAreaPrice(models.ReServiceCodeDOFSIT, testServiceArea, testIsPeakPeriod, testCents, "Test Contract Year", 1.125) + suite.setupDomesticServiceAreaPrice(models.ReServiceCodeDOFSIT, testServiceArea, testIsPeakPeriod, testCents, "Base Period Year 1", 1.125) domServiceAreaPrice, err := fetchDomServiceAreaPrice(suite.AppContextForTest(), testdatagen.DefaultContractCode, models.ReServiceCodeDOFSIT, testServiceArea, testIsPeakPeriod) suite.NoError(err) @@ -60,7 +60,7 @@ func (suite *GHCRateEngineServiceSuite) Test_fetchDomServiceAreaPrice() { }) suite.Run("no records found", func() { - suite.setupDomesticServiceAreaPrice(models.ReServiceCodeDOFSIT, testServiceArea, testIsPeakPeriod, testCents, "Test Contract Year", 1.125) + suite.setupDomesticServiceAreaPrice(models.ReServiceCodeDOFSIT, testServiceArea, testIsPeakPeriod, testCents, "Base Period Year 1", 1.125) // Look for service code DDFSIT that we haven't added _, err := fetchDomServiceAreaPrice(suite.AppContextForTest(), testdatagen.DefaultContractCode, models.ReServiceCodeDDFSIT, testServiceArea, testIsPeakPeriod) diff --git a/pkg/services/mocks/MTOServiceItemCreator.go b/pkg/services/mocks/MTOServiceItemCreator.go index ae6e7d230e7..ab7cd5f1deb 100644 --- a/pkg/services/mocks/MTOServiceItemCreator.go +++ b/pkg/services/mocks/MTOServiceItemCreator.go @@ -8,6 +8,8 @@ import ( models "github.com/transcom/mymove/pkg/models" + unit "github.com/transcom/mymove/pkg/unit" + validate "github.com/gobuffalo/validate/v3" ) @@ -55,6 +57,34 @@ func (_m *MTOServiceItemCreator) CreateMTOServiceItem(appCtx appcontext.AppConte return r0, r1, r2 } +// FindEstimatedPrice provides a mock function with given fields: appCtx, serviceItem, mtoShipment +func (_m *MTOServiceItemCreator) FindEstimatedPrice(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, mtoShipment models.MTOShipment) (unit.Cents, error) { + ret := _m.Called(appCtx, serviceItem, mtoShipment) + + if len(ret) == 0 { + panic("no return value specified for FindEstimatedPrice") + } + + var r0 unit.Cents + var r1 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, *models.MTOServiceItem, models.MTOShipment) (unit.Cents, error)); ok { + return rf(appCtx, serviceItem, mtoShipment) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, *models.MTOServiceItem, models.MTOShipment) unit.Cents); ok { + r0 = rf(appCtx, serviceItem, mtoShipment) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, *models.MTOServiceItem, models.MTOShipment) error); ok { + r1 = rf(appCtx, serviceItem, mtoShipment) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // NewMTOServiceItemCreator creates a new instance of MTOServiceItemCreator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewMTOServiceItemCreator(t interface { diff --git a/pkg/services/mocks/MTOServiceItemUpdater.go b/pkg/services/mocks/MTOServiceItemUpdater.go index ed356fd7699..bbb6bd86828 100644 --- a/pkg/services/mocks/MTOServiceItemUpdater.go +++ b/pkg/services/mocks/MTOServiceItemUpdater.go @@ -138,6 +138,36 @@ func (_m *MTOServiceItemUpdater) UpdateMTOServiceItemBasic(appCtx appcontext.App return r0, r1 } +// UpdateMTOServiceItemPricingEstimate provides a mock function with given fields: appCtx, serviceItem, shipment, eTag +func (_m *MTOServiceItemUpdater) UpdateMTOServiceItemPricingEstimate(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, shipment models.MTOShipment, eTag string) (*models.MTOServiceItem, error) { + ret := _m.Called(appCtx, serviceItem, shipment, eTag) + + if len(ret) == 0 { + panic("no return value specified for UpdateMTOServiceItemPricingEstimate") + } + + var r0 *models.MTOServiceItem + var r1 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, *models.MTOServiceItem, models.MTOShipment, string) (*models.MTOServiceItem, error)); ok { + return rf(appCtx, serviceItem, shipment, eTag) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, *models.MTOServiceItem, models.MTOShipment, string) *models.MTOServiceItem); ok { + r0 = rf(appCtx, serviceItem, shipment, eTag) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.MTOServiceItem) + } + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, *models.MTOServiceItem, models.MTOShipment, string) error); ok { + r1 = rf(appCtx, serviceItem, shipment, eTag) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // UpdateMTOServiceItemPrime provides a mock function with given fields: appCtx, serviceItem, planner, shipment, eTag func (_m *MTOServiceItemUpdater) UpdateMTOServiceItemPrime(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, planner route.Planner, shipment models.MTOShipment, eTag string) (*models.MTOServiceItem, error) { ret := _m.Called(appCtx, serviceItem, planner, shipment, eTag) diff --git a/pkg/services/mocks/OfficeUserFetcherPop.go b/pkg/services/mocks/OfficeUserFetcherPop.go index 6a5a414d7f0..24c35ca7142 100644 --- a/pkg/services/mocks/OfficeUserFetcherPop.go +++ b/pkg/services/mocks/OfficeUserFetcherPop.go @@ -46,6 +46,34 @@ func (_m *OfficeUserFetcherPop) FetchOfficeUserByID(appCtx appcontext.AppContext return r0, r1 } +// FetchOfficeUserByIDWithTransportationOfficeAssignments provides a mock function with given fields: appCtx, id +func (_m *OfficeUserFetcherPop) FetchOfficeUserByIDWithTransportationOfficeAssignments(appCtx appcontext.AppContext, id uuid.UUID) (models.OfficeUser, error) { + ret := _m.Called(appCtx, id) + + if len(ret) == 0 { + panic("no return value specified for FetchOfficeUserByIDWithTransportationOfficeAssignments") + } + + var r0 models.OfficeUser + var r1 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, uuid.UUID) (models.OfficeUser, error)); ok { + return rf(appCtx, id) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, uuid.UUID) models.OfficeUser); ok { + r0 = rf(appCtx, id) + } else { + r0 = ret.Get(0).(models.OfficeUser) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, uuid.UUID) error); ok { + r1 = rf(appCtx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // FetchOfficeUsersByRoleAndOffice provides a mock function with given fields: appCtx, role, officeID func (_m *OfficeUserFetcherPop) FetchOfficeUsersByRoleAndOffice(appCtx appcontext.AppContext, role roles.RoleType, officeID uuid.UUID) ([]models.OfficeUser, error) { ret := _m.Called(appCtx, role, officeID) @@ -76,6 +104,36 @@ func (_m *OfficeUserFetcherPop) FetchOfficeUsersByRoleAndOffice(appCtx appcontex return r0, r1 } +// FetchSafetyMoveOfficeUsersByRoleAndOffice provides a mock function with given fields: appCtx, role, officeID +func (_m *OfficeUserFetcherPop) FetchSafetyMoveOfficeUsersByRoleAndOffice(appCtx appcontext.AppContext, role roles.RoleType, officeID uuid.UUID) ([]models.OfficeUser, error) { + ret := _m.Called(appCtx, role, officeID) + + if len(ret) == 0 { + panic("no return value specified for FetchSafetyMoveOfficeUsersByRoleAndOffice") + } + + var r0 []models.OfficeUser + var r1 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, roles.RoleType, uuid.UUID) ([]models.OfficeUser, error)); ok { + return rf(appCtx, role, officeID) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, roles.RoleType, uuid.UUID) []models.OfficeUser); ok { + r0 = rf(appCtx, role, officeID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]models.OfficeUser) + } + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, roles.RoleType, uuid.UUID) error); ok { + r1 = rf(appCtx, role, officeID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // NewOfficeUserFetcherPop creates a new instance of OfficeUserFetcherPop. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewOfficeUserFetcherPop(t interface { diff --git a/pkg/services/mocks/OfficeUserUpdater.go b/pkg/services/mocks/OfficeUserUpdater.go index bc236493d27..5a884f836a2 100644 --- a/pkg/services/mocks/OfficeUserUpdater.go +++ b/pkg/services/mocks/OfficeUserUpdater.go @@ -20,9 +20,9 @@ type OfficeUserUpdater struct { mock.Mock } -// UpdateOfficeUser provides a mock function with given fields: appCtx, id, payload -func (_m *OfficeUserUpdater) UpdateOfficeUser(appCtx appcontext.AppContext, id uuid.UUID, payload *adminmessages.OfficeUserUpdate) (*models.OfficeUser, *validate.Errors, error) { - ret := _m.Called(appCtx, id, payload) +// UpdateOfficeUser provides a mock function with given fields: appCtx, id, payload, primaryTransportationOfficeId +func (_m *OfficeUserUpdater) UpdateOfficeUser(appCtx appcontext.AppContext, id uuid.UUID, payload *adminmessages.OfficeUserUpdate, primaryTransportationOfficeId uuid.UUID) (*models.OfficeUser, *validate.Errors, error) { + ret := _m.Called(appCtx, id, payload, primaryTransportationOfficeId) if len(ret) == 0 { panic("no return value specified for UpdateOfficeUser") @@ -31,27 +31,27 @@ func (_m *OfficeUserUpdater) UpdateOfficeUser(appCtx appcontext.AppContext, id u var r0 *models.OfficeUser var r1 *validate.Errors var r2 error - if rf, ok := ret.Get(0).(func(appcontext.AppContext, uuid.UUID, *adminmessages.OfficeUserUpdate) (*models.OfficeUser, *validate.Errors, error)); ok { - return rf(appCtx, id, payload) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, uuid.UUID, *adminmessages.OfficeUserUpdate, uuid.UUID) (*models.OfficeUser, *validate.Errors, error)); ok { + return rf(appCtx, id, payload, primaryTransportationOfficeId) } - if rf, ok := ret.Get(0).(func(appcontext.AppContext, uuid.UUID, *adminmessages.OfficeUserUpdate) *models.OfficeUser); ok { - r0 = rf(appCtx, id, payload) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, uuid.UUID, *adminmessages.OfficeUserUpdate, uuid.UUID) *models.OfficeUser); ok { + r0 = rf(appCtx, id, payload, primaryTransportationOfficeId) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*models.OfficeUser) } } - if rf, ok := ret.Get(1).(func(appcontext.AppContext, uuid.UUID, *adminmessages.OfficeUserUpdate) *validate.Errors); ok { - r1 = rf(appCtx, id, payload) + if rf, ok := ret.Get(1).(func(appcontext.AppContext, uuid.UUID, *adminmessages.OfficeUserUpdate, uuid.UUID) *validate.Errors); ok { + r1 = rf(appCtx, id, payload, primaryTransportationOfficeId) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*validate.Errors) } } - if rf, ok := ret.Get(2).(func(appcontext.AppContext, uuid.UUID, *adminmessages.OfficeUserUpdate) error); ok { - r2 = rf(appCtx, id, payload) + if rf, ok := ret.Get(2).(func(appcontext.AppContext, uuid.UUID, *adminmessages.OfficeUserUpdate, uuid.UUID) error); ok { + r2 = rf(appCtx, id, payload, primaryTransportationOfficeId) } else { r2 = ret.Error(2) } diff --git a/pkg/services/mocks/RequestedOfficeUserFetcherPop.go b/pkg/services/mocks/RequestedOfficeUserFetcherPop.go new file mode 100644 index 00000000000..1b1ac5b3db0 --- /dev/null +++ b/pkg/services/mocks/RequestedOfficeUserFetcherPop.go @@ -0,0 +1,59 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + appcontext "github.com/transcom/mymove/pkg/appcontext" + + models "github.com/transcom/mymove/pkg/models" + + uuid "github.com/gofrs/uuid" +) + +// RequestedOfficeUserFetcherPop is an autogenerated mock type for the RequestedOfficeUserFetcherPop type +type RequestedOfficeUserFetcherPop struct { + mock.Mock +} + +// FetchRequestedOfficeUserByID provides a mock function with given fields: appCtx, id +func (_m *RequestedOfficeUserFetcherPop) FetchRequestedOfficeUserByID(appCtx appcontext.AppContext, id uuid.UUID) (models.OfficeUser, error) { + ret := _m.Called(appCtx, id) + + if len(ret) == 0 { + panic("no return value specified for FetchRequestedOfficeUserByID") + } + + var r0 models.OfficeUser + var r1 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, uuid.UUID) (models.OfficeUser, error)); ok { + return rf(appCtx, id) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, uuid.UUID) models.OfficeUser); ok { + r0 = rf(appCtx, id) + } else { + r0 = ret.Get(0).(models.OfficeUser) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, uuid.UUID) error); ok { + r1 = rf(appCtx, id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewRequestedOfficeUserFetcherPop creates a new instance of RequestedOfficeUserFetcherPop. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewRequestedOfficeUserFetcherPop(t interface { + mock.TestingT + Cleanup(func()) +}) *RequestedOfficeUserFetcherPop { + mock := &RequestedOfficeUserFetcherPop{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/services/mocks/ServiceItemListFetcher.go b/pkg/services/mocks/ServiceItemListFetcher.go new file mode 100644 index 00000000000..d12da0c35f2 --- /dev/null +++ b/pkg/services/mocks/ServiceItemListFetcher.go @@ -0,0 +1,59 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + appcontext "github.com/transcom/mymove/pkg/appcontext" + + models "github.com/transcom/mymove/pkg/models" +) + +// ServiceItemListFetcher is an autogenerated mock type for the ServiceItemListFetcher type +type ServiceItemListFetcher struct { + mock.Mock +} + +// FetchServiceItemList provides a mock function with given fields: appCtx +func (_m *ServiceItemListFetcher) FetchServiceItemList(appCtx appcontext.AppContext) (*models.ReServiceItems, error) { + ret := _m.Called(appCtx) + + if len(ret) == 0 { + panic("no return value specified for FetchServiceItemList") + } + + var r0 *models.ReServiceItems + var r1 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext) (*models.ReServiceItems, error)); ok { + return rf(appCtx) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext) *models.ReServiceItems); ok { + r0 = rf(appCtx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.ReServiceItems) + } + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext) error); ok { + r1 = rf(appCtx) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewServiceItemListFetcher creates a new instance of ServiceItemListFetcher. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewServiceItemListFetcher(t interface { + mock.TestingT + Cleanup(func()) +}) *ServiceItemListFetcher { + mock := &ServiceItemListFetcher{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/services/mocks/ShipmentRateAreaFinder.go b/pkg/services/mocks/ShipmentRateAreaFinder.go new file mode 100644 index 00000000000..663a74a3cb8 --- /dev/null +++ b/pkg/services/mocks/ShipmentRateAreaFinder.go @@ -0,0 +1,61 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + appcontext "github.com/transcom/mymove/pkg/appcontext" + + models "github.com/transcom/mymove/pkg/models" + + services "github.com/transcom/mymove/pkg/services" +) + +// ShipmentRateAreaFinder is an autogenerated mock type for the ShipmentRateAreaFinder type +type ShipmentRateAreaFinder struct { + mock.Mock +} + +// GetPrimeMoveShipmentOconusRateArea provides a mock function with given fields: appCtx, move +func (_m *ShipmentRateAreaFinder) GetPrimeMoveShipmentOconusRateArea(appCtx appcontext.AppContext, move models.Move) (*[]services.ShipmentPostalCodeRateArea, error) { + ret := _m.Called(appCtx, move) + + if len(ret) == 0 { + panic("no return value specified for GetPrimeMoveShipmentOconusRateArea") + } + + var r0 *[]services.ShipmentPostalCodeRateArea + var r1 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.Move) (*[]services.ShipmentPostalCodeRateArea, error)); ok { + return rf(appCtx, move) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.Move) *[]services.ShipmentPostalCodeRateArea); ok { + r0 = rf(appCtx, move) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*[]services.ShipmentPostalCodeRateArea) + } + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, models.Move) error); ok { + r1 = rf(appCtx, move) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewShipmentRateAreaFinder creates a new instance of ShipmentRateAreaFinder. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewShipmentRateAreaFinder(t interface { + mock.TestingT + Cleanup(func()) +}) *ShipmentRateAreaFinder { + mock := &ShipmentRateAreaFinder{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/services/mocks/TransportaionOfficeAssignmentFetcher.go b/pkg/services/mocks/TransportaionOfficeAssignmentFetcher.go new file mode 100644 index 00000000000..4a2bcf95e49 --- /dev/null +++ b/pkg/services/mocks/TransportaionOfficeAssignmentFetcher.go @@ -0,0 +1,61 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + appcontext "github.com/transcom/mymove/pkg/appcontext" + + models "github.com/transcom/mymove/pkg/models" + + uuid "github.com/gofrs/uuid" +) + +// TransportaionOfficeAssignmentFetcher is an autogenerated mock type for the TransportaionOfficeAssignmentFetcher type +type TransportaionOfficeAssignmentFetcher struct { + mock.Mock +} + +// FetchTransportaionOfficeAssignmentsByOfficeUserID provides a mock function with given fields: appCtx, officeUserId +func (_m *TransportaionOfficeAssignmentFetcher) FetchTransportaionOfficeAssignmentsByOfficeUserID(appCtx appcontext.AppContext, officeUserId uuid.UUID) (models.TransportationOfficeAssignments, error) { + ret := _m.Called(appCtx, officeUserId) + + if len(ret) == 0 { + panic("no return value specified for FetchTransportaionOfficeAssignmentsByOfficeUserID") + } + + var r0 models.TransportationOfficeAssignments + var r1 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, uuid.UUID) (models.TransportationOfficeAssignments, error)); ok { + return rf(appCtx, officeUserId) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, uuid.UUID) models.TransportationOfficeAssignments); ok { + r0 = rf(appCtx, officeUserId) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(models.TransportationOfficeAssignments) + } + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, uuid.UUID) error); ok { + r1 = rf(appCtx, officeUserId) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewTransportaionOfficeAssignmentFetcher creates a new instance of TransportaionOfficeAssignmentFetcher. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewTransportaionOfficeAssignmentFetcher(t interface { + mock.TestingT + Cleanup(func()) +}) *TransportaionOfficeAssignmentFetcher { + mock := &TransportaionOfficeAssignmentFetcher{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/services/mocks/TransportaionOfficeAssignmentUpdater.go b/pkg/services/mocks/TransportaionOfficeAssignmentUpdater.go new file mode 100644 index 00000000000..2a0995c7fe7 --- /dev/null +++ b/pkg/services/mocks/TransportaionOfficeAssignmentUpdater.go @@ -0,0 +1,61 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + appcontext "github.com/transcom/mymove/pkg/appcontext" + + models "github.com/transcom/mymove/pkg/models" + + uuid "github.com/gofrs/uuid" +) + +// TransportaionOfficeAssignmentUpdater is an autogenerated mock type for the TransportaionOfficeAssignmentUpdater type +type TransportaionOfficeAssignmentUpdater struct { + mock.Mock +} + +// UpdateTransportaionOfficeAssignments provides a mock function with given fields: appCtx, officeUserId, transportationOfficeAssignments +func (_m *TransportaionOfficeAssignmentUpdater) UpdateTransportaionOfficeAssignments(appCtx appcontext.AppContext, officeUserId uuid.UUID, transportationOfficeAssignments models.TransportationOfficeAssignments) (models.TransportationOfficeAssignments, error) { + ret := _m.Called(appCtx, officeUserId, transportationOfficeAssignments) + + if len(ret) == 0 { + panic("no return value specified for UpdateTransportaionOfficeAssignments") + } + + var r0 models.TransportationOfficeAssignments + var r1 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, uuid.UUID, models.TransportationOfficeAssignments) (models.TransportationOfficeAssignments, error)); ok { + return rf(appCtx, officeUserId, transportationOfficeAssignments) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, uuid.UUID, models.TransportationOfficeAssignments) models.TransportationOfficeAssignments); ok { + r0 = rf(appCtx, officeUserId, transportationOfficeAssignments) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(models.TransportationOfficeAssignments) + } + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, uuid.UUID, models.TransportationOfficeAssignments) error); ok { + r1 = rf(appCtx, officeUserId, transportationOfficeAssignments) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewTransportaionOfficeAssignmentUpdater creates a new instance of TransportaionOfficeAssignmentUpdater. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewTransportaionOfficeAssignmentUpdater(t interface { + mock.TestingT + Cleanup(func()) +}) *TransportaionOfficeAssignmentUpdater { + mock := &TransportaionOfficeAssignmentUpdater{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/services/move/move_router.go b/pkg/services/move/move_router.go index 245f3602027..c44f3756de0 100644 --- a/pkg/services/move/move_router.go +++ b/pkg/services/move/move_router.go @@ -163,6 +163,10 @@ func (router moveRouter) needsServiceCounseling(appCtx appcontext.AppContext, mo return false, nil } + if move.IsPPMOnly() { + return true, nil + } + return originDutyLocation.ProvidesServicesCounseling, nil } diff --git a/pkg/services/move/move_router_test.go b/pkg/services/move/move_router_test.go index b0d0eb87aee..7b1e21f0d2f 100644 --- a/pkg/services/move/move_router_test.go +++ b/pkg/services/move/move_router_test.go @@ -283,6 +283,69 @@ func (suite *MoveServiceSuite) TestMoveSubmission() { }, }, nil) + shipment := factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusDraft, + ShipmentType: models.MTOShipmentTypeHHG, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + move.MTOShipments = models.MTOShipments{shipment} + + newSignedCertification := factory.BuildSignedCertification(nil, []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + }, nil) + err := moveRouter.Submit(suite.AppContextForTest(), &move, &newSignedCertification) + suite.NoError(err) + err = suite.DB().Where("move_id = $1", move.ID).First(&newSignedCertification) + suite.NoError(err) + suite.NotNil(newSignedCertification) + + err = suite.DB().Find(&move, move.ID) + suite.NoError(err) + suite.Equal(tt.moveStatus, move.Status) + }) + } + }) + + suite.Run("PPM moves are routed correctly and SignedCertification is created", func() { + // Under test: MoveRouter.Submit (Full PPM should always route to service counselor, never to office user) + // Set up: Create moves and SignedCertification + // Expected outcome: signed cert is created + // Expected outcome: Move status is set to needs service counseling for both true and false on origin providing service counseling + tests := []struct { + desc string + ProvidesServicesCounseling bool + moveStatus models.MoveStatus + }{ + {"Routes to Service Counseling", true, models.MoveStatusNeedsServiceCounseling}, + {"Routes to Service Counseling", false, models.MoveStatusNeedsServiceCounseling}, + } + for _, tt := range tests { + suite.Run(tt.desc, func() { + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + ProvidesServicesCounseling: tt.ProvidesServicesCounseling, + }, + Type: &factory.DutyLocations.OriginDutyLocation, + }, + { + Model: models.Move{ + Status: models.MoveStatusDRAFT, + }, + }, + }, nil) + shipment := factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{ { Model: models.MTOShipment{ @@ -408,7 +471,7 @@ func (suite *MoveServiceSuite) TestMoveSubmission() { err := moveRouter.Submit(suite.AppContextForTest(), &move, &newSignedCertification) suite.NoError(err) - suite.Equal(models.MoveStatusSUBMITTED, move.Status, "expected Submitted") + suite.Equal(models.MoveStatusNeedsServiceCounseling, move.Status, "expected Needs Service Counseling") suite.Equal(models.MTOShipmentStatusSubmitted, move.MTOShipments[0].Status, "expected Submitted") suite.Equal(models.PPMShipmentStatusSubmitted, move.MTOShipments[0].PPMShipment.Status, "expected Submitted") }) diff --git a/pkg/services/move/move_weights.go b/pkg/services/move/move_weights.go index d29dc0c7df0..cf75d604652 100644 --- a/pkg/services/move/move_weights.go +++ b/pkg/services/move/move_weights.go @@ -98,7 +98,7 @@ 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) + totalWeightAllowance := models.GetWeightAllotment(*move.Orders.Grade, move.Orders.OrdersType) weight := totalWeightAllowance.TotalWeightSelf if *move.Orders.Entitlement.DependentsAuthorized { @@ -175,7 +175,7 @@ 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) + totalWeightAllowance := models.GetWeightAllotment(*move.Orders.Grade, move.Orders.OrdersType) weight := totalWeightAllowance.TotalWeightSelf if *move.Orders.Entitlement.DependentsAuthorized { diff --git a/pkg/services/move_history/move_history_fetcher_test.go b/pkg/services/move_history/move_history_fetcher_test.go index f84af138e33..3706ec214eb 100644 --- a/pkg/services/move_history/move_history_fetcher_test.go +++ b/pkg/services/move_history/move_history_fetcher_test.go @@ -374,7 +374,7 @@ func (suite *MoveHistoryServiceSuite) TestMoveHistoryFetcherScenarios() { mock.Anything, mock.Anything, ).Return(400, nil) - updater := mtoserviceitem.NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator) + updater := mtoserviceitem.NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer(), ghcrateengine.NewDomesticDestinationSITDeliveryPricer(), ghcrateengine.NewDomesticOriginSITFuelSurchargePricer()) move := factory.BuildApprovalsRequestedMove(suite.DB(), nil, nil) serviceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ { diff --git a/pkg/services/mto_service_item.go b/pkg/services/mto_service_item.go index 25926ae3fca..0931fcbefc7 100644 --- a/pkg/services/mto_service_item.go +++ b/pkg/services/mto_service_item.go @@ -9,6 +9,7 @@ import ( "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/route" + "github.com/transcom/mymove/pkg/unit" ) // MTOServiceItemFetcher is the exported interface for fetching a mto service item @@ -23,6 +24,7 @@ type MTOServiceItemFetcher interface { //go:generate mockery --name MTOServiceItemCreator type MTOServiceItemCreator interface { CreateMTOServiceItem(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem) (*models.MTOServiceItems, *validate.Errors, error) + FindEstimatedPrice(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, mtoShipment models.MTOShipment) (unit.Cents, error) } // MTOServiceItemUpdater is the exported interface for updating an mto service item @@ -32,6 +34,7 @@ type MTOServiceItemUpdater interface { ApproveOrRejectServiceItem(appCtx appcontext.AppContext, mtoServiceItemID uuid.UUID, status models.MTOServiceItemStatus, rejectionReason *string, eTag string) (*models.MTOServiceItem, error) UpdateMTOServiceItem(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, eTag string, validator string) (*models.MTOServiceItem, error) UpdateMTOServiceItemBasic(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, eTag string) (*models.MTOServiceItem, error) + UpdateMTOServiceItemPricingEstimate(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, shipment models.MTOShipment, eTag string) (*models.MTOServiceItem, error) UpdateMTOServiceItemPrime(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, planner route.Planner, shipment models.MTOShipment, eTag string) (*models.MTOServiceItem, error) ConvertItemToCustomerExpense(appCtx appcontext.AppContext, shipment *models.MTOShipment, customerExpenseReason *string, convertToCustomerExpense bool) (*models.MTOServiceItem, error) } diff --git a/pkg/services/mto_service_item/mto_service_item_creator.go b/pkg/services/mto_service_item/mto_service_item_creator.go index 2ab0802badd..c4a53941d0f 100644 --- a/pkg/services/mto_service_item/mto_service_item_creator.go +++ b/pkg/services/mto_service_item/mto_service_item_creator.go @@ -39,7 +39,7 @@ type mtoServiceItemCreator struct { fuelSurchargePricer services.FuelSurchargePricer } -func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, mtoShipment models.MTOShipment) (unit.Cents, error) { +func (o *mtoServiceItemCreator) FindEstimatedPrice(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, mtoShipment models.MTOShipment) (unit.Cents, error) { if serviceItem.ReService.Code == models.ReServiceCodeDOP || serviceItem.ReService.Code == models.ReServiceCodeDPK || serviceItem.ReService.Code == models.ReServiceCodeDDP || @@ -55,7 +55,11 @@ func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, requestedPickupDate := *mtoShipment.RequestedPickupDate currTime := time.Now() var distance int - primeEstimatedWeight := *mtoShipment.PrimeEstimatedWeight + primeEstimatedWeight := mtoShipment.PrimeEstimatedWeight + if mtoShipment.ShipmentType == models.MTOShipmentTypeHHGOutOfNTSDom { + newWeight := int(primeEstimatedWeight.Float64() * 1.1) + primeEstimatedWeight = (*unit.Pound)(&newWeight) + } contractCode, err := FetchContractCode(appCtx, currTime) if err != nil { @@ -74,7 +78,7 @@ func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, return 0, err } - price, _, err = o.originPricer.Price(appCtx, contractCode, requestedPickupDate, *mtoShipment.PrimeEstimatedWeight, domesticServiceArea.ServiceArea, isPPM) + price, _, err = o.originPricer.Price(appCtx, contractCode, requestedPickupDate, *primeEstimatedWeight, domesticServiceArea.ServiceArea, isPPM) if err != nil { return 0, err } @@ -87,7 +91,7 @@ func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, servicesScheduleOrigin := domesticServiceArea.ServicesSchedule - price, _, err = o.packPricer.Price(appCtx, contractCode, requestedPickupDate, *mtoShipment.PrimeEstimatedWeight, servicesScheduleOrigin, isPPM) + price, _, err = o.packPricer.Price(appCtx, contractCode, requestedPickupDate, *primeEstimatedWeight, servicesScheduleOrigin, isPPM) if err != nil { return 0, err } @@ -102,7 +106,7 @@ func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, } } - price, _, err = o.destinationPricer.Price(appCtx, contractCode, requestedPickupDate, *mtoShipment.PrimeEstimatedWeight, domesticServiceArea.ServiceArea, isPPM) + price, _, err = o.destinationPricer.Price(appCtx, contractCode, requestedPickupDate, *primeEstimatedWeight, domesticServiceArea.ServiceArea, isPPM) if err != nil { return 0, err } @@ -115,7 +119,7 @@ func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, serviceScheduleDestination := domesticServiceArea.ServicesSchedule - price, _, err = o.unpackPricer.Price(appCtx, contractCode, requestedPickupDate, *mtoShipment.PrimeEstimatedWeight, serviceScheduleDestination, isPPM) + price, _, err = o.unpackPricer.Price(appCtx, contractCode, requestedPickupDate, *primeEstimatedWeight, serviceScheduleDestination, isPPM) if err != nil { return 0, err } @@ -133,7 +137,7 @@ func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, return 0, err } } - price, _, err = o.linehaulPricer.Price(appCtx, contractCode, requestedPickupDate, unit.Miles(distance), *mtoShipment.PrimeEstimatedWeight, domesticServiceArea.ServiceArea, isPPM) + price, _, err = o.linehaulPricer.Price(appCtx, contractCode, requestedPickupDate, unit.Miles(distance), *primeEstimatedWeight, domesticServiceArea.ServiceArea, isPPM) if err != nil { return 0, err } @@ -149,7 +153,7 @@ func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, return 0, err } } - price, _, err = o.shorthaulPricer.Price(appCtx, contractCode, requestedPickupDate, unit.Miles(distance), *mtoShipment.PrimeEstimatedWeight, domesticServiceArea.ServiceArea) + price, _, err = o.shorthaulPricer.Price(appCtx, contractCode, requestedPickupDate, unit.Miles(distance), *primeEstimatedWeight, domesticServiceArea.ServiceArea) if err != nil { return 0, err } @@ -173,7 +177,7 @@ func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, } } - fscWeightBasedDistanceMultiplier, err := LookupFSCWeightBasedDistanceMultiplier(appCtx, primeEstimatedWeight) + fscWeightBasedDistanceMultiplier, err := LookupFSCWeightBasedDistanceMultiplier(appCtx, *primeEstimatedWeight) if err != nil { return 0, err } @@ -185,7 +189,7 @@ func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, if err != nil { return 0, err } - price, _, err = o.fuelSurchargePricer.Price(appCtx, pickupDateForFSC, unit.Miles(distance), primeEstimatedWeight, fscWeightBasedDistanceMultiplierFloat, eiaFuelPrice, isPPM) + price, _, err = o.fuelSurchargePricer.Price(appCtx, pickupDateForFSC, unit.Miles(distance), *primeEstimatedWeight, fscWeightBasedDistanceMultiplierFloat, eiaFuelPrice, isPPM) if err != nil { return 0, err } @@ -603,10 +607,8 @@ func (o *mtoServiceItemCreator) CreateMTOServiceItem(appCtx appcontext.AppContex // if estimated weight for shipment provided by the prime, calculate the estimated prices for // DLH, DPK, DOP, DDP, DUPK - - // NTS-release requested pickup dates are for handle out, their pricing is handled differently as their locations are based on storage facilities, not pickup locations - if mtoShipment.PrimeEstimatedWeight != nil && mtoShipment.RequestedPickupDate != nil && mtoShipment.ShipmentType != models.MTOShipmentTypeHHGOutOfNTSDom { - serviceItemEstimatedPrice, err := o.findEstimatedPrice(appCtx, serviceItem, mtoShipment) + if mtoShipment.PrimeEstimatedWeight != nil && mtoShipment.RequestedPickupDate != nil { + serviceItemEstimatedPrice, err := o.FindEstimatedPrice(appCtx, serviceItem, mtoShipment) if serviceItemEstimatedPrice != 0 && err == nil { serviceItem.PricingEstimate = &serviceItemEstimatedPrice } diff --git a/pkg/services/mto_service_item/mto_service_item_creator_test.go b/pkg/services/mto_service_item/mto_service_item_creator_test.go index e9799db0278..3a6e7030c19 100644 --- a/pkg/services/mto_service_item/mto_service_item_creator_test.go +++ b/pkg/services/mto_service_item/mto_service_item_creator_test.go @@ -1818,3 +1818,585 @@ func (suite *MTOServiceItemServiceSuite) TestCreateDestSITServiceItem() { suite.Contains(invalidInputError.ValidationErrors.Keys(), "reServiceCode") }) } + +func (suite *MTOServiceItemServiceSuite) TestPriceEstimator() { + suite.Run("Calcuating price estimated on creation for HHG ", func() { + setupTestData := func() models.MTOShipment { + // Set up data to use for all Origin SIT Service Item tests + + move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + estimatedPrimeWeight := unit.Pound(6000) + pickupDate := time.Date(2024, time.July, 31, 12, 0, 0, 0, time.UTC) + pickupAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress2}) + deliveryAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress3}) + + mtoShipment := factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: pickupAddress, + LinkOnly: true, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: deliveryAddress, + LinkOnly: true, + Type: &factory.Addresses.DeliveryAddress, + }, + { + Model: models.MTOShipment{ + PrimeEstimatedWeight: &estimatedPrimeWeight, + RequestedPickupDate: &pickupDate, + }, + }, + }, nil) + + return mtoShipment + } + + reServiceCodeDOP := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDOP) + reServiceCodeDPK := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDPK) + reServiceCodeDDP := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDDP) + reServiceCodeDUPK := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDUPK) + reServiceCodeDLH := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDLH) + reServiceCodeDSH := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDSH) + reServiceCodeFSC := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeFSC) + + startDate := time.Now().AddDate(-1, 0, 0) + endDate := startDate.AddDate(1, 1, 1) + sitEntryDate := time.Date(2020, time.October, 24, 0, 0, 0, 0, time.UTC) + sitPostalCode := "99999" + reason := "lorem ipsum" + + contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + contractYear := testdatagen.MakeReContractYear(suite.DB(), + testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + Name: "Test Contract Year", + EscalationCompounded: 1.125, + StartDate: startDate, + EndDate: endDate, + }, + }) + + serviceArea := testdatagen.MakeReDomesticServiceArea(suite.DB(), + testdatagen.Assertions{ + ReDomesticServiceArea: models.ReDomesticServiceArea{ + Contract: contractYear.Contract, + ServiceArea: "945", + ServicesSchedule: 1, + }, + }) + + serviceAreaDest := testdatagen.MakeReDomesticServiceArea(suite.DB(), + testdatagen.Assertions{ + ReDomesticServiceArea: models.ReDomesticServiceArea{ + Contract: contractYear.Contract, + ServiceArea: "503", + ServicesSchedule: 1, + }, + }) + + serviceAreaPriceDOP := models.ReDomesticServiceAreaPrice{ + ContractID: contractYear.Contract.ID, + ServiceID: reServiceCodeDOP.ID, + IsPeakPeriod: true, + DomesticServiceAreaID: serviceArea.ID, + PriceCents: unit.Cents(1234), + } + + serviceAreaPriceDPK := models.ReDomesticOtherPrice{ + ContractID: contractYear.Contract.ID, + ServiceID: reServiceCodeDPK.ID, + IsPeakPeriod: true, + Schedule: 1, + PriceCents: unit.Cents(121), + } + + serviceAreaPriceDDP := models.ReDomesticServiceAreaPrice{ + ContractID: contractYear.Contract.ID, + ServiceID: reServiceCodeDDP.ID, + IsPeakPeriod: true, + DomesticServiceAreaID: serviceAreaDest.ID, + PriceCents: unit.Cents(482), + } + + serviceAreaPriceDUPK := models.ReDomesticOtherPrice{ + ContractID: contractYear.Contract.ID, + ServiceID: reServiceCodeDUPK.ID, + IsPeakPeriod: true, + Schedule: 1, + PriceCents: unit.Cents(945), + } + + serviceAreaPriceDLH := models.ReDomesticLinehaulPrice{ + ContractID: contractYear.Contract.ID, + WeightLower: 500, + WeightUpper: 10000, + MilesLower: 1, + MilesUpper: 10000, + IsPeakPeriod: true, + DomesticServiceAreaID: serviceArea.ID, + PriceMillicents: unit.Millicents(482), + } + + serviceAreaPriceDSH := models.ReDomesticServiceAreaPrice{ + ContractID: contractYear.Contract.ID, + ServiceID: reServiceCodeDSH.ID, + IsPeakPeriod: true, + DomesticServiceAreaID: serviceArea.ID, + PriceCents: unit.Cents(999), + } + + testdatagen.MakeGHCDieselFuelPrice(suite.DB(), testdatagen.Assertions{ + GHCDieselFuelPrice: models.GHCDieselFuelPrice{ + FuelPriceInMillicents: unit.Millicents(281400), + PublicationDate: time.Date(2020, time.March, 9, 0, 0, 0, 0, time.UTC), + EffectiveDate: time.Date(2020, time.March, 10, 0, 0, 0, 0, time.UTC), + EndDate: time.Date(2025, time.March, 17, 0, 0, 0, 0, time.UTC), + }, + }) + + suite.MustSave(&serviceAreaPriceDOP) + suite.MustSave(&serviceAreaPriceDPK) + suite.MustSave(&serviceAreaPriceDDP) + suite.MustSave(&serviceAreaPriceDUPK) + suite.MustSave(&serviceAreaPriceDLH) + suite.MustSave(&serviceAreaPriceDSH) + + testdatagen.MakeReZip3(suite.DB(), testdatagen.Assertions{ + ReZip3: models.ReZip3{ + Contract: contract, + ContractID: contract.ID, + DomesticServiceArea: serviceArea, + Zip3: "945", + }, + }) + + testdatagen.MakeReZip3(suite.DB(), testdatagen.Assertions{ + ReZip3: models.ReZip3{ + Contract: contract, + ContractID: contract.ID, + DomesticServiceArea: serviceAreaDest, + Zip3: "503", + }, + }) + + shipment := setupTestData() + actualPickupAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress2}) + serviceItemDOP := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDOP, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemDPK := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDPK, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemDDP := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDDP, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemDUPK := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDUPK, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemDLH := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDLH, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemDSH := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDSH, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemFSC := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeFSC, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + builder := query.NewQueryBuilder() + moveRouter := moverouter.NewMoveRouter() + planner := &mocks.Planner{} + planner.On("ZipTransitDistance", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + ).Return(400, nil) + creator := NewMTOServiceItemCreator(planner, builder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) + + dopEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDOP, shipment) + suite.Equal(unit.Cents(83280), dopEstimatedPriceInCents) + + dpkEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDPK, shipment) + suite.Equal(unit.Cents(8160), dpkEstimatedPriceInCents) + + ddpEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDDP, shipment) + suite.Equal(unit.Cents(32520), ddpEstimatedPriceInCents) + + dupkEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDUPK, shipment) + suite.Equal(unit.Cents(63780), dupkEstimatedPriceInCents) + + dlhEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDLH, shipment) + suite.Equal(unit.Cents(14400), dlhEstimatedPriceInCents) + + dshEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDSH, shipment) + suite.Equal(unit.Cents(26976000), dshEstimatedPriceInCents) + + fscEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemFSC, shipment) + suite.Equal(unit.Cents(786), fscEstimatedPriceInCents) + }) + + suite.Run("Calcuating price estimated on creation for NTS shipment ", func() { + setupTestData := func() models.MTOShipment { + // Set up data to use for all Origin SIT Service Item tests + + move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + estimatedPrimeWeight := unit.Pound(6000) + pickupDate := time.Date(2024, time.July, 31, 12, 0, 0, 0, time.UTC) + pickupAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress2}) + deliveryAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress3}) + + mtoShipment := factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: pickupAddress, + LinkOnly: true, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: deliveryAddress, + LinkOnly: true, + Type: &factory.Addresses.DeliveryAddress, + }, + { + Model: models.MTOShipment{ + PrimeEstimatedWeight: &estimatedPrimeWeight, + RequestedPickupDate: &pickupDate, + ShipmentType: models.MTOShipmentTypeHHGOutOfNTSDom, + }, + }, + }, nil) + + return mtoShipment + } + + reServiceCodeDOP := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDOP) + reServiceCodeDPK := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDPK) + reServiceCodeDDP := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDDP) + reServiceCodeDUPK := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDUPK) + reServiceCodeDLH := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDLH) + reServiceCodeDSH := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDSH) + reServiceCodeFSC := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeFSC) + + startDate := time.Now().AddDate(-1, 0, 0) + endDate := startDate.AddDate(1, 1, 1) + sitEntryDate := time.Date(2020, time.October, 24, 0, 0, 0, 0, time.UTC) + sitPostalCode := "99999" + reason := "lorem ipsum" + + contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + contractYear := testdatagen.MakeReContractYear(suite.DB(), + testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + Name: "Test Contract Year", + EscalationCompounded: 1.125, + StartDate: startDate, + EndDate: endDate, + }, + }) + + serviceArea := testdatagen.MakeReDomesticServiceArea(suite.DB(), + testdatagen.Assertions{ + ReDomesticServiceArea: models.ReDomesticServiceArea{ + Contract: contractYear.Contract, + ServiceArea: "945", + ServicesSchedule: 1, + }, + }) + + serviceAreaDest := testdatagen.MakeReDomesticServiceArea(suite.DB(), + testdatagen.Assertions{ + ReDomesticServiceArea: models.ReDomesticServiceArea{ + Contract: contractYear.Contract, + ServiceArea: "503", + ServicesSchedule: 1, + }, + }) + + serviceAreaPriceDOP := models.ReDomesticServiceAreaPrice{ + ContractID: contractYear.Contract.ID, + ServiceID: reServiceCodeDOP.ID, + IsPeakPeriod: true, + DomesticServiceAreaID: serviceArea.ID, + PriceCents: unit.Cents(1234), + } + + serviceAreaPriceDPK := models.ReDomesticOtherPrice{ + ContractID: contractYear.Contract.ID, + ServiceID: reServiceCodeDPK.ID, + IsPeakPeriod: true, + Schedule: 1, + PriceCents: unit.Cents(121), + } + + serviceAreaPriceDDP := models.ReDomesticServiceAreaPrice{ + ContractID: contractYear.Contract.ID, + ServiceID: reServiceCodeDDP.ID, + IsPeakPeriod: true, + DomesticServiceAreaID: serviceAreaDest.ID, + PriceCents: unit.Cents(482), + } + + serviceAreaPriceDUPK := models.ReDomesticOtherPrice{ + ContractID: contractYear.Contract.ID, + ServiceID: reServiceCodeDUPK.ID, + IsPeakPeriod: true, + Schedule: 1, + PriceCents: unit.Cents(945), + } + + serviceAreaPriceDLH := models.ReDomesticLinehaulPrice{ + ContractID: contractYear.Contract.ID, + WeightLower: 500, + WeightUpper: 10000, + MilesLower: 1, + MilesUpper: 10000, + IsPeakPeriod: true, + DomesticServiceAreaID: serviceArea.ID, + PriceMillicents: unit.Millicents(482), + } + + serviceAreaPriceDSH := models.ReDomesticServiceAreaPrice{ + ContractID: contractYear.Contract.ID, + ServiceID: reServiceCodeDSH.ID, + IsPeakPeriod: true, + DomesticServiceAreaID: serviceArea.ID, + PriceCents: unit.Cents(999), + } + + testdatagen.MakeGHCDieselFuelPrice(suite.DB(), testdatagen.Assertions{ + GHCDieselFuelPrice: models.GHCDieselFuelPrice{ + FuelPriceInMillicents: unit.Millicents(281400), + PublicationDate: time.Date(2020, time.March, 9, 0, 0, 0, 0, time.UTC), + EffectiveDate: time.Date(2020, time.March, 10, 0, 0, 0, 0, time.UTC), + EndDate: time.Date(2025, time.March, 17, 0, 0, 0, 0, time.UTC), + }, + }) + + suite.MustSave(&serviceAreaPriceDOP) + suite.MustSave(&serviceAreaPriceDPK) + suite.MustSave(&serviceAreaPriceDDP) + suite.MustSave(&serviceAreaPriceDUPK) + suite.MustSave(&serviceAreaPriceDLH) + suite.MustSave(&serviceAreaPriceDSH) + + testdatagen.MakeReZip3(suite.DB(), testdatagen.Assertions{ + ReZip3: models.ReZip3{ + Contract: contract, + ContractID: contract.ID, + DomesticServiceArea: serviceArea, + Zip3: "945", + }, + }) + + testdatagen.MakeReZip3(suite.DB(), testdatagen.Assertions{ + ReZip3: models.ReZip3{ + Contract: contract, + ContractID: contract.ID, + DomesticServiceArea: serviceAreaDest, + Zip3: "503", + }, + }) + + shipment := setupTestData() + actualPickupAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress2}) + serviceItemDOP := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDOP, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemDPK := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDPK, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemDDP := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDDP, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemDUPK := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDUPK, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemDLH := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDLH, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemDSH := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDSH, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemFSC := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeFSC, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + builder := query.NewQueryBuilder() + moveRouter := moverouter.NewMoveRouter() + planner := &mocks.Planner{} + planner.On("ZipTransitDistance", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + ).Return(400, nil) + creator := NewMTOServiceItemCreator(planner, builder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) + + dopEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDOP, shipment) + suite.Equal(unit.Cents(91608), dopEstimatedPriceInCents) + + dpkEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDPK, shipment) + suite.Equal(unit.Cents(8976), dpkEstimatedPriceInCents) + + ddpEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDDP, shipment) + suite.Equal(unit.Cents(35772), ddpEstimatedPriceInCents) + + dupkEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDUPK, shipment) + suite.Equal(unit.Cents(70158), dupkEstimatedPriceInCents) + + dlhEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDLH, shipment) + suite.Equal(unit.Cents(15840), dlhEstimatedPriceInCents) + + dshEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDSH, shipment) + suite.Equal(unit.Cents(29673600), dshEstimatedPriceInCents) + + fscEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemFSC, shipment) + suite.Equal(unit.Cents(786), fscEstimatedPriceInCents) + }) + +} diff --git a/pkg/services/mto_service_item/mto_service_item_updater.go b/pkg/services/mto_service_item/mto_service_item_updater.go index b2ac848273f..6491b32e757 100644 --- a/pkg/services/mto_service_item/mto_service_item_updater.go +++ b/pkg/services/mto_service_item/mto_service_item_updater.go @@ -3,6 +3,7 @@ package mtoserviceitem import ( "database/sql" "fmt" + "strconv" "time" "github.com/gobuffalo/validate/v3" @@ -19,6 +20,7 @@ import ( movetaskorder "github.com/transcom/mymove/pkg/services/move_task_order" "github.com/transcom/mymove/pkg/services/query" sitstatus "github.com/transcom/mymove/pkg/services/sit_status" + "github.com/transcom/mymove/pkg/unit" ) // OriginSITLocation is the constant representing when the shipment in storage occurs at the origin @@ -37,22 +39,28 @@ type mtoServiceItemQueryBuilder interface { } type mtoServiceItemUpdater struct { - planner route.Planner - builder mtoServiceItemQueryBuilder - createNewBuilder func() mtoServiceItemQueryBuilder - moveRouter services.MoveRouter - shipmentFetcher services.MTOShipmentFetcher - addressCreator services.AddressCreator + planner route.Planner + builder mtoServiceItemQueryBuilder + createNewBuilder func() mtoServiceItemQueryBuilder + moveRouter services.MoveRouter + shipmentFetcher services.MTOShipmentFetcher + addressCreator services.AddressCreator + unpackPricer services.DomesticUnpackPricer + linehaulPricer services.DomesticLinehaulPricer + destinationPricer services.DomesticDestinationPricer + fuelSurchargePricer services.FuelSurchargePricer + sitFuelSurchargePricer services.DomesticDestinationSITFuelSurchargePricer + sitDeliverPricer services.DomesticDestinationSITDeliveryPricer } // NewMTOServiceItemUpdater returns a new mto service item updater -func NewMTOServiceItemUpdater(planner route.Planner, builder mtoServiceItemQueryBuilder, moveRouter services.MoveRouter, shipmentFetcher services.MTOShipmentFetcher, addressCreator services.AddressCreator) services.MTOServiceItemUpdater { +func NewMTOServiceItemUpdater(planner route.Planner, builder mtoServiceItemQueryBuilder, moveRouter services.MoveRouter, shipmentFetcher services.MTOShipmentFetcher, addressCreator services.AddressCreator, unpackPricer services.DomesticUnpackPricer, linehaulPricer services.DomesticLinehaulPricer, destinationPricer services.DomesticDestinationPricer, fuelSurchargePricer services.FuelSurchargePricer, domesticDestinationSITDeliveryPricer services.DomesticDestinationSITDeliveryPricer, domesticDestinationSITFuelSurchargePricer services.DomesticDestinationSITFuelSurchargePricer) services.MTOServiceItemUpdater { // used inside a transaction and mocking return &mtoServiceItemUpdater{builder: builder} createNewBuilder := func() mtoServiceItemQueryBuilder { return query.NewQueryBuilder() } - return &mtoServiceItemUpdater{planner, builder, createNewBuilder, moveRouter, shipmentFetcher, addressCreator} + return &mtoServiceItemUpdater{planner, builder, createNewBuilder, moveRouter, shipmentFetcher, addressCreator, unpackPricer, linehaulPricer, destinationPricer, fuelSurchargePricer, domesticDestinationSITFuelSurchargePricer, domesticDestinationSITDeliveryPricer} } func (p *mtoServiceItemUpdater) ApproveOrRejectServiceItem( @@ -118,6 +126,183 @@ func (p *mtoServiceItemUpdater) ConvertItemToCustomerExpense( return p.convertItemToCustomerExpense(appCtx, *mtoServiceItem, customerExpenseReason, convertToCustomerExpense, eTag, checkETag()) } +func (p *mtoServiceItemUpdater) findEstimatedPrice(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, mtoShipment models.MTOShipment) (unit.Cents, error) { + if serviceItem.ReService.Code == models.ReServiceCodeDDP || + serviceItem.ReService.Code == models.ReServiceCodeDUPK || + serviceItem.ReService.Code == models.ReServiceCodeDLH || + serviceItem.ReService.Code == models.ReServiceCodeFSC || + serviceItem.ReService.Code == models.ReServiceCodeDDDSIT || + serviceItem.ReService.Code == models.ReServiceCodeDDSFSC { + + isPPM := false + if mtoShipment.ShipmentType == models.MTOShipmentTypePPM { + isPPM = true + } + + var pickupDate *time.Time + if mtoShipment.ActualPickupDate != nil { + pickupDate = mtoShipment.ActualPickupDate + } else { + if mtoShipment.RequestedPickupDate != nil { + pickupDate = mtoShipment.RequestedPickupDate + } + } + + currTime := time.Now() + var distance int + + var shipmentWeight unit.Pound + if mtoShipment.PrimeActualWeight != nil { + shipmentWeight = *mtoShipment.PrimeActualWeight + } else { + if mtoShipment.PrimeEstimatedWeight != nil { + shipmentWeight = *mtoShipment.PrimeEstimatedWeight + } else { + return 0, apperror.NewInvalidInputError(serviceItem.ID, nil, nil, "No estimated or actual weight exists for this service item.") + } + } + + contractCode, err := FetchContractCode(appCtx, currTime) + if err != nil && pickupDate != nil { + contractCode, err = FetchContractCode(appCtx, *pickupDate) + if err != nil { + return 0, err + } + } + + var price unit.Cents + + // destination + if serviceItem.ReService.Code == models.ReServiceCodeDDP { + var domesticServiceArea models.ReDomesticServiceArea + if mtoShipment.DestinationAddress != nil { + domesticServiceArea, err = fetchDomesticServiceArea(appCtx, contractCode, mtoShipment.DestinationAddress.PostalCode) + if err != nil { + return 0, err + } + } + + price, _, err = p.destinationPricer.Price(appCtx, contractCode, *pickupDate, shipmentWeight, domesticServiceArea.ServiceArea, isPPM) + if err != nil { + return 0, err + } + } + if serviceItem.ReService.Code == models.ReServiceCodeDUPK { + domesticServiceArea, err := fetchDomesticServiceArea(appCtx, contractCode, mtoShipment.DestinationAddress.PostalCode) + if err != nil { + return 0, err + } + + serviceScheduleDestination := domesticServiceArea.ServicesSchedule + + price, _, err = p.unpackPricer.Price(appCtx, contractCode, *pickupDate, shipmentWeight, serviceScheduleDestination, isPPM) + if err != nil { + return 0, err + } + } + + // linehaul + if serviceItem.ReService.Code == models.ReServiceCodeDLH { + domesticServiceArea, err := fetchDomesticServiceArea(appCtx, contractCode, mtoShipment.PickupAddress.PostalCode) + if err != nil { + return 0, err + } + if mtoShipment.PickupAddress != nil && mtoShipment.DestinationAddress != nil { + distance, err = p.planner.ZipTransitDistance(appCtx, mtoShipment.PickupAddress.PostalCode, mtoShipment.DestinationAddress.PostalCode) + if err != nil { + return 0, err + } + } + price, _, err = p.linehaulPricer.Price(appCtx, contractCode, *pickupDate, unit.Miles(distance), shipmentWeight, domesticServiceArea.ServiceArea, isPPM) + if err != nil { + return 0, err + } + } + // unpacking + if serviceItem.ReService.Code == models.ReServiceCodeDUPK { + domesticServiceArea, err := fetchDomesticServiceArea(appCtx, contractCode, mtoShipment.DestinationAddress.PostalCode) + if err != nil { + return 0, err + } + price, _, err = p.unpackPricer.Price(appCtx, contractCode, *pickupDate, shipmentWeight, domesticServiceArea.ServicesSchedule, isPPM) + if err != nil { + return 0, err + } + } + // destination sit delivery + if serviceItem.ReService.Code == models.ReServiceCodeDDDSIT { + domesticServiceArea, err := fetchDomesticServiceArea(appCtx, contractCode, mtoShipment.DestinationAddress.PostalCode) + if err != nil { + return 0, err + } + if mtoShipment.DestinationAddress != nil { + distance, err = p.planner.ZipTransitDistance(appCtx, serviceItem.SITDestinationFinalAddress.PostalCode, mtoShipment.DestinationAddress.PostalCode) + if err != nil { + return 0, err + } + } + price, _, err = p.sitDeliverPricer.Price(appCtx, contractCode, *pickupDate, shipmentWeight, domesticServiceArea.ServiceArea, domesticServiceArea.SITPDSchedule, mtoShipment.DestinationAddress.PostalCode, serviceItem.SITDestinationFinalAddress.PostalCode, unit.Miles(distance)) + if err != nil { + return 0, err + } + } + // destination sit fuel surcharge + if serviceItem.ReService.Code == models.ReServiceCodeDDSFSC { + if mtoShipment.DestinationAddress != nil { + distance, err = p.planner.ZipTransitDistance(appCtx, serviceItem.SITDestinationFinalAddress.PostalCode, mtoShipment.DestinationAddress.PostalCode) + if err != nil { + return 0, err + } + } + fscWeightBasedDistanceMultiplier, err := LookupFSCWeightBasedDistanceMultiplier(appCtx, shipmentWeight) + if err != nil { + return 0, err + } + fscWeightBasedDistanceMultiplierFloat, err := strconv.ParseFloat(fscWeightBasedDistanceMultiplier, 64) + if err != nil { + return 0, err + } + eiaFuelPrice, err := LookupEIAFuelPrice(appCtx, *pickupDate) + if err != nil { + return 0, err + } + price, _, err = p.sitFuelSurchargePricer.Price(appCtx, *mtoShipment.ActualPickupDate, unit.Miles(distance), shipmentWeight, fscWeightBasedDistanceMultiplierFloat, eiaFuelPrice, isPPM) + if err != nil { + return 0, err + } + } + // fuel surcharge + if serviceItem.ReService.Code == models.ReServiceCodeFSC { + if mtoShipment.PickupAddress != nil && mtoShipment.DestinationAddress != nil { + distance, err = p.planner.ZipTransitDistance(appCtx, mtoShipment.PickupAddress.PostalCode, mtoShipment.DestinationAddress.PostalCode) + if err != nil { + return 0, err + } + } + + fscWeightBasedDistanceMultiplier, err := LookupFSCWeightBasedDistanceMultiplier(appCtx, shipmentWeight) + if err != nil { + return 0, err + } + fscWeightBasedDistanceMultiplierFloat, err := strconv.ParseFloat(fscWeightBasedDistanceMultiplier, 64) + if err != nil { + return 0, err + } + eiaFuelPrice, err := LookupEIAFuelPrice(appCtx, *pickupDate) + if err != nil { + return 0, err + } + price, _, err = p.fuelSurchargePricer.Price(appCtx, *pickupDate, unit.Miles(distance), shipmentWeight, fscWeightBasedDistanceMultiplierFloat, eiaFuelPrice, isPPM) + if err != nil { + return 0, err + } + + } + return price, nil + } + return 0, nil +} + func (p *mtoServiceItemUpdater) findServiceItem(appCtx appcontext.AppContext, serviceItemID uuid.UUID) (*models.MTOServiceItem, error) { var serviceItem models.MTOServiceItem err := appCtx.DB().Q().EagerPreload( @@ -299,6 +484,21 @@ func (p *mtoServiceItemUpdater) convertItemToCustomerExpense( return &serviceItem, nil } +// UpdateMTOServiceItemPricingEstimate updates the MTO Service Item pricing estimate +func (p *mtoServiceItemUpdater) UpdateMTOServiceItemPricingEstimate( + appCtx appcontext.AppContext, + mtoServiceItem *models.MTOServiceItem, + shipment models.MTOShipment, + eTag string, +) (*models.MTOServiceItem, error) { + estimatedPrice, err := p.findEstimatedPrice(appCtx, mtoServiceItem, shipment) + if estimatedPrice != 0 && err == nil { + mtoServiceItem.PricingEstimate = &estimatedPrice + return p.UpdateMTOServiceItem(appCtx, mtoServiceItem, eTag, UpdateMTOServiceItemBasicValidator) + } + return mtoServiceItem, err +} + // UpdateMTOServiceItemBasic updates the MTO Service Item using base validators func (p *mtoServiceItemUpdater) UpdateMTOServiceItemBasic( appCtx appcontext.AppContext, diff --git a/pkg/services/mto_service_item/mto_service_item_updater_test.go b/pkg/services/mto_service_item/mto_service_item_updater_test.go index f2c6889ca98..1baffb834e4 100644 --- a/pkg/services/mto_service_item/mto_service_item_updater_test.go +++ b/pkg/services/mto_service_item/mto_service_item_updater_test.go @@ -23,6 +23,7 @@ import ( "github.com/transcom/mymove/pkg/models" mocks "github.com/transcom/mymove/pkg/route/mocks" "github.com/transcom/mymove/pkg/services/address" + "github.com/transcom/mymove/pkg/services/ghcrateengine" moverouter "github.com/transcom/mymove/pkg/services/move" movetaskorder "github.com/transcom/mymove/pkg/services/move_task_order" mtoshipment "github.com/transcom/mymove/pkg/services/mto_shipment" @@ -48,7 +49,7 @@ func (suite *MTOServiceItemServiceSuite) TestMTOServiceItemUpdater() { mock.Anything, mock.Anything, ).Return(400, nil) - updater := NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator) + updater := NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer(), ghcrateengine.NewDomesticDestinationSITDeliveryPricer(), ghcrateengine.NewDomesticOriginSITFuelSurchargePricer()) setupServiceItem := func() (models.MTOServiceItem, string) { serviceItem := testdatagen.MakeDefaultMTOServiceItem(suite.DB()) @@ -1757,7 +1758,7 @@ func (suite *MTOServiceItemServiceSuite) TestUpdateMTOServiceItemStatus() { mock.Anything, mock.Anything, ).Return(400, nil) - updater := NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator) + updater := NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer(), ghcrateengine.NewDomesticDestinationSITDeliveryPricer(), ghcrateengine.NewDomesticOriginSITFuelSurchargePricer()) rejectionReason := models.StringPointer("") @@ -2345,6 +2346,105 @@ func (suite *MTOServiceItemServiceSuite) TestUpdateMTOServiceItemStatus() { }) } +func (suite *MTOServiceItemServiceSuite) setupServiceItemData() { + startDate := time.Date(2020, time.January, 1, 12, 0, 0, 0, time.UTC) + endDate := time.Date(2020, time.December, 31, 12, 0, 0, 0, time.UTC) + + testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: startDate, + EndDate: endDate, + }, + }) + + originalDomesticServiceArea := testdatagen.FetchOrMakeReDomesticServiceArea(suite.DB(), testdatagen.Assertions{ + ReDomesticServiceArea: models.ReDomesticServiceArea{ + ServiceArea: "004", + ServicesSchedule: 2, + }, + ReContract: testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}), + }) + + testdatagen.FetchOrMakeReZip3(suite.DB(), testdatagen.Assertions{ + ReZip3: models.ReZip3{ + Contract: originalDomesticServiceArea.Contract, + ContractID: originalDomesticServiceArea.ContractID, + DomesticServiceArea: originalDomesticServiceArea, + Zip3: "902", + }, + }) + + testdatagen.FetchOrMakeReDomesticLinehaulPrice(suite.DB(), testdatagen.Assertions{ + ReDomesticLinehaulPrice: models.ReDomesticLinehaulPrice{ + Contract: originalDomesticServiceArea.Contract, + ContractID: originalDomesticServiceArea.ContractID, + DomesticServiceArea: originalDomesticServiceArea, + DomesticServiceAreaID: originalDomesticServiceArea.ID, + WeightLower: unit.Pound(500), + WeightUpper: unit.Pound(9999), + MilesLower: 250, + MilesUpper: 9999, + PriceMillicents: unit.Millicents(606800), + IsPeakPeriod: false, + }, + }) +} + +func (suite *MTOServiceItemServiceSuite) TestUpdateMTOServiceItemPricingEstimate() { + builder := query.NewQueryBuilder() + moveRouter := moverouter.NewMoveRouter() + shipmentFetcher := mtoshipment.NewMTOShipmentFetcher() + addressCreator := address.NewAddressCreator() + planner := &mocks.Planner{} + planner.On("ZipTransitDistance", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + ).Return(400, nil) + updater := NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer(), ghcrateengine.NewDomesticDestinationSITDeliveryPricer(), ghcrateengine.NewDomesticOriginSITFuelSurchargePricer()) + + setupServiceItem := func() (models.MTOServiceItem, string) { + serviceItem := testdatagen.MakeDefaultMTOServiceItem(suite.DB()) + eTag := etag.GenerateEtag(serviceItem.UpdatedAt) + return serviceItem, eTag + } + + setupServiceItems := func() models.MTOServiceItems { + serviceItems := testdatagen.MakeMTOServiceItems(suite.DB()) + return serviceItems + } + + suite.Run("Validation Error", func() { + suite.setupServiceItemData() + serviceItem, eTag := setupServiceItem() + invalidServiceItem := serviceItem + invalidServiceItem.MoveTaskOrderID = serviceItem.ID // invalid Move ID + + updatedServiceItem, err := updater.UpdateMTOServiceItemPricingEstimate(suite.AppContextForTest(), &invalidServiceItem, serviceItem.MTOShipment, eTag) + + suite.Nil(updatedServiceItem) + suite.Error(err) + suite.IsType(apperror.InvalidInputError{}, err) + + invalidInputError := err.(apperror.InvalidInputError) + suite.True(invalidInputError.ValidationErrors.HasAny()) + suite.Contains(invalidInputError.ValidationErrors.Keys(), "moveTaskOrderID") + }) + + suite.Run("Returns updated service item on success wihtout error", func() { + suite.setupServiceItemData() + serviceItems := setupServiceItems() + + for _, serviceItem := range serviceItems { + eTag := etag.GenerateEtag(serviceItem.UpdatedAt) + updatedServiceItem, err := updater.UpdateMTOServiceItemPricingEstimate(suite.AppContextForTest(), &serviceItem, serviceItem.MTOShipment, eTag) + + suite.NotNil(updatedServiceItem) + suite.Nil(err) + } + }) +} + // Helper function to create a rejected service item func buildRejectedServiceItem(suite *MTOServiceItemServiceSuite, reServiceCode models.ReServiceCode, reason string, contactDatePlusGracePeriod, aMonthAgo, now, sitRequestedDelivery time.Time, requestApprovalsRequestedStatus bool) models.MTOServiceItem { return factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ diff --git a/pkg/services/mto_service_item/mto_service_item_validators.go b/pkg/services/mto_service_item/mto_service_item_validators.go index 09d9c1b09ee..f3423517301 100644 --- a/pkg/services/mto_service_item/mto_service_item_validators.go +++ b/pkg/services/mto_service_item/mto_service_item_validators.go @@ -616,6 +616,9 @@ func (v *updateMTOServiceItemData) setNewMTOServiceItem() *models.MTOServiceItem newMTOServiceItem.ActualWeight = services.SetOptionalPoundField( v.updatedServiceItem.ActualWeight, newMTOServiceItem.ActualWeight) + newMTOServiceItem.PricingEstimate = services.SetNoNilOptionalCentField( + v.updatedServiceItem.PricingEstimate, newMTOServiceItem.PricingEstimate) + return &newMTOServiceItem } diff --git a/pkg/services/mto_shipment.go b/pkg/services/mto_shipment.go index a409c6db436..187d290cb9f 100644 --- a/pkg/services/mto_shipment.go +++ b/pkg/services/mto_shipment.go @@ -153,3 +153,15 @@ type ShipmentSITStatus interface { CalculateShipmentSITAllowance(appCtx appcontext.AppContext, shipment models.MTOShipment) (int, error) RetrieveShipmentSIT(appCtx appcontext.AppContext, shipment models.MTOShipment) (models.SITServiceItemGroupings, error) } + +type ShipmentPostalCodeRateArea struct { + PostalCode string + RateArea *models.ReRateArea +} + +// ShipmentRateAreaFinder is the interface to retrieve Oconus RateArea info for shipment +// +//go:generate mockery --name ShipmentRateAreaFinder +type ShipmentRateAreaFinder interface { + GetPrimeMoveShipmentOconusRateArea(appCtx appcontext.AppContext, move models.Move) (*[]ShipmentPostalCodeRateArea, error) +} diff --git a/pkg/services/mto_shipment/mto_shipment_creator.go b/pkg/services/mto_shipment/mto_shipment_creator.go index 0beb077d217..9a98e962b57 100644 --- a/pkg/services/mto_shipment/mto_shipment_creator.go +++ b/pkg/services/mto_shipment/mto_shipment_creator.go @@ -36,19 +36,19 @@ func NewMTOShipmentCreatorV1(builder createMTOShipmentQueryBuilder, fetcher serv fetcher, moveRouter, addressCreator, - []validator{protectV1Diversion()}, + []validator{protectV1Diversion(), MTOShipmentHasTertiaryAddressWithNoSecondaryAddressCreate()}, } } // NewMTOShipmentCreator creates a new struct with the service dependencies -// This is utilized in Prime API V2 +// This is utilized in Prime API V2 and Prime API V3 func NewMTOShipmentCreatorV2(builder createMTOShipmentQueryBuilder, fetcher services.Fetcher, moveRouter services.MoveRouter, addressCreator services.AddressCreator) services.MTOShipmentCreator { return &mtoShipmentCreator{ builder, fetcher, moveRouter, addressCreator, - []validator{checkDiversionValid(), childDiversionPrimeWeightRule()}, + []validator{checkDiversionValid(), childDiversionPrimeWeightRule(), MTOShipmentHasTertiaryAddressWithNoSecondaryAddressCreate()}, } } diff --git a/pkg/services/mto_shipment/mto_shipment_creator_test.go b/pkg/services/mto_shipment/mto_shipment_creator_test.go index 82e057c61f6..434bc241ca2 100644 --- a/pkg/services/mto_shipment/mto_shipment_creator_test.go +++ b/pkg/services/mto_shipment/mto_shipment_creator_test.go @@ -286,6 +286,41 @@ func (suite *MTOShipmentServiceSuite) TestCreateMTOShipment() { suite.Equal("failed to create pickup address - the country GB is not supported at this time - only US is allowed", err.Error()) }) + suite.Run("If the shipment has an international address it should be returned", func() { + subtestData := suite.createSubtestData(nil) + creator := subtestData.shipmentCreator + + internationalAddress := factory.BuildAddress(nil, []factory.Customization{ + { + Model: models.Country{ + Country: "GB", + CountryName: "UNITED KINGDOM", + }, + }, + }, nil) + // stubbed countries need an ID + internationalAddress.ID = uuid.Must(uuid.NewV4()) + + mtoShipment := factory.BuildMTOShipment(nil, []factory.Customization{ + { + Model: subtestData.move, + LinkOnly: true, + }, + { + Model: internationalAddress, + LinkOnly: true, + }, + }, nil) + + mtoShipmentClear := clearShipmentIDFields(&mtoShipment) + mtoShipmentClear.MTOServiceItems = models.MTOServiceItems{} + + _, err := creator.CreateMTOShipment(suite.AppContextForTest(), mtoShipmentClear) + + suite.Error(err) + suite.Equal("failed to create pickup address - the country GB is not supported at this time - only US is allowed", err.Error()) + }) + suite.Run("If the shipment is created successfully it should return ShipmentLocator", func() { subtestData := suite.createSubtestData(nil) creator := subtestData.shipmentCreator diff --git a/pkg/services/mto_shipment/mto_shipment_rate_area_fetcher.go b/pkg/services/mto_shipment/mto_shipment_rate_area_fetcher.go new file mode 100644 index 00000000000..bbc290dba9e --- /dev/null +++ b/pkg/services/mto_shipment/mto_shipment_rate_area_fetcher.go @@ -0,0 +1,115 @@ +package mtoshipment + +import ( + "database/sql" + "fmt" + "time" + + "github.com/gofrs/uuid" + "golang.org/x/exp/slices" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/apperror" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" +) + +type mtoShipmentRateAreaFetcher struct { +} + +// NewMTOShipmentFetcher creates a new MTOShipmentFetcher struct that supports ListMTOShipments +func NewMTOShipmentRateAreaFetcher() services.ShipmentRateAreaFinder { + return &mtoShipmentRateAreaFetcher{} +} + +func (f mtoShipmentRateAreaFetcher) GetPrimeMoveShipmentOconusRateArea(appCtx appcontext.AppContext, moveTaskOrder models.Move) (*[]services.ShipmentPostalCodeRateArea, error) { + if moveTaskOrder.AvailableToPrimeAt == nil { + return nil, apperror.NewUnprocessableEntityError("Move not available to the Prime, unable to retrieve move shipment oconus rateArea") + } + + contract, err := fetchContract(appCtx, *moveTaskOrder.AvailableToPrimeAt) + if err != nil { + return nil, err + } + + // build set of postalCodes to fetch rateArea for + var postalCodes = make([]string, 0) + for _, shipment := range moveTaskOrder.MTOShipments { + if shipment.PickupAddress != nil { + if !slices.Contains(postalCodes, shipment.PickupAddress.PostalCode) { + postalCodes = append(postalCodes, shipment.PickupAddress.PostalCode) + } + } + if shipment.DestinationAddress != nil { + if !slices.Contains(postalCodes, shipment.DestinationAddress.PostalCode) { + postalCodes = append(postalCodes, shipment.DestinationAddress.PostalCode) + } + } + if shipment.PPMShipment != nil { + if shipment.PPMShipment.PickupAddress != nil { + if !slices.Contains(postalCodes, shipment.PPMShipment.PickupAddress.PostalCode) { + postalCodes = append(postalCodes, shipment.PPMShipment.PickupAddress.PostalCode) + } + } + if shipment.PPMShipment.DestinationAddress != nil { + if !slices.Contains(postalCodes, shipment.PPMShipment.DestinationAddress.PostalCode) { + postalCodes = append(postalCodes, shipment.PPMShipment.DestinationAddress.PostalCode) + } + } + } + } + + ra, err := fetchRateArea(appCtx, contract.ID, postalCodes) + if err != nil { + return nil, err + } + + return ra, nil +} + +func fetchRateArea(appCtx appcontext.AppContext, contractId uuid.UUID, postalCode []string) (*[]services.ShipmentPostalCodeRateArea, error) { + var rateArea = make([]services.ShipmentPostalCodeRateArea, 0) + for _, code := range postalCode { + ra, err := fetchOconusRateAreaByPostalCode(appCtx, contractId, code) + if err != nil { + if err != sql.ErrNoRows { + return nil, apperror.NewQueryError("GetRateArea", err, fmt.Sprintf("error retrieving rateArea for contractId:%s, postalCode:%s", contractId, code)) + } + } else { + rateArea = append(rateArea, services.ShipmentPostalCodeRateArea{PostalCode: code, RateArea: ra}) + } + } + return &rateArea, nil +} + +func fetchOconusRateAreaByPostalCode(appCtx appcontext.AppContext, contractId uuid.UUID, postalCode string) (*models.ReRateArea, error) { + var area models.ReRateArea + + err := appCtx.DB().Q().RawQuery(`select + re_rate_areas.* + from v_locations + join re_oconus_rate_areas on re_oconus_rate_areas.us_post_region_cities_id = v_locations.uprc_id + join re_rate_areas on re_oconus_rate_areas.rate_area_id = re_rate_areas.id and v_locations.uspr_zip_id = ? + and re_rate_areas.contract_id = ?`, + postalCode, contractId).First(&area) + + if err != nil { + return nil, err + } + + return &area, err +} + +func fetchContract(appCtx appcontext.AppContext, date time.Time) (*models.ReContract, error) { + var contractYear models.ReContractYear + err := appCtx.DB().EagerPreload("Contract").Where("? between start_date and end_date", date). + First(&contractYear) + if err != nil { + if err == sql.ErrNoRows { + return nil, apperror.NewNotFoundError(uuid.Nil, fmt.Sprintf("no contract year found for %s", date.String())) + } + return nil, err + } + + return &contractYear.Contract, nil +} diff --git a/pkg/services/mto_shipment/mto_shipment_rate_area_fetcher_test.go b/pkg/services/mto_shipment/mto_shipment_rate_area_fetcher_test.go new file mode 100644 index 00000000000..976e03ac2e1 --- /dev/null +++ b/pkg/services/mto_shipment/mto_shipment_rate_area_fetcher_test.go @@ -0,0 +1,445 @@ +package mtoshipment + +import ( + "database/sql" + "fmt" + "time" + + "github.com/gofrs/uuid" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/apperror" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/testdatagen" +) + +const testContractCode = "TEST" +const testContractName = "Test Contract" +const fairbanksAlaskaPostalCode = "99716" +const anchorageAlaskaPostalCode = "99521" +const wasillaAlaskaPostalCode = "99652" + +func (suite *MTOShipmentServiceSuite) TestGetMoveShipmentRateArea() { + shipmentRateAreaFetcher := NewMTOShipmentRateAreaFetcher() + + suite.Run("test mapping of one rateArea to many postCodes and one rateArea to one", func() { + availableToPrimeAtTime := time.Now().Add(-500 * time.Hour) + testMove := models.Move{ + AvailableToPrimeAt: &availableToPrimeAtTime, + MTOShipments: models.MTOShipments{ + models.MTOShipment{ + PickupAddress: &models.Address{ + StreetAddress1: "123 Main St", + City: "Fairbanks", + State: "AK", + PostalCode: fairbanksAlaskaPostalCode, + }, + DestinationAddress: &models.Address{ + StreetAddress1: "123 Main St", + City: "Anchorage", + State: "AK", + PostalCode: anchorageAlaskaPostalCode, + }, + }, + models.MTOShipment{ + PickupAddress: &models.Address{ + StreetAddress1: "123 Main St", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + DestinationAddress: &models.Address{ + StreetAddress1: "123 Main St", + City: "San Diego", + State: "CA", + PostalCode: "92075", + }, + }, + models.MTOShipment{ + PPMShipment: &models.PPMShipment{ + PickupAddress: &models.Address{ + StreetAddress1: "123 Main St", + City: "Wasilla", + State: "AK", + PostalCode: wasillaAlaskaPostalCode, + }, + DestinationAddress: &models.Address{ + StreetAddress1: "123 Main St", + City: "Wasilla", + State: "AK", + PostalCode: wasillaAlaskaPostalCode, + }, + }, + }, + }, + } + + // create test contract + contract, err := suite.createContract(suite.AppContextForTest(), testContractCode, testContractName) + suite.NotNil(contract) + suite.FatalNoError(err) + + // setup contract year within availableToPrimeAtTime time + testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: availableToPrimeAtTime, + EndDate: time.Now(), + ContractID: contract.ID, + }, + }) + + setupRateArea := func(contract models.ReContract) models.ReRateArea { + rateAreaCode := uuid.Must(uuid.NewV4()).String()[0:5] + rateArea := models.ReRateArea{ + ID: uuid.Must(uuid.NewV4()), + ContractID: contract.ID, + IsOconus: true, + Code: rateAreaCode, + Name: fmt.Sprintf("Alaska-%s", rateAreaCode), + Contract: contract, + } + verrs, err := suite.DB().ValidateAndCreate(&rateArea) + if verrs.HasAny() { + suite.Fail(verrs.Error()) + } + if err != nil { + suite.Fail(err.Error()) + } + return rateArea + } + + setupRateAreaToPostalCodeData := func(rateArea models.ReRateArea, postalCode string) models.ReRateArea { + // fetch US by country id + us_countryId := uuid.FromStringOrNil("c390ced2-89e1-418d-bbff-f8a79b89c4b6") + us_country, err := models.FetchCountryByID(suite.DB(), us_countryId) + suite.NotNil(us_country) + suite.FatalNoError(err) + + usprc, err := findUsPostRegionCityByZipCode(suite.AppContextForTest(), postalCode) + suite.NotNil(usprc) + suite.FatalNoError(err) + + oconusRateArea := testOnlyOconusRateArea{ + ID: uuid.Must(uuid.NewV4()), + RateAreaId: rateArea.ID, + CountryId: us_country.ID, + UsPostRegionCityId: usprc.ID, + Active: true, + } + verrs, err := suite.DB().ValidateAndCreate(&oconusRateArea) + if verrs.HasAny() { + suite.Fail(verrs.Error()) + } + if err != nil { + suite.Fail(err.Error()) + } + + return rateArea + } + + setupRateAreaToManyPostalCodesData := func(contract models.ReContract, testPostalCode []string) models.ReRateArea { + rateArea := setupRateArea(contract) + for _, postalCode := range testPostalCode { + setupRateAreaToPostalCodeData(rateArea, postalCode) + } + return rateArea + } + + // setup Fairbanks and Anchorage to have same RateArea + rateArea1 := setupRateAreaToManyPostalCodesData(*contract, []string{fairbanksAlaskaPostalCode, anchorageAlaskaPostalCode}) + // setup Wasilla to have it's own RateArea + rateArea2 := setupRateAreaToPostalCodeData(setupRateArea(*contract), wasillaAlaskaPostalCode) + + shipmentPostalCodeRateArea, err := shipmentRateAreaFetcher.GetPrimeMoveShipmentOconusRateArea(suite.AppContextForTest(), testMove) + suite.NotNil(shipmentPostalCodeRateArea) + suite.FatalNoError(err) + suite.Equal(3, len(*shipmentPostalCodeRateArea)) + + isRateAreaEquals := func(expectedRateArea models.ReRateArea, postalCode string, shipmentPostalCodeRateArea *[]services.ShipmentPostalCodeRateArea) bool { + var shipmentPostalCodeRateAreaLookupMap = make(map[string]services.ShipmentPostalCodeRateArea) + for _, i := range *shipmentPostalCodeRateArea { + shipmentPostalCodeRateAreaLookupMap[i.PostalCode] = i + } + if _, ok := shipmentPostalCodeRateAreaLookupMap[postalCode]; !ok { + return false + } + return (shipmentPostalCodeRateAreaLookupMap[postalCode].RateArea.ID == expectedRateArea.ID && shipmentPostalCodeRateAreaLookupMap[postalCode].RateArea.Name == expectedRateArea.Name && shipmentPostalCodeRateAreaLookupMap[postalCode].RateArea.Code == expectedRateArea.Code) + } + + suite.Equal(true, isRateAreaEquals(rateArea1, fairbanksAlaskaPostalCode, shipmentPostalCodeRateArea)) + suite.Equal(true, isRateAreaEquals(rateArea1, anchorageAlaskaPostalCode, shipmentPostalCodeRateArea)) + suite.Equal(true, isRateAreaEquals(rateArea2, wasillaAlaskaPostalCode, shipmentPostalCodeRateArea)) + + suite.Equal(false, isRateAreaEquals(rateArea2, fairbanksAlaskaPostalCode, shipmentPostalCodeRateArea)) + suite.Equal(false, isRateAreaEquals(rateArea2, anchorageAlaskaPostalCode, shipmentPostalCodeRateArea)) + suite.Equal(false, isRateAreaEquals(rateArea1, wasillaAlaskaPostalCode, shipmentPostalCodeRateArea)) + }) + + suite.Run("no oconus rateArea found returns empty array", func() { + availableToPrimeAtTime := time.Now().Add(-500 * time.Hour) + testMove := models.Move{ + AvailableToPrimeAt: &availableToPrimeAtTime, + MTOShipments: models.MTOShipments{ + models.MTOShipment{ + PickupAddress: &models.Address{ + StreetAddress1: "123 Main St", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + DestinationAddress: &models.Address{ + StreetAddress1: "123 Main St", + City: "San Diego", + State: "CA", + PostalCode: "92075", + }, + }, + models.MTOShipment{ + PPMShipment: &models.PPMShipment{ + PickupAddress: &models.Address{ + StreetAddress1: "123 Main St", + City: "NY", + State: "NY", + PostalCode: "11220", + }, + DestinationAddress: &models.Address{ + StreetAddress1: "123 Main St", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + }, + }, + }, + } + + // create test contract + contract, err := suite.createContract(suite.AppContextForTest(), testContractCode, testContractName) + suite.NotNil(contract) + suite.FatalNoError(err) + + // setup contract year within availableToPrimeAtTime time + testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: availableToPrimeAtTime, + EndDate: time.Now(), + ContractID: contract.ID, + }, + }) + + shipmentPostalCodeRateArea, err := shipmentRateAreaFetcher.GetPrimeMoveShipmentOconusRateArea(suite.AppContextForTest(), testMove) + suite.NotNil(shipmentPostalCodeRateArea) + suite.Equal(0, len(*shipmentPostalCodeRateArea)) + suite.Nil(err) + }) + + suite.Run("not available to prime error", func() { + testMove := models.Move{ + MTOShipments: models.MTOShipments{ + models.MTOShipment{ + PickupAddress: &models.Address{ + StreetAddress1: "123 Main St", + City: "Fairbanks", + State: "AK", + PostalCode: fairbanksAlaskaPostalCode, + }, + DestinationAddress: &models.Address{ + StreetAddress1: "123 Main St", + City: "Anchorage", + State: "AK", + PostalCode: anchorageAlaskaPostalCode, + }, + }, + }, + } + + shipmentPostalCodeRateArea, err := shipmentRateAreaFetcher.GetPrimeMoveShipmentOconusRateArea(suite.AppContextForTest(), testMove) + suite.Nil(shipmentPostalCodeRateArea) + suite.NotNil(err) + suite.IsType(apperror.UnprocessableEntityError{}, err) + }) + + suite.Run("contract for move not found", func() { + availableToPrimeAtTime := time.Now().Add(-500 * time.Hour) + testMove := models.Move{ + AvailableToPrimeAt: &availableToPrimeAtTime, + MTOShipments: models.MTOShipments{ + models.MTOShipment{ + PickupAddress: &models.Address{ + StreetAddress1: "123 Main St", + City: "Fairbanks", + State: "AK", + PostalCode: fairbanksAlaskaPostalCode, + }, + DestinationAddress: &models.Address{ + StreetAddress1: "123 Main St", + City: "Anchorage", + State: "AK", + PostalCode: anchorageAlaskaPostalCode, + }, + }, + }, + } + + // create test contract + contract, err := suite.createContract(suite.AppContextForTest(), testContractCode, testContractName) + suite.NotNil(contract) + suite.FatalNoError(err) + + // setup contract year within availableToPrimeAtTime time + testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: time.Now(), + EndDate: time.Now().Add(5 * time.Hour), + ContractID: contract.ID, + }, + }) + + shipmentPostalCodeRateArea, err := shipmentRateAreaFetcher.GetPrimeMoveShipmentOconusRateArea(suite.AppContextForTest(), testMove) + suite.Nil(shipmentPostalCodeRateArea) + suite.NotNil(err) + suite.IsType(apperror.NotFoundError{}, err) + }) +} + +func (suite *MTOShipmentServiceSuite) TestFetchRateAreaByPostalCode() { + // fetch US by country id + us_countryId := uuid.FromStringOrNil("c390ced2-89e1-418d-bbff-f8a79b89c4b6") + us_country, err := models.FetchCountryByID(suite.DB(), us_countryId) + suite.NotNil(us_country) + suite.FatalNoError(err) + + // create test contract + contract, err := suite.createContract(suite.AppContextForTest(), testContractCode, testContractName) + suite.NotNil(contract) + suite.FatalNoError(err) + + // create rateArea associated to contract + rateArea := models.ReRateArea{ + ID: uuid.Must(uuid.NewV4()), + ContractID: contract.ID, + IsOconus: true, + Code: "SomeAlaskaCode", + Name: "Alaska", + Contract: *contract, + } + verrs, err := suite.DB().ValidateAndCreate(&rateArea) + if verrs.HasAny() { + suite.Fail(verrs.Error()) + } + if err != nil { + suite.Fail(err.Error()) + } + + const alaskaPostalCode = "99506" + + usprc, err := findUsPostRegionCityByZipCode(suite.AppContextForTest(), alaskaPostalCode) + suite.NotNil(usprc) + suite.FatalNoError(err) + + oconusRateArea := testOnlyOconusRateArea{ + ID: uuid.Must(uuid.NewV4()), + RateAreaId: rateArea.ID, + CountryId: us_country.ID, + UsPostRegionCityId: usprc.ID, + Active: true, + } + verrs, err = suite.DB().ValidateAndCreate(&oconusRateArea) + if verrs.HasAny() { + suite.Fail(verrs.Error()) + } + if err != nil { + suite.Fail(err.Error()) + } + + match, err := fetchOconusRateAreaByPostalCode(suite.AppContextForTest(), contract.ID, alaskaPostalCode) + suite.NotNil(match) + suite.FatalNoError(err) +} + +func (suite *MTOShipmentServiceSuite) TestFetchRateAreaByPostalCodeNotFound() { + _, err := fetchOconusRateAreaByPostalCode(suite.AppContextForTest(), uuid.FromStringOrNil("51393fa4-b31c-40fe-bedf-b692703c46eb"), "90210") + suite.NotNil(err) +} + +func (suite *MTOShipmentServiceSuite) TestFetchContract() { + // create test contract + expectedContract, err := suite.createContract(suite.AppContextForTest(), testContractCode, testContractName) + suite.NotNil(expectedContract) + suite.FatalNoError(err) + + time := time.Now().Add(-50 * time.Hour) + testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: time, + EndDate: time, + ContractID: expectedContract.ID, + }, + }) + contract, err := fetchContract(suite.AppContextForTest(), time) + suite.NotNil(contract) + suite.Nil(err) + suite.Equal(expectedContract.ID, contract.ID) +} + +func (suite *MTOShipmentServiceSuite) TestFetchContractNotFound() { + _, err := fetchContract(suite.AppContextForTest(), time.Now()) + suite.NotNil(err) +} + +func (suite *MTOShipmentServiceSuite) createContract(appCtx appcontext.AppContext, contractCode string, contractName string) (*models.ReContract, error) { + + // See if contract code already exists. + exists, err := appCtx.DB().Where("code = ?", testContractCode).Exists(&models.ReContract{}) + if err != nil { + return nil, fmt.Errorf("could not determine if contract code [%s] existed: %w", testContractCode, err) + } + if exists { + return nil, fmt.Errorf("the provided contract code [%s] already exists", testContractCode) + } + + // Contract code is new; insert it. + contract := models.ReContract{ + Code: contractCode, + Name: contractName, + } + verrs, err := appCtx.DB().ValidateAndSave(&contract) + if verrs.HasAny() { + return nil, fmt.Errorf("validation errors when saving contract [%+v]: %w", contract, verrs) + } + if err != nil { + return nil, fmt.Errorf("could not save contract [%+v]: %w", contract, err) + } + + return &contract, nil +} + +func findUsPostRegionCityByZipCode(appCtx appcontext.AppContext, zipCode string) (*models.UsPostRegionCity, error) { + var usprc models.UsPostRegionCity + err := appCtx.DB().Where("uspr_zip_id = ?", zipCode).First(&usprc) + if err != nil { + switch err { + case sql.ErrNoRows: + return nil, fmt.Errorf("No UsPostRegionCity found for provided zip code %s", zipCode) + default: + return nil, err + } + } + return &usprc, nil +} + +// **** This model is specifically for testing only to allow both R/W (READ,INSERTS). models.OconusRateArea is (R)READONLY. *** +type testOnlyOconusRateArea struct { + ID uuid.UUID `json:"id" db:"id"` + RateAreaId uuid.UUID `json:"rate_area_id" db:"rate_area_id"` + CountryId uuid.UUID `json:"country_id" db:"country_id"` + UsPostRegionCityId uuid.UUID `json:"us_post_region_cities_id" db:"us_post_region_cities_id"` + Active bool `json:"active" db:"active"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + UpdatedAt time.Time `json:"updated_at" db:"updated_at"` +} + +func (o testOnlyOconusRateArea) TableName() string { + return "re_oconus_rate_areas" +} diff --git a/pkg/services/mto_shipment/mto_shipment_updater.go b/pkg/services/mto_shipment/mto_shipment_updater.go index cc3ca14e81d..92ca8ba58c8 100644 --- a/pkg/services/mto_shipment/mto_shipment_updater.go +++ b/pkg/services/mto_shipment/mto_shipment_updater.go @@ -75,7 +75,7 @@ func NewCustomerMTOShipmentUpdater(builder UpdateMTOShipmentQueryBuilder, _ serv moveWeights, sender, recalculator, - []validator{checkStatus()}, + []validator{checkStatus(), MTOShipmentHasTertiaryAddressWithNoSecondaryAddressUpdate()}, } } @@ -90,7 +90,7 @@ func NewOfficeMTOShipmentUpdater(builder UpdateMTOShipmentQueryBuilder, _ servic moveWeights, sender, recalculator, - []validator{checkStatus(), checkUpdateAllowed()}, + []validator{checkStatus(), checkUpdateAllowed(), MTOShipmentHasTertiaryAddressWithNoSecondaryAddressUpdate()}, } } @@ -107,7 +107,7 @@ func NewPrimeMTOShipmentUpdater(builder UpdateMTOShipmentQueryBuilder, _ service moveWeights, sender, recalculator, - []validator{checkStatus(), checkAvailToPrime(), checkPrimeValidationsOnModel(planner)}, + []validator{checkStatus(), checkAvailToPrime(), checkPrimeValidationsOnModel(planner), MTOShipmentHasTertiaryAddressWithNoSecondaryAddressUpdate()}, } } @@ -1063,15 +1063,27 @@ func reServiceCodesForShipment(shipment models.MTOShipment) []models.ReServiceCo // default service items that we want created as a side effect. // More info in MB-1140: https://dp3.atlassian.net/browse/MB-1140 + // international shipment service items are created in the shipment_approver switch shipment.ShipmentType { case models.MTOShipmentTypeHHG: + if shipment.MarketCode != models.MarketCodeInternational { + originZIP3 := shipment.PickupAddress.PostalCode[0:3] + destinationZIP3 := shipment.DestinationAddress.PostalCode[0:3] + + if originZIP3 == destinationZIP3 { + return []models.ReServiceCode{ + models.ReServiceCodeDSH, + models.ReServiceCodeFSC, + models.ReServiceCodeDOP, + models.ReServiceCodeDDP, + models.ReServiceCodeDPK, + models.ReServiceCodeDUPK, + } + } - originZIP3 := shipment.PickupAddress.PostalCode[0:3] - destinationZIP3 := shipment.DestinationAddress.PostalCode[0:3] - - if originZIP3 == destinationZIP3 { + // Need to create: Dom Linehaul, Fuel Surcharge, Dom Origin Price, Dom Destination Price, Dom Packing, and Dom Unpacking. return []models.ReServiceCode{ - models.ReServiceCodeDSH, + models.ReServiceCodeDLH, models.ReServiceCodeFSC, models.ReServiceCodeDOP, models.ReServiceCodeDDP, @@ -1079,16 +1091,6 @@ func reServiceCodesForShipment(shipment models.MTOShipment) []models.ReServiceCo models.ReServiceCodeDUPK, } } - - // Need to create: Dom Linehaul, Fuel Surcharge, Dom Origin Price, Dom Destination Price, Dom Packing, and Dom Unpacking. - return []models.ReServiceCode{ - models.ReServiceCodeDLH, - models.ReServiceCodeFSC, - models.ReServiceCodeDOP, - models.ReServiceCodeDDP, - models.ReServiceCodeDPK, - models.ReServiceCodeDUPK, - } case models.MTOShipmentTypeHHGIntoNTSDom: // Need to create: Dom Linehaul, Fuel Surcharge, Dom Origin Price, Dom Destination Price, Dom NTS Packing return []models.ReServiceCode{ diff --git a/pkg/services/mto_shipment/mto_shipment_updater_test.go b/pkg/services/mto_shipment/mto_shipment_updater_test.go index 230bc161da9..dfabece3112 100644 --- a/pkg/services/mto_shipment/mto_shipment_updater_test.go +++ b/pkg/services/mto_shipment/mto_shipment_updater_test.go @@ -309,6 +309,11 @@ func (suite *MTOShipmentServiceSuite) TestMTOShipmentUpdater() { ShipmentType: models.MTOShipmentTypeHHG, }, }, + { + Model: secondaryPickupAddress, + LinkOnly: true, + Type: &factory.Addresses.SecondaryPickupAddress, + }, { Model: tertiaryPickupAddress, LinkOnly: true, diff --git a/pkg/services/mto_shipment/rules.go b/pkg/services/mto_shipment/rules.go index 3c6e37c37e9..46663cc2c00 100644 --- a/pkg/services/mto_shipment/rules.go +++ b/pkg/services/mto_shipment/rules.go @@ -52,7 +52,7 @@ func checkAvailToPrime() validator { return apperror.NewQueryError("Move", err, "Unexpected error") } if !availToPrime { - return apperror.NewNotFoundError(newer.ID, "for mtoShipment") + return apperror.NewNotFoundError(newer.ID, "not available to prime for mtoShipment") } return nil }) @@ -149,6 +149,78 @@ func checkPrimeDeleteAllowed() validator { }) } +// helper function to check if the secondary address is empty, but the tertiary is not +func isMTOShipmentAddressCreateSequenceValid(mtoShipmentToCheck models.MTOShipment) bool { + bothPickupAddressesEmpty := (models.IsAddressEmpty(mtoShipmentToCheck.SecondaryPickupAddress) && models.IsAddressEmpty(mtoShipmentToCheck.TertiaryPickupAddress)) + bothDestinationAddressesEmpty := (models.IsAddressEmpty(mtoShipmentToCheck.SecondaryDeliveryAddress) && models.IsAddressEmpty(mtoShipmentToCheck.TertiaryDeliveryAddress)) + bothPickupAddressesNotEmpty := !bothPickupAddressesEmpty + bothDestinationAddressesNotEmpty := !bothDestinationAddressesEmpty + hasNoSecondaryHasTertiaryPickup := (models.IsAddressEmpty(mtoShipmentToCheck.SecondaryPickupAddress) && !models.IsAddressEmpty(mtoShipmentToCheck.TertiaryPickupAddress)) + hasNoSecondaryHasTertiaryDestination := (models.IsAddressEmpty(mtoShipmentToCheck.SecondaryDeliveryAddress) && !models.IsAddressEmpty(mtoShipmentToCheck.TertiaryDeliveryAddress)) + + // need an explicit case to capture when both are empty or not empty + if ((bothPickupAddressesEmpty && bothDestinationAddressesEmpty) || (bothPickupAddressesNotEmpty && bothDestinationAddressesNotEmpty)) && !(hasNoSecondaryHasTertiaryPickup || hasNoSecondaryHasTertiaryDestination) { + return true + } + if hasNoSecondaryHasTertiaryPickup || hasNoSecondaryHasTertiaryDestination { + return false + } + return true +} + +// helper function to check if the secondary address is empty, but the tertiary is not +func hasTertiaryWithNoSecondaryAddress(secondaryAddress *models.Address, tertiaryAddress *models.Address) bool { + return (models.IsAddressEmpty(secondaryAddress) && !models.IsAddressEmpty(tertiaryAddress)) +} + +/* Checks if a valid address sequence is being maintained. This will return false if the tertiary address is being updated while the secondary address remains empty +* + */ +func isMTOAddressUpdateSequenceValid(shipmentToUpdateWith *models.MTOShipment, currentShipment *models.MTOShipment) bool { + // if the incoming model has both fields, then we know the model will be updated with both secondary and tertiary addresses. therefore return true + if !models.IsAddressEmpty(shipmentToUpdateWith.SecondaryPickupAddress) && !models.IsAddressEmpty(shipmentToUpdateWith.TertiaryPickupAddress) { + return true + } + if !models.IsAddressEmpty(shipmentToUpdateWith.SecondaryDeliveryAddress) && !models.IsAddressEmpty(shipmentToUpdateWith.TertiaryDeliveryAddress) { + return true + } + if currentShipment.SecondaryPickupAddress == nil && shipmentToUpdateWith.TertiaryPickupAddress != nil { + return !hasTertiaryWithNoSecondaryAddress(currentShipment.SecondaryPickupAddress, shipmentToUpdateWith.TertiaryPickupAddress) + } + if currentShipment.SecondaryDeliveryAddress == nil && shipmentToUpdateWith.TertiaryDeliveryAddress != nil { + return !hasTertiaryWithNoSecondaryAddress(currentShipment.SecondaryDeliveryAddress, shipmentToUpdateWith.TertiaryDeliveryAddress) + } + // no addresses are being updated, so correct address sequence is maintained, return true + return true +} + +func MTOShipmentHasTertiaryAddressWithNoSecondaryAddressUpdate() validator { + return validatorFunc(func(appCtx appcontext.AppContext, newer *models.MTOShipment, older *models.MTOShipment) error { + verrs := validate.NewErrors() + if newer != nil && older != nil { + squenceIsValid := isMTOAddressUpdateSequenceValid(newer, older) + if !squenceIsValid { + verrs.Add("error validating mto shipment", "Shipment cannot have a third address without a second address present") + return apperror.NewInvalidInputError(newer.ID, nil, verrs, "Invalid input found while validating the MTO shipment") + } + } + return nil + }) +} +func MTOShipmentHasTertiaryAddressWithNoSecondaryAddressCreate() validator { + return validatorFunc(func(appCtx appcontext.AppContext, newer *models.MTOShipment, _ *models.MTOShipment) error { + verrs := validate.NewErrors() + if newer != nil { + squenceIsValid := isMTOShipmentAddressCreateSequenceValid(*newer) + if !squenceIsValid { + verrs.Add("error validating mto shipment", "Shipment cannot have a third address without a second address present") + return apperror.NewInvalidInputError(newer.ID, nil, verrs, "Invalid input found while validating the MTO shipment") + } + } + return nil + }) +} + // This function checks Prime specific validations on the model // It expects older to represent what's in the db and mtoShipment to represent the requested update // It updates mtoShipment accordingly if there are dependent updates like requiredDeliveryDate diff --git a/pkg/services/mto_shipment/rules_test.go b/pkg/services/mto_shipment/rules_test.go index e89166df36d..22b7fdf1a26 100644 --- a/pkg/services/mto_shipment/rules_test.go +++ b/pkg/services/mto_shipment/rules_test.go @@ -42,6 +42,108 @@ func (suite *MTOShipmentServiceSuite) TestUpdateValidations() { } }) + suite.Run("MTOShipmentHasTertiaryAddressWithNoSecondaryAddressUpdate Invalid add tertiary address without secondary", func() { + tertiaryDeliveryAddress := factory.BuildAddress(suite.DB(), nil, nil) + + minimalMove := models.MTOShipment{ + TertiaryDeliveryAddress: &tertiaryDeliveryAddress, + } + + mtoShipment_ThNScndP_address_Move := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + + checker := MTOShipmentHasTertiaryAddressWithNoSecondaryAddressUpdate() + err := checker.Validate(suite.AppContextForTest(), &minimalMove, &mtoShipment_ThNScndP_address_Move.MTOShipments[0]) + suite.Error(err) + }) + + suite.Run("MTOShipmentHasTertiaryAddressWithNoSecondaryAddressUpdate Valid add secondary address", func() { + secondaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + + minimalMove := models.MTOShipment{ + SecondaryPickupAddress: &secondaryPickupAddress, + } + + mtoShipment_ThNScndP_address_Move := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + + checker := MTOShipmentHasTertiaryAddressWithNoSecondaryAddressUpdate() + err := checker.Validate(suite.AppContextForTest(), &mtoShipment_ThNScndP_address_Move.MTOShipments[0], &minimalMove) + suite.NoError(err) + }) + + suite.Run("MTOShipmentHasTertiaryAddressWithNoSecondaryAddressUpdate Valid remove secondary address", func() { + secondaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + + oldMove := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + oldMove.MTOShipments[0].SecondaryPickupAddress = &secondaryPickupAddress + + newMove := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + + checker := MTOShipmentHasTertiaryAddressWithNoSecondaryAddressUpdate() + err := checker.Validate(suite.AppContextForTest(), &newMove.MTOShipments[0], &oldMove.MTOShipments[0]) + suite.NoError(err) + }) + + suite.Run("MTOShipmentHasTertiaryAddressWithNoSecondaryAddressUpdate Valid", func() { + tertiaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + + minimalMove := models.MTOShipment{ + TertiaryPickupAddress: &tertiaryPickupAddress, + } + + mtoShipment_ThNScndP_address_Move := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + + checker := MTOShipmentHasTertiaryAddressWithNoSecondaryAddressUpdate() + err := checker.Validate(suite.AppContextForTest(), &mtoShipment_ThNScndP_address_Move.MTOShipments[0], &minimalMove) + suite.NoError(err) + }) + + suite.Run("MTOShipmentHasTertiaryAddressWithNoSecondaryAddressCreate No Secondary Address With Tertiary Invalid", func() { + secondaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + TertiaryDestinationAddress := factory.BuildAddress(suite.DB(), nil, nil) + tertiaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + + mtoShipment_Valid_address := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + mtoShipment_Valid_address.MTOShipments[0].SecondaryPickupAddress = &secondaryPickupAddress + mtoShipment_Valid_address.MTOShipments[0].TertiaryPickupAddress = &tertiaryPickupAddress + mtoShipment_Valid_address.MTOShipments[0].TertiaryDeliveryAddress = &TertiaryDestinationAddress + + checker := MTOShipmentHasTertiaryAddressWithNoSecondaryAddressCreate() + err := checker.Validate(suite.AppContextForTest(), &mtoShipment_Valid_address.MTOShipments[0], nil) + suite.Error(err) + }) + + suite.Run("MTOShipmentHasTertiaryAddressWithNoSecondaryAddressCreate with Secondary Address Valid", func() { + secondaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + TertiaryDestinationAddress := factory.BuildAddress(suite.DB(), nil, nil) + tertiaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + + mtoShipment_Valid_address := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + mtoShipment_Valid_address.MTOShipments[0].SecondaryPickupAddress = &secondaryPickupAddress + mtoShipment_Valid_address.MTOShipments[0].TertiaryPickupAddress = &tertiaryPickupAddress + mtoShipment_Valid_address.MTOShipments[0].TertiaryDeliveryAddress = &TertiaryDestinationAddress + + checker := MTOShipmentHasTertiaryAddressWithNoSecondaryAddressCreate() + err := checker.Validate(suite.AppContextForTest(), &mtoShipment_Valid_address.MTOShipments[0], nil) + suite.Error(err) + }) + + suite.Run("MTOShipmentHasTertiaryAddressWithNoSecondaryAddressCreate Valid", func() { + SecondaryDestinationAddress := factory.BuildAddress(suite.DB(), nil, nil) + secondaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + TertiaryDestinationAddress := factory.BuildAddress(suite.DB(), nil, nil) + tertiaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + + mtoShipment_Valid_address := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + mtoShipment_Valid_address.MTOShipments[0].SecondaryPickupAddress = &secondaryPickupAddress + mtoShipment_Valid_address.MTOShipments[0].SecondaryDeliveryAddress = &SecondaryDestinationAddress + mtoShipment_Valid_address.MTOShipments[0].TertiaryPickupAddress = &tertiaryPickupAddress + mtoShipment_Valid_address.MTOShipments[0].TertiaryDeliveryAddress = &TertiaryDestinationAddress + + checker := MTOShipmentHasTertiaryAddressWithNoSecondaryAddressCreate() + err := checker.Validate(suite.AppContextForTest(), &mtoShipment_Valid_address.MTOShipments[0], nil) + suite.NoError(err) + }) + suite.Run("checkAvailToPrime", func() { appCtx := suite.AppContextForTest() diff --git a/pkg/services/mto_shipment/shipment_approver.go b/pkg/services/mto_shipment/shipment_approver.go index 4b8df67134a..52e849e469b 100644 --- a/pkg/services/mto_shipment/shipment_approver.go +++ b/pkg/services/mto_shipment/shipment_approver.go @@ -77,6 +77,13 @@ func (f *shipmentApprover) ApproveShipment(appCtx appcontext.AppContext, shipmen } } + // create international shipment service items + if shipment.ShipmentType == models.MTOShipmentTypeHHG && shipment.MarketCode == models.MarketCodeInternational { + err := models.CreateApprovedServiceItemsForShipment(appCtx.DB(), shipment) + if err != nil { + return shipment, err + } + } transactionError := appCtx.NewTransaction(func(txnAppCtx appcontext.AppContext) error { verrs, err := txnAppCtx.DB().ValidateAndSave(shipment) if verrs != nil && verrs.HasAny() { diff --git a/pkg/services/mto_shipment/shipment_approver_test.go b/pkg/services/mto_shipment/shipment_approver_test.go index a20a440c8f5..943cd4edb72 100644 --- a/pkg/services/mto_shipment/shipment_approver_test.go +++ b/pkg/services/mto_shipment/shipment_approver_test.go @@ -2,6 +2,7 @@ package mtoshipment import ( "math" + "slices" "time" "github.com/gofrs/uuid" @@ -13,6 +14,7 @@ import ( "github.com/transcom/mymove/pkg/etag" "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" + "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/ghcrateengine" @@ -188,6 +190,71 @@ func (suite *MTOShipmentServiceSuite) createApproveShipmentSubtestData() (subtes } func (suite *MTOShipmentServiceSuite) TestApproveShipment() { + suite.Run("If the international mtoShipment is approved successfully it should create pre approved mtoServiceItems", func() { + internationalShipment := factory.BuildMTOShipment(suite.AppContextForTest().DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVED, + }, + }, + { + Model: models.Address{ + StreetAddress1: "Tester Address", + City: "Des Moines", + State: "IA", + PostalCode: "50314", + IsOconus: models.BoolPointer(false), + }, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: models.MTOShipment{ + MarketCode: "i", + Status: models.MTOShipmentStatusSubmitted, + }, + }, + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "Anchorage", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + }, + Type: &factory.Addresses.DeliveryAddress, + }, + }, nil) + internationalShipmentEtag := etag.GenerateEtag(internationalShipment.UpdatedAt) + + shipmentRouter := NewShipmentRouter() + var serviceItemCreator services.MTOServiceItemCreator + var planner route.Planner + var moveWeights services.MoveWeights + + // Approve international shipment + shipmentApprover := NewShipmentApprover(shipmentRouter, serviceItemCreator, planner, moveWeights) + _, err := shipmentApprover.ApproveShipment(suite.AppContextForTest(), internationalShipment.ID, internationalShipmentEtag) + suite.NoError(err) + + // Get created pre approved service items + var serviceItems []models.MTOServiceItem + err2 := suite.AppContextForTest().DB().EagerPreload("ReService").Where("mto_shipment_id = ?", internationalShipment.ID).Order("created_at asc").All(&serviceItems) + suite.NoError(err2) + + expectedReserviceCodes := []models.ReServiceCode{ + models.ReServiceCodePOEFSC, + models.ReServiceCodeISLH, + models.ReServiceCodeIHPK, + models.ReServiceCodeIHUPK, + } + + suite.Equal(4, len(serviceItems)) + for i := 0; i < len(serviceItems); i++ { + actualReServiceCode := serviceItems[i].ReService.Code + suite.True(slices.Contains(expectedReserviceCodes, actualReServiceCode)) + } + }) + suite.Run("If the mtoShipment is approved successfully it should create approved mtoServiceItems", func() { subtestData := suite.createApproveShipmentSubtestData() appCtx := subtestData.appCtx diff --git a/pkg/services/office_user.go b/pkg/services/office_user.go index 9e7a17d6bbe..94e5410d3ab 100644 --- a/pkg/services/office_user.go +++ b/pkg/services/office_user.go @@ -22,7 +22,9 @@ type OfficeUserFetcher interface { //go:generate mockery --name OfficeUserFetcherPop type OfficeUserFetcherPop interface { FetchOfficeUserByID(appCtx appcontext.AppContext, id uuid.UUID) (models.OfficeUser, error) + FetchOfficeUserByIDWithTransportationOfficeAssignments(appCtx appcontext.AppContext, id uuid.UUID) (models.OfficeUser, error) FetchOfficeUsersByRoleAndOffice(appCtx appcontext.AppContext, role roles.RoleType, officeID uuid.UUID) ([]models.OfficeUser, error) + FetchSafetyMoveOfficeUsersByRoleAndOffice(appCtx appcontext.AppContext, role roles.RoleType, officeID uuid.UUID) ([]models.OfficeUser, error) } // OfficeUserGblocFetcher is the exported interface for fetching the GBLOC of the @@ -40,9 +42,9 @@ type OfficeUserCreator interface { CreateOfficeUser(appCtx appcontext.AppContext, user *models.OfficeUser, transportationIDFilter []QueryFilter) (*models.OfficeUser, *validate.Errors, error) } -// OfficeUserUpdater is the exported interface for creating an office user +// OfficeUserUpdater is the exported interface for updating an office user // //go:generate mockery --name OfficeUserUpdater type OfficeUserUpdater interface { - UpdateOfficeUser(appCtx appcontext.AppContext, id uuid.UUID, payload *adminmessages.OfficeUserUpdate) (*models.OfficeUser, *validate.Errors, error) + UpdateOfficeUser(appCtx appcontext.AppContext, id uuid.UUID, payload *adminmessages.OfficeUserUpdate, primaryTransportationOfficeId uuid.UUID) (*models.OfficeUser, *validate.Errors, error) } diff --git a/pkg/services/office_user/office_user_fetcher.go b/pkg/services/office_user/office_user_fetcher.go index c5722f58111..78f310b8a5e 100644 --- a/pkg/services/office_user/office_user_fetcher.go +++ b/pkg/services/office_user/office_user_fetcher.go @@ -56,6 +56,21 @@ func (o *officeUserFetcherPop) FetchOfficeUserByID(appCtx appcontext.AppContext, return officeUser, err } +func (o *officeUserFetcherPop) FetchOfficeUserByIDWithTransportationOfficeAssignments(appCtx appcontext.AppContext, id uuid.UUID) (models.OfficeUser, error) { + var officeUser models.OfficeUser + err := appCtx.DB().Eager("TransportationOffice", "TransportationOfficeAssignments", "TransportationOfficeAssignments.TransportationOffice").Find(&officeUser, id) + if err != nil { + switch err { + case sql.ErrNoRows: + return models.OfficeUser{}, apperror.NewNotFoundError(id, "looking for OfficeUser") + default: + return models.OfficeUser{}, apperror.NewQueryError("OfficeUser", err, "") + } + } + + return officeUser, err +} + // Fetch office users of the same role within a gbloc, for assignment purposes func (o *officeUserFetcherPop) FetchOfficeUsersByRoleAndOffice(appCtx appcontext.AppContext, role roles.RoleType, officeID uuid.UUID) ([]models.OfficeUser, error) { var officeUsers []models.OfficeUser @@ -84,6 +99,37 @@ func (o *officeUserFetcherPop) FetchOfficeUsersByRoleAndOffice(appCtx appcontext return officeUsers, nil } +func (o *officeUserFetcherPop) FetchSafetyMoveOfficeUsersByRoleAndOffice(appCtx appcontext.AppContext, role roles.RoleType, officeID uuid.UUID) ([]models.OfficeUser, error) { + var officeUsers []models.OfficeUser + + err := appCtx.DB().EagerPreload( + "User", + "User.Roles", + "User.Privileges", + "TransportationOffice", + "TransportationOffice.Gbloc", + ). + Join("users", "users.id = office_users.user_id"). + Join("users_roles", "users.id = users_roles.user_id"). + Join("roles", "users_roles.role_id = roles.id"). + LeftJoin("users_privileges", "users.id = users_privileges.user_id"). + LeftJoin("privileges", "privileges.id = users_privileges.privilege_id"). + Where("transportation_office_id = ?", officeID). + Where("role_type = ?", role). + Where("users_roles.deleted_at IS NULL"). + Where("office_users.active = TRUE"). + Where("users_privileges.deleted_at IS NULL"). + Where("privileges.privilege_type = 'safety'"). + Order("last_name asc"). + All(&officeUsers) + + if err != nil { + return nil, err + } + + return officeUsers, nil +} + // NewOfficeUserFetcherPop return an implementation of the OfficeUserFetcherPop interface func NewOfficeUserFetcherPop() services.OfficeUserFetcherPop { return &officeUserFetcherPop{} diff --git a/pkg/services/office_user/office_user_updater.go b/pkg/services/office_user/office_user_updater.go index 8eeb1326533..63320541f36 100644 --- a/pkg/services/office_user/office_user_updater.go +++ b/pkg/services/office_user/office_user_updater.go @@ -16,7 +16,7 @@ type officeUserUpdater struct { } // UpdateOfficeUser updates an office user -func (o *officeUserUpdater) UpdateOfficeUser(appCtx appcontext.AppContext, id uuid.UUID, payload *adminmessages.OfficeUserUpdate) (*models.OfficeUser, *validate.Errors, error) { +func (o *officeUserUpdater) UpdateOfficeUser(appCtx appcontext.AppContext, id uuid.UUID, payload *adminmessages.OfficeUserUpdate, primaryTransportationOfficeID uuid.UUID) (*models.OfficeUser, *validate.Errors, error) { var foundUser models.OfficeUser filters := []services.QueryFilter{query.NewQueryFilter("id", "=", id.String())} err := o.builder.FetchOne(appCtx, &foundUser, filters) @@ -46,8 +46,8 @@ func (o *officeUserUpdater) UpdateOfficeUser(appCtx appcontext.AppContext, id uu foundUser.Active = *payload.Active } - transportationOfficeID := payload.TransportationOfficeID.String() - if transportationOfficeID != uuid.Nil.String() && transportationOfficeID != "" { + transportationOfficeID := primaryTransportationOfficeID.String() + if primaryTransportationOfficeID != uuid.Nil && transportationOfficeID != uuid.Nil.String() && transportationOfficeID != "" { transportationIDFilter := []services.QueryFilter{ query.NewQueryFilter("id", "=", transportationOfficeID), } diff --git a/pkg/services/office_user/office_user_updater_test.go b/pkg/services/office_user/office_user_updater_test.go index a2a61114fc3..e1057af02cf 100644 --- a/pkg/services/office_user/office_user_updater_test.go +++ b/pkg/services/office_user/office_user_updater_test.go @@ -20,14 +20,20 @@ func (suite *OfficeUserServiceSuite) TestUpdateOfficeUser() { suite.Run("If the user is updated successfully it should be returned", func() { officeUser := factory.BuildOfficeUser(suite.DB(), nil, nil) transportationOffice := factory.BuildDefaultTransportationOffice(suite.DB()) + primaryOffice := true firstName := "Lea" payload := &adminmessages.OfficeUserUpdate{ - FirstName: &firstName, - TransportationOfficeID: strfmt.UUID(transportationOffice.ID.String()), + FirstName: &firstName, + TransportationOfficeAssignments: []*adminmessages.OfficeUserTransportationOfficeAssignment{ + { + TransportationOfficeID: strfmt.UUID(transportationOffice.ID.String()), + PrimaryOffice: &primaryOffice, + }, + }, } - updatedOfficeUser, verrs, err := updater.UpdateOfficeUser(suite.AppContextForTest(), officeUser.ID, payload) + updatedOfficeUser, verrs, err := updater.UpdateOfficeUser(suite.AppContextForTest(), officeUser.ID, payload, uuid.FromStringOrNil(transportationOffice.ID.String())) suite.NoError(err) suite.Nil(verrs) suite.Equal(updatedOfficeUser.ID.String(), officeUser.ID.String()) @@ -41,7 +47,7 @@ func (suite *OfficeUserServiceSuite) TestUpdateOfficeUser() { suite.Run("If we are provided an office user that doesn't exist, the create should fail", func() { payload := &adminmessages.OfficeUserUpdate{} - _, _, err := updater.UpdateOfficeUser(suite.AppContextForTest(), uuid.FromStringOrNil("00000000-0000-0000-0000-000000000001"), payload) + _, _, err := updater.UpdateOfficeUser(suite.AppContextForTest(), uuid.FromStringOrNil("00000000-0000-0000-0000-000000000001"), payload, uuid.Nil) suite.Error(err) suite.Equal(sql.ErrNoRows.Error(), err.Error()) }) @@ -56,11 +62,18 @@ func (suite *OfficeUserServiceSuite) TestUpdateOfficeUser() { }, }, }, nil) + primaryOffice := true + payload := &adminmessages.OfficeUserUpdate{ - TransportationOfficeID: strfmt.UUID("00000000-0000-0000-0000-000000000001"), + TransportationOfficeAssignments: []*adminmessages.OfficeUserTransportationOfficeAssignment{ + { + TransportationOfficeID: strfmt.UUID("00000000-0000-0000-0000-000000000001"), + PrimaryOffice: &primaryOffice, + }, + }, } - _, _, err := updater.UpdateOfficeUser(suite.AppContextForTest(), officeUser.ID, payload) + _, _, err := updater.UpdateOfficeUser(suite.AppContextForTest(), officeUser.ID, payload, uuid.FromStringOrNil("00000000-0000-0000-0000-000000000001")) suite.Error(err) suite.Equal(sql.ErrNoRows.Error(), err.Error()) }) diff --git a/pkg/services/order/order_fetcher.go b/pkg/services/order/order_fetcher.go index 8d5976fe855..e11d172ca37 100644 --- a/pkg/services/order/order_fetcher.go +++ b/pkg/services/order/order_fetcher.go @@ -306,13 +306,18 @@ func (f orderFetcher) ListOrders(appCtx appcontext.AppContext, officeUserID uuid return moves, count, nil } +// TODO: Update query to select distinct duty locations func (f orderFetcher) ListAllOrderLocations(appCtx appcontext.AppContext, officeUserID uuid.UUID, params *services.ListOrderParams) ([]models.Move, error) { var moves []models.Move - var transportationOffice models.TransportationOffice - // select the GBLOC associated with the transportation office of the session's current office user - err := appCtx.DB().Q(). - Join("office_users", "transportation_offices.id = office_users.transportation_office_id"). - Where("office_users.id = ?", officeUserID).First(&transportationOffice) + var err error + var officeUserGbloc string + + if params.ViewAsGBLOC != nil { + officeUserGbloc = *params.ViewAsGBLOC + } else { + gblocFetcher := officeuser.NewOfficeUserGblocFetcher() + officeUserGbloc, err = gblocFetcher.FetchGblocForOfficeUser(appCtx, officeUserID) + } if err != nil { return []models.Move{}, err @@ -323,14 +328,6 @@ func (f orderFetcher) ListAllOrderLocations(appCtx appcontext.AppContext, office appCtx.Logger().Error("Error retreiving user privileges", zap.Error(err)) } - officeUserGbloc := transportationOffice.Gbloc - - // Alright let's build our query based on the filters we got from the handler. These use the FilterOption type above. - // Essentially these are private functions that return query objects that we can mash together to form a complete - // query from modular parts. - - // The services counselor queue does not base exclude marine results. - // Only the TIO and TOO queues should. needsCounseling := false if len(params.Status) > 0 { for _, status := range params.Status { @@ -352,10 +349,8 @@ func (f orderFetcher) ListAllOrderLocations(appCtx appcontext.AppContext, office // If the user is associated with the USMC GBLOC we want to show them ALL the USMC moves, so let's override here. // We also only want to do the gbloc filtering thing if we aren't a USMC user, which we cover with the else. // var gblocQuery QueryOption - var gblocToFilterBy *string if officeUserGbloc == "USMC" && !needsCounseling { branchQuery = branchFilter(models.StringPointer(string(models.AffiliationMARINES)), needsCounseling, ppmCloseoutGblocs) - gblocToFilterBy = &officeUserGbloc } // We need to use three different GBLOC filter queries because: @@ -368,13 +363,13 @@ func (f orderFetcher) ListAllOrderLocations(appCtx appcontext.AppContext, office // does not populate the pickup address field. var gblocQuery QueryOption if ppmCloseoutGblocs { - gblocQuery = gblocFilterForPPMCloseoutForNavyMarineAndCG(gblocToFilterBy) + gblocQuery = gblocFilterForPPMCloseoutForNavyMarineAndCG(&officeUserGbloc) } else if needsCounseling { - gblocQuery = gblocFilterForSC(gblocToFilterBy) + gblocQuery = gblocFilterForSC(&officeUserGbloc) } else if params.NeedsPPMCloseout != nil && *params.NeedsPPMCloseout { - gblocQuery = gblocFilterForSCinArmyAirForce(gblocToFilterBy) + gblocQuery = gblocFilterForSCinArmyAirForce(&officeUserGbloc) } else { - gblocQuery = gblocFilterForTOO(gblocToFilterBy) + gblocQuery = gblocFilterForTOO(&officeUserGbloc) } moveStatusQuery := moveStatusFilter(params.Status) // Adding to an array so we can iterate over them and apply the filters after the query structure is set below @@ -396,6 +391,7 @@ func (f orderFetcher) ListAllOrderLocations(appCtx appcontext.AppContext, office InnerJoin("ppm_shipments", "ppm_shipments.shipment_id = mto_shipments.id"). InnerJoin("duty_locations as origin_dl", "orders.origin_duty_location_id = origin_dl.id"). LeftJoin("duty_locations as dest_dl", "dest_dl.id = orders.new_duty_location_id"). + LeftJoin("transportation_offices as closeout_to", "closeout_to.id = moves.closeout_office_id"). LeftJoin("office_users", "office_users.id = moves.locked_by"). Where("show = ?", models.BoolPointer(true)) @@ -425,6 +421,7 @@ func (f orderFetcher) ListAllOrderLocations(appCtx appcontext.AppContext, office // and we don't want it to get hidden from services counselors. LeftJoin("move_to_gbloc", "move_to_gbloc.move_id = moves.id"). LeftJoin("duty_locations as dest_dl", "dest_dl.id = orders.new_duty_location_id"). + LeftJoin("transportation_offices as closeout_to", "closeout_to.id = moves.closeout_office_id"). LeftJoin("office_users", "office_users.id = moves.locked_by"). Where("show = ?", models.BoolPointer(true)) diff --git a/pkg/services/order/order_fetcher_test.go b/pkg/services/order/order_fetcher_test.go index 71ec964c8ce..a71a18f89e9 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/roles" "github.com/transcom/mymove/pkg/services" moveservice "github.com/transcom/mymove/pkg/services/move" + officeuserservice "github.com/transcom/mymove/pkg/services/office_user" "github.com/transcom/mymove/pkg/testdatagen" ) @@ -1747,7 +1748,7 @@ func (suite *OrderServiceSuite) TestListOrdersForTOOWithPPM() { suite.Len(moves, 1) } -func (suite *OrderServiceSuite) TestListOrdersForHQWithViewAsParam() { +func (suite *OrderServiceSuite) TestListOrdersWithViewAsGBLOCParam() { var hqOfficeUser models.OfficeUser var hqOfficeUserAGFM models.OfficeUser @@ -1845,7 +1846,7 @@ func (suite *OrderServiceSuite) TestListOrdersForHQWithViewAsParam() { suite.Equal(expectedMove1.Locator, moves[0].Locator) suite.Equal(expectedMove2.Locator, moves[1].Locator) - // Expect the same results with a ViewAsGBLOC that equals the user's default GBLOC + // Expect the same results with a ViewAsGBLOC that equals the user's default GBLOC, KKFA params = services.ListOrderParams{Sort: models.StringPointer("locator"), Order: models.StringPointer("asc"), ViewAsGBLOC: models.StringPointer("KKFA")} moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&hqSession), hqOfficeUser.ID, roles.RoleTypeTOO, ¶ms) suite.NoError(err) @@ -2021,6 +2022,142 @@ func (suite *OrderServiceSuite) TestListAllOrderLocations() { }) } +func (suite *OrderServiceSuite) TestListAllOrderLocationsWithViewAsGBLOCParam() { + suite.Run("returns a list of all order locations in the current users queue", func() { + orderFetcher := NewOrderFetcher() + officeUserFetcher := officeuserservice.NewOfficeUserFetcherPop() + movesContainOriginDutyLocation := func(moves models.Moves, keyword string) func() (success bool) { + return func() (success bool) { + for _, record := range moves { + if strings.Contains(record.Orders.OriginDutyLocation.Name, keyword) { + return true + } + } + return false + } + } + + // Create SC office user with a default transportation office in the AGFM GBLOC + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{ + Name: "Fort Punxsutawney", + Gbloc: "AGFM", + }, + }, + }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + // Add a secondary GBLOC to the above office user, this should default to KKFA + factory.BuildAlternateTransportationOfficeAssignment(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + ID: officeUser.ID, + }, + LinkOnly: true, + }, + }, nil) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + // Create two default moves with shipment, should be in KKFA and have the status SUBMITTED + KKFAMove1 := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + KKFAMove2 := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + + // Create third move with the same origin duty location as one of the above + KKFAMove3 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + ID: KKFAMove2.Orders.OriginDutyLocation.ID, + }, + Type: &factory.DutyLocations.OriginDutyLocation, + LinkOnly: true, + }, + }, nil) + + officeUser, _ = officeUserFetcher.FetchOfficeUserByIDWithTransportationOfficeAssignments(suite.AppContextForTest(), officeUser.ID) + + // Confirm office user has the desired transportation office assignments + suite.Equal("AGFM", officeUser.TransportationOffice.Gbloc) + if officeUser.TransportationOfficeAssignments[0].TransportationOffice.Gbloc == "AGFM" { + suite.Equal("AGFM", officeUser.TransportationOfficeAssignments[0].TransportationOffice.Gbloc) + suite.Equal(true, *officeUser.TransportationOfficeAssignments[0].PrimaryOffice) + suite.Equal("KKFA", officeUser.TransportationOfficeAssignments[1].TransportationOffice.Gbloc) + suite.Equal(false, *officeUser.TransportationOfficeAssignments[1].PrimaryOffice) + } else { + suite.Equal("KKFA", officeUser.TransportationOfficeAssignments[0].TransportationOffice.Gbloc) + suite.Equal(false, *officeUser.TransportationOfficeAssignments[0].PrimaryOffice) + suite.Equal("AGFM", officeUser.TransportationOfficeAssignments[1].TransportationOffice.Gbloc) + suite.Equal(true, *officeUser.TransportationOfficeAssignments[1].PrimaryOffice) + } + + // Confirm the factory created moves have the desired GBLOCS, 3x KKFA, + suite.Equal("KKFA", *KKFAMove1.Orders.OriginDutyLocationGBLOC) + suite.Equal("KKFA", *KKFAMove2.Orders.OriginDutyLocationGBLOC) + suite.Equal("KKFA", *KKFAMove3.Orders.OriginDutyLocationGBLOC) + + // Fetch and check secondary GBLOC + KKFA := "KKFA" + params := services.ListOrderParams{ + ViewAsGBLOC: &KKFA, + } + KKFAmoves, err := orderFetcher.ListAllOrderLocations(suite.AppContextWithSessionForTest(&session), officeUser.ID, ¶ms) + + suite.FatalNoError(err) + // This value should be updated to 3 if ListAllOrderLocations is updated to return distinct locations + suite.Equal(3, len(KKFAmoves)) + + suite.Equal("KKFA", *KKFAmoves[0].Orders.OriginDutyLocationGBLOC) + suite.Equal("KKFA", *KKFAmoves[1].Orders.OriginDutyLocationGBLOC) + suite.Equal("KKFA", *KKFAmoves[2].Orders.OriginDutyLocationGBLOC) + + suite.Condition(movesContainOriginDutyLocation(KKFAmoves, KKFAMove1.Orders.OriginDutyLocation.Name), "Should contain first KKFA move's origin duty location") + suite.Condition(movesContainOriginDutyLocation(KKFAmoves, KKFAMove2.Orders.OriginDutyLocation.Name), "Should contain second KKFA move's origin duty location") + suite.Condition(movesContainOriginDutyLocation(KKFAmoves, KKFAMove3.Orders.OriginDutyLocation.Name), "Should contain third KKFA move's origin duty location") + }) +} + +func (suite *OrderServiceSuite) TestOriginDutyLocationFilter() { + var session auth.Session + 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 + } + officeUser, expectedMove, session = setupTestData() + }) + locationName := expectedMove.Orders.OriginDutyLocation.Name + suite.Run("Returns orders matching full originDutyLocation name filter", func() { + 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)) + suite.Equal(locationName, string(expectedMoves[0].Orders.OriginDutyLocation.Name)) + }) + + suite.Run("Returns orders matching partial originDutyLocation name filter", func() { + //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, " ")}) + suite.NoError(err) + suite.Equal(1, len(expectedMoves)) + suite.Equal(locationName, string(expectedMoves[0].Orders.OriginDutyLocation.Name)) + }) +} + func (suite *OrderServiceSuite) TestListOrdersFilteredByCustomerName() { serviceMemberFirstName := "Margaret" serviceMemberLastName := "Starlight" @@ -2147,41 +2284,3 @@ func (suite *OrderServiceSuite) TestListOrdersFilteredByCustomerName() { suite.Equal(0, len(moves)) }) } - -func (suite *OrderServiceSuite) TestOriginDutyLocationFilter() { - var session auth.Session - 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 - } - officeUser, expectedMove, session = setupTestData() - }) - locationName := expectedMove.Orders.OriginDutyLocation.Name - suite.Run("Returns orders matching full originDutyLocation name filter", func() { - 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)) - suite.Equal(locationName, string(expectedMoves[0].Orders.OriginDutyLocation.Name)) - }) - - suite.Run("Returns orders matching partial originDutyLocation name filter", func() { - //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, " ")}) - suite.NoError(err) - suite.Equal(1, len(expectedMoves)) - suite.Equal(locationName, string(expectedMoves[0].Orders.OriginDutyLocation.Name)) - }) -} diff --git a/pkg/services/order/order_updater.go b/pkg/services/order/order_updater.go index 2645a0bcea1..38c991a112a 100644 --- a/pkg/services/order/order_updater.go +++ b/pkg/services/order/order_updater.go @@ -87,7 +87,7 @@ func (f *orderUpdater) UpdateAllowanceAsTOO(appCtx appcontext.AppContext, orderI return &models.Order{}, uuid.Nil, apperror.NewPreconditionFailedError(orderID, query.StaleIdentifierError{StaleIdentifier: eTag}) } - orderToUpdate := allowanceFromTOOPayload(*order, payload) + orderToUpdate := allowanceFromTOOPayload(appCtx, *order, payload) return f.updateOrder(appCtx, orderToUpdate) } @@ -104,7 +104,7 @@ func (f *orderUpdater) UpdateAllowanceAsCounselor(appCtx appcontext.AppContext, return &models.Order{}, uuid.Nil, apperror.NewPreconditionFailedError(orderID, query.StaleIdentifierError{StaleIdentifier: eTag}) } - orderToUpdate := allowanceFromCounselingPayload(*order, payload) + orderToUpdate := allowanceFromCounselingPayload(appCtx, *order, payload) return f.updateOrder(appCtx, orderToUpdate) } @@ -177,7 +177,7 @@ func (f *orderUpdater) findOrderWithAmendedOrders(appCtx appcontext.AppContext, return &order, nil } -func orderFromTOOPayload(_ appcontext.AppContext, existingOrder models.Order, payload ghcmessages.UpdateOrderPayload) models.Order { +func orderFromTOOPayload(appCtx appcontext.AppContext, existingOrder models.Order, payload ghcmessages.UpdateOrderPayload) models.Order { order := existingOrder // update order origin duty location @@ -257,7 +257,7 @@ func orderFromTOOPayload(_ appcontext.AppContext, existingOrder models.Order, pa if payload.Grade != nil { order.Grade = (*internalmessages.OrderPayGrade)(payload.Grade) // Calculate new DBWeightAuthorized based on the new grade - weightAllotment := models.GetWeightAllotment(*order.Grade) + weightAllotment := models.GetWeightAllotment(*order.Grade, order.OrdersType) weight := weightAllotment.TotalWeightSelf // Payload does not have this information, retrieve dependents from the existing order if existingOrder.HasDependents && *order.Entitlement.DependentsAuthorized { @@ -390,7 +390,7 @@ 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) + weightAllotment := models.GetWeightAllotment(*order.Grade, order.OrdersType) weight := weightAllotment.TotalWeightSelf // Payload does not have this information, retrieve dependents from the existing order if existingOrder.HasDependents && *order.Entitlement.DependentsAuthorized { @@ -403,7 +403,7 @@ func orderFromCounselingPayload(existingOrder models.Order, payload ghcmessages. return order } -func allowanceFromTOOPayload(existingOrder models.Order, payload ghcmessages.UpdateAllowancePayload) models.Order { +func allowanceFromTOOPayload(appCtx appcontext.AppContext, existingOrder models.Order, payload ghcmessages.UpdateAllowancePayload) models.Order { order := existingOrder if payload.ProGearWeight != nil { @@ -430,7 +430,7 @@ func allowanceFromTOOPayload(existingOrder models.Order, payload ghcmessages.Upd } // Calculate new DBWeightAuthorized based on the new grade - weightAllotment := models.GetWeightAllotment(*order.Grade) + weightAllotment := models.GetWeightAllotment(*order.Grade, order.OrdersType) weight := weightAllotment.TotalWeightSelf // Payload does not have this information, retrieve dependents from the existing order if existingOrder.HasDependents && *payload.DependentsAuthorized { @@ -456,10 +456,54 @@ func allowanceFromTOOPayload(existingOrder models.Order, payload ghcmessages.Upd order.Entitlement.GunSafe = *payload.GunSafe } + if payload.AccompaniedTour != nil { + order.Entitlement.AccompaniedTour = payload.AccompaniedTour + } + + if payload.DependentsUnderTwelve != nil { + order.Entitlement.DependentsUnderTwelve = models.IntPointer(int(*payload.DependentsUnderTwelve)) + } + + if payload.DependentsTwelveAndOver != nil { + order.Entitlement.DependentsTwelveAndOver = models.IntPointer(int(*payload.DependentsTwelveAndOver)) + } + + if order.OriginDutyLocationID != nil { + var originDutyLocation models.DutyLocation + originDutyLocation, err := models.FetchDutyLocation(appCtx.DB(), *order.OriginDutyLocationID) + if err == nil { + order.OriginDutyLocationID = &originDutyLocation.ID + order.OriginDutyLocation = &originDutyLocation + } + } + + 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 + } + } + + // Recalculate UB allowance of order entitlement + hasDepedents := false + if order.Entitlement != nil && order.Entitlement.DependentsAuthorized != nil && *order.Entitlement.DependentsAuthorized { + hasDepedents = true + } else if order.HasDependents { + hasDepedents = true + } + 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 + } + } + return order } -func allowanceFromCounselingPayload(existingOrder models.Order, payload ghcmessages.CounselingUpdateAllowancePayload) models.Order { +func allowanceFromCounselingPayload(appCtx appcontext.AppContext, existingOrder models.Order, payload ghcmessages.CounselingUpdateAllowancePayload) models.Order { order := existingOrder if payload.ProGearWeight != nil { @@ -486,7 +530,7 @@ func allowanceFromCounselingPayload(existingOrder models.Order, payload ghcmessa } // Calculate new DBWeightAuthorized based on the new grade - weightAllotment := models.GetWeightAllotment(*order.Grade) + weightAllotment := models.GetWeightAllotment(*order.Grade, order.OrdersType) weight := weightAllotment.TotalWeightSelf // Payload does not have this information, retrieve dependents from the existing order if existingOrder.HasDependents && *payload.DependentsAuthorized { @@ -512,6 +556,44 @@ func allowanceFromCounselingPayload(existingOrder models.Order, payload ghcmessa order.Entitlement.GunSafe = *payload.GunSafe } + if payload.AccompaniedTour != nil { + order.Entitlement.AccompaniedTour = payload.AccompaniedTour + } + + if payload.DependentsUnderTwelve != nil { + order.Entitlement.DependentsUnderTwelve = models.IntPointer(int(*payload.DependentsUnderTwelve)) + } + + if payload.DependentsTwelveAndOver != nil { + order.Entitlement.DependentsTwelveAndOver = models.IntPointer(int(*payload.DependentsTwelveAndOver)) + } + + if order.OriginDutyLocationID != nil { + var originDutyLocation models.DutyLocation + originDutyLocation, err := models.FetchDutyLocation(appCtx.DB(), *order.OriginDutyLocationID) + if err == nil { + order.OriginDutyLocationID = &originDutyLocation.ID + order.OriginDutyLocation = &originDutyLocation + } + } + + 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 + } + } + + // 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 + } + } + return order } @@ -632,14 +714,6 @@ func updateOrderInTx(appCtx appcontext.AppContext, order models.Order, checks .. } } - // update entitlement - if order.Entitlement != nil { - verrs, err = appCtx.DB().ValidateAndUpdate(order.Entitlement) - if e := handleError(order.ID, verrs, err); e != nil { - return nil, e - } - } - if order.NewDutyLocationID != uuid.Nil { // TODO refactor to use service objects to fetch duty location var newDutyLocation models.DutyLocation @@ -668,6 +742,22 @@ func updateOrderInTx(appCtx appcontext.AppContext, order models.Order, checks .. order.DestinationGBLOC = &newDestinationGBLOC.GBLOC } + // 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, order.Entitlement.DependentsAuthorized, order.Entitlement.AccompaniedTour, order.Entitlement.DependentsUnderTwelve, order.Entitlement.DependentsTwelveAndOver) + if err == nil { + order.Entitlement.UBAllowance = &unaccompaniedBaggageAllowance + } + } + + // update entitlement + if order.Entitlement != nil { + verrs, err = appCtx.DB().ValidateAndUpdate(order.Entitlement) + if e := handleError(order.ID, verrs, err); e != nil { + return nil, e + } + } + verrs, err = appCtx.DB().ValidateAndUpdate(&order) if e := handleError(order.ID, verrs, err); e != nil { return nil, e diff --git a/pkg/services/order/order_updater_test.go b/pkg/services/order/order_updater_test.go index 5e3c3495fde..1bf72866552 100644 --- a/pkg/services/order/order_updater_test.go +++ b/pkg/services/order/order_updater_test.go @@ -606,6 +606,51 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsTOO() { suite.Equal(*updatedOrder.Entitlement.DBAuthorizedWeight, 16000) }) + suite.Run("Updates the allowance when all OCONUS fields are valid with dependents", func() { + moveRouter := move.NewMoveRouter() + orderUpdater := NewOrderUpdater(moveRouter) + order := factory.BuildServiceCounselingCompletedMove(suite.DB(), nil, nil).Orders + + grade := ghcmessages.GradeO5 + affiliation := ghcmessages.AffiliationAIRFORCE + ocie := false + proGearWeight := models.Int64Pointer(100) + proGearWeightSpouse := models.Int64Pointer(10) + rmeWeight := models.Int64Pointer(10000) + eTag := etag.GenerateEtag(order.UpdatedAt) + + payload := ghcmessages.UpdateAllowancePayload{ + Agency: &affiliation, + DependentsAuthorized: models.BoolPointer(true), + Grade: &grade, + OrganizationalClothingAndIndividualEquipment: &ocie, + ProGearWeight: proGearWeight, + ProGearWeightSpouse: proGearWeightSpouse, + RequiredMedicalEquipmentWeight: rmeWeight, + AccompaniedTour: models.BoolPointer(true), + DependentsTwelveAndOver: models.Int64Pointer(2), + DependentsUnderTwelve: models.Int64Pointer(4), + } + + updatedOrder, _, err := orderUpdater.UpdateAllowanceAsTOO(suite.AppContextForTest(), order.ID, payload, eTag) + suite.NoError(err) + + var orderInDB models.Order + err = suite.DB().Find(&orderInDB, order.ID) + + suite.NoError(err) + suite.Equal(order.ID.String(), updatedOrder.ID.String()) + suite.Equal(payload.DependentsAuthorized, updatedOrder.Entitlement.DependentsAuthorized) + suite.Equal(*payload.ProGearWeight, int64(updatedOrder.Entitlement.ProGearWeight)) + suite.Equal(*payload.ProGearWeightSpouse, int64(updatedOrder.Entitlement.ProGearWeightSpouse)) + suite.Equal(*payload.RequiredMedicalEquipmentWeight, int64(updatedOrder.Entitlement.RequiredMedicalEquipmentWeight)) + suite.Equal(*payload.OrganizationalClothingAndIndividualEquipment, updatedOrder.Entitlement.OrganizationalClothingAndIndividualEquipment) + suite.Equal(*updatedOrder.Entitlement.DBAuthorizedWeight, 16000) + suite.Equal(*payload.DependentsTwelveAndOver, int64(*updatedOrder.Entitlement.DependentsTwelveAndOver)) + suite.Equal(*payload.AccompaniedTour, *updatedOrder.Entitlement.AccompaniedTour) + suite.Equal(*payload.DependentsUnderTwelve, int64(*updatedOrder.Entitlement.DependentsUnderTwelve)) + }) + suite.Run("Updates the allowance when all fields are valid with dependents", func() { moveRouter := move.NewMoveRouter() orderUpdater := NewOrderUpdater(moveRouter) @@ -678,6 +723,46 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsCounselor() { suite.IsType(apperror.PreconditionFailedError{}, err) }) + suite.Run("Updates the entitlement of OCONUS fields", func() { + moveRouter := move.NewMoveRouter() + orderUpdater := NewOrderUpdater(moveRouter) + order := factory.BuildNeedsServiceCounselingMove(suite.DB(), nil, nil).Orders + + grade := ghcmessages.GradeO5 + affiliation := ghcmessages.AffiliationAIRFORCE + ocie := false + proGearWeight := models.Int64Pointer(100) + proGearWeightSpouse := models.Int64Pointer(10) + rmeWeight := models.Int64Pointer(10000) + eTag := etag.GenerateEtag(order.UpdatedAt) + + payload := ghcmessages.CounselingUpdateAllowancePayload{ + Agency: &affiliation, + DependentsAuthorized: models.BoolPointer(true), + Grade: &grade, + OrganizationalClothingAndIndividualEquipment: &ocie, + ProGearWeight: proGearWeight, + ProGearWeightSpouse: proGearWeightSpouse, + RequiredMedicalEquipmentWeight: rmeWeight, + AccompaniedTour: models.BoolPointer(true), + DependentsTwelveAndOver: models.Int64Pointer(1), + DependentsUnderTwelve: models.Int64Pointer(2), + } + + updatedOrder, _, err := orderUpdater.UpdateAllowanceAsCounselor(suite.AppContextForTest(), order.ID, payload, eTag) + suite.NoError(err) + + suite.Equal(order.ID.String(), updatedOrder.ID.String()) + suite.Equal(payload.DependentsAuthorized, updatedOrder.Entitlement.DependentsAuthorized) + suite.Equal(*payload.ProGearWeight, int64(updatedOrder.Entitlement.ProGearWeight)) + suite.Equal(*payload.ProGearWeightSpouse, int64(updatedOrder.Entitlement.ProGearWeightSpouse)) + suite.Equal(*payload.RequiredMedicalEquipmentWeight, int64(updatedOrder.Entitlement.RequiredMedicalEquipmentWeight)) + suite.Equal(*payload.OrganizationalClothingAndIndividualEquipment, updatedOrder.Entitlement.OrganizationalClothingAndIndividualEquipment) + suite.Equal(*payload.AccompaniedTour, *updatedOrder.Entitlement.AccompaniedTour) + suite.Equal(*payload.DependentsUnderTwelve, int64(*updatedOrder.Entitlement.DependentsUnderTwelve)) + suite.Equal(*payload.DependentsTwelveAndOver, int64(*updatedOrder.Entitlement.DependentsTwelveAndOver)) + }) + suite.Run("Updates the allowance when all fields are valid with dependents authorized but not present", func() { moveRouter := move.NewMoveRouter() orderUpdater := NewOrderUpdater(moveRouter) diff --git a/pkg/services/ppm_closeout/ppm_closeout.go b/pkg/services/ppm_closeout/ppm_closeout.go index f10ea7373e9..925385d079b 100644 --- a/pkg/services/ppm_closeout/ppm_closeout.go +++ b/pkg/services/ppm_closeout/ppm_closeout.go @@ -86,18 +86,13 @@ func (p *ppmCloseoutFetcher) GetPPMCloseout(appCtx appcontext.AppContext, ppmShi proGearSpouseMax := unit.Pound(500) var fullAllowableWeight unit.Pound - if len(ppmShipment.WeightTickets) >= 1 { - for _, weightTicket := range ppmShipment.WeightTickets { - if weightTicket.Status != nil && *weightTicket.Status == models.PPMDocumentStatusApproved { - fullAllowableWeight += *weightTicket.AllowableWeight - } - } + if ppmShipment.AllowableWeight != nil { + fullAllowableWeight = *ppmShipment.AllowableWeight } fullWeightGCCShipment := ppmShipment - // fullWeightGCCShipment.ActualWeight = fullEntitlementWeight - // Set pro gear werights for the GCC calculation to the max allowed before calculating GCC price + // Set pro gear weights for the GCC calculation to the max allowed before calculating GCC price fullWeightGCCShipment.ProGearWeight = &proGearCustomerMax fullWeightGCCShipment.SpouseProGearWeight = &proGearSpouseMax gcc, _ := p.calculateGCC(appCtx, *fullWeightGCCShipment, fullAllowableWeight) @@ -356,6 +351,10 @@ func (p *ppmCloseoutFetcher) getServiceItemPrices(appCtx appcontext.AppContext, } } + if ppmShipment.AllowableWeight != nil && *ppmShipment.AllowableWeight < totalWeight { + totalWeight = *ppmShipment.AllowableWeight + } + if totalWeight > 0 { // Reassign ppm shipment fields to their expected location on the mto shipment for dates, addresses, weights ... ppmToMtoShipment = ppmshipment.MapPPMShipmentFinalFields(ppmShipment, totalWeight) diff --git a/pkg/services/ppm_closeout/ppm_closeout_test.go b/pkg/services/ppm_closeout/ppm_closeout_test.go index 2a6f6fa0614..2564a06b468 100644 --- a/pkg/services/ppm_closeout/ppm_closeout_test.go +++ b/pkg/services/ppm_closeout/ppm_closeout_test.go @@ -396,6 +396,39 @@ func (suite *PPMCloseoutSuite) TestPPMShipmentCreator() { appCtx.Logger().Debug("+%v", zap.Error(err)) suite.IsType(err, apperror.PPMNotReadyForCloseoutError{}) }) + + suite.Run("Can successfully GET a PPMCloseout Object when the allowable weight is less than the net weight", func() { + // Under test: GetPPMCloseout + // Set up: Established ZIPs, ReServices, and all pricing data + // Expected: PPMCloseout Object successfully retrieved + appCtx := suite.AppContextForTest() + + mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), + "50309", "30813").Return(2294, nil) + + mockedPaymentRequestHelper.On( + "FetchServiceParamsForServiceItems", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("[]models.MTOServiceItem")).Return(serviceParams, nil) + + mockIncentiveValue := unit.Cents(100000) + mockPpmEstimator.On( + "FinalIncentiveWithDefaultChecks", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")).Return(&mockIncentiveValue, nil) + + ppmShipment := suite.mockPPMShipmentForCloseoutWithAllowableWeightTest() + + ppmCloseoutObj, err := ppmCloseoutFetcher.GetPPMCloseout(appCtx, ppmShipment.ID) + if err != nil { + appCtx.Logger().Error("Error getting PPM closeout object: ", zap.Error(err)) + } + + suite.Nil(err) + suite.NotNil(ppmCloseoutObj) + suite.NotEmpty(ppmCloseoutObj) + }) } func mockServiceParamsTables() models.ServiceParams { @@ -606,3 +639,59 @@ func (suite *PPMCloseoutSuite) mockPPMShipmentForCloseoutTest(buildType ppmBuild return ppmShipment } + +func (suite *PPMCloseoutSuite) mockPPMShipmentForCloseoutWithAllowableWeightTest() models.PPMShipment { + ppmID, _ := uuid.FromString("00000000-0000-0000-0000-000000000000") + estWeight := unit.Pound(2000) + actualMoveDate := time.Now() + expectedDepartureDate := &actualMoveDate + miles := unit.Miles(200) + emptyWeight1 := unit.Pound(1000) + emptyWeight2 := unit.Pound(1500) + fullWeight1 := unit.Pound(7000) + fullWeight2 := unit.Pound(4500) // Net weight sums to 9000 + finalIncentive := unit.Cents(20000) + allowableWeight := unit.Pound(8000) + + weightTickets := models.WeightTickets{ + models.WeightTicket{ + EmptyWeight: &emptyWeight1, + FullWeight: &fullWeight1, + }, + models.WeightTicket{ + EmptyWeight: &emptyWeight2, + FullWeight: &fullWeight2, + }, + } + + sitDaysAllowance := 20 + sitLocation := models.SITLocationTypeOrigin + date := time.Now() + + ppmShipmentCustomization := []factory.Customization{ + { + Model: models.MTOShipment{ + Distance: &miles, + SITDaysAllowance: &sitDaysAllowance, + }, + }, + { + Model: models.PPMShipment{ + ID: ppmID, + ExpectedDepartureDate: *expectedDepartureDate, + ActualMoveDate: &actualMoveDate, + EstimatedWeight: &estWeight, + WeightTickets: weightTickets, + FinalIncentive: &finalIncentive, + SITLocation: &sitLocation, + SITEstimatedEntryDate: &date, + SITEstimatedDepartureDate: &date, + AllowableWeight: &allowableWeight, + }, + }, + } + + ppmShipment := factory.BuildPPMShipmentThatNeedsCloseout(suite.AppContextForTest().DB(), nil, ppmShipmentCustomization) + + return ppmShipment +} diff --git a/pkg/services/ppmshipment/ppm_estimator.go b/pkg/services/ppmshipment/ppm_estimator.go index 29a16bd0dab..0125fb5c395 100644 --- a/pkg/services/ppmshipment/ppm_estimator.go +++ b/pkg/services/ppmshipment/ppm_estimator.go @@ -113,7 +113,6 @@ func (f *estimatePPM) CalculatePPMSITEstimatedCostBreakdown(appCtx appcontext.Ap return ppmSITEstimatedCostInfoData, nil } -// EstimateIncentiveWithDefaultChecks func that returns the estimate hard coded to 12K (because it'll be clear that the value is coming from the service) func (f *estimatePPM) EstimateIncentiveWithDefaultChecks(appCtx appcontext.AppContext, oldPPMShipment models.PPMShipment, newPPMShipment *models.PPMShipment) (*unit.Cents, *unit.Cents, error) { return f.estimateIncentive(appCtx, oldPPMShipment, newPPMShipment, f.checks...) } @@ -131,10 +130,14 @@ func (f *estimatePPM) PriceBreakdown(appCtx appcontext.AppContext, ppmShipment * } func shouldSkipEstimatingIncentive(newPPMShipment *models.PPMShipment, oldPPMShipment *models.PPMShipment) bool { - return oldPPMShipment.ExpectedDepartureDate.Equal(newPPMShipment.ExpectedDepartureDate) && - newPPMShipment.PickupAddress.PostalCode == oldPPMShipment.PickupAddress.PostalCode && - newPPMShipment.DestinationAddress.PostalCode == oldPPMShipment.DestinationAddress.PostalCode && - ((newPPMShipment.EstimatedWeight == nil && oldPPMShipment.EstimatedWeight == nil) || (oldPPMShipment.EstimatedWeight != nil && newPPMShipment.EstimatedWeight.Int() == oldPPMShipment.EstimatedWeight.Int())) + if oldPPMShipment.Status != models.PPMShipmentStatusDraft && oldPPMShipment.EstimatedIncentive != nil && *newPPMShipment.EstimatedIncentive == 0 { + return false + } else { + return oldPPMShipment.ExpectedDepartureDate.Equal(newPPMShipment.ExpectedDepartureDate) && + newPPMShipment.PickupAddress.PostalCode == oldPPMShipment.PickupAddress.PostalCode && + newPPMShipment.DestinationAddress.PostalCode == oldPPMShipment.DestinationAddress.PostalCode && + ((newPPMShipment.EstimatedWeight == nil && oldPPMShipment.EstimatedWeight == nil) || (oldPPMShipment.EstimatedWeight != nil && newPPMShipment.EstimatedWeight.Int() == oldPPMShipment.EstimatedWeight.Int())) + } } func shouldSkipCalculatingFinalIncentive(newPPMShipment *models.PPMShipment, oldPPMShipment *models.PPMShipment, originalTotalWeight unit.Pound, newTotalWeight unit.Pound) bool { @@ -274,12 +277,11 @@ func (f *estimatePPM) maxIncentive(appCtx appcontext.AppContext, oldPPMShipment return nil, err } - maxIncentive := oldPPMShipment.MaxIncentive - if maxIncentive == nil { - maxIncentive, err = f.calculatePrice(appCtx, newPPMShipment, unit.Pound(*orders.Entitlement.DBAuthorizedWeight), contract, true) - if err != nil { - return nil, err - } + // since the max incentive is based off of the authorized weight entitlement and that value CAN change + // we will calculate the max incentive each time it is called + maxIncentive, err := f.calculatePrice(appCtx, newPPMShipment, unit.Pound(*orders.Entitlement.DBAuthorizedWeight), contract, true) + if err != nil { + return nil, err } return maxIncentive, nil @@ -301,6 +303,10 @@ func (f *estimatePPM) finalIncentive(appCtx appcontext.AppContext, oldPPMShipmen } originalTotalWeight, newTotalWeight := SumWeightTickets(oldPPMShipment, *newPPMShipment) + if newPPMShipment.AllowableWeight != nil && *newPPMShipment.AllowableWeight < newTotalWeight { + newTotalWeight = *newPPMShipment.AllowableWeight + } + isMissingInfo := shouldSetFinalIncentiveToNil(newPPMShipment, newTotalWeight) var skipCalculateFinalIncentive bool finalIncentive := oldPPMShipment.FinalIncentive @@ -361,27 +367,55 @@ func SumWeightTickets(ppmShipment, newPPMShipment models.PPMShipment) (originalT // calculatePrice returns an incentive value for the ppm shipment as if we were pricing the service items for // an HHG shipment with the same values for a payment request. In this case we're not persisting service items, // MTOServiceItems or PaymentRequestServiceItems, to the database to avoid unnecessary work and get a quicker result. +// we use this when calculating the estimated, final, and max incentive values func (f estimatePPM) calculatePrice(appCtx appcontext.AppContext, ppmShipment *models.PPMShipment, totalWeight unit.Pound, contract models.ReContract, isMaxIncentiveCheck bool) (*unit.Cents, error) { logger := appCtx.Logger() zeroTotal := false serviceItemsToPrice := BaseServiceItems(ppmShipment.ShipmentID) + var move models.Move + err := appCtx.DB().Q().Eager( + "Orders.Entitlement", + "Orders.OriginDutyLocation.Address", + "Orders.NewDutyLocation.Address", + ).Where("show = TRUE").Find(&move, ppmShipment.Shipment.MoveTaskOrderID) + if err != nil { + return nil, apperror.NewNotFoundError(ppmShipment.ID, " error querying move") + } + orders := move.Orders + if orders.Entitlement.DBAuthorizedWeight == nil { + return nil, apperror.NewNotFoundError(ppmShipment.ID, " DB authorized weight cannot be nil") + } + // Replace linehaul pricer with shorthaul pricer if move is within the same Zip3 var pickupPostal, destPostal string - // Check different address values for a postal code - if ppmShipment.ActualPickupPostalCode != nil { - pickupPostal = *ppmShipment.ActualPickupPostalCode - } else if ppmShipment.PickupAddress.PostalCode != "" { - pickupPostal = ppmShipment.PickupAddress.PostalCode - } + // if we are getting the max incentive, we want to use the addresses on the orders, else use what's on the shipment + if isMaxIncentiveCheck { + if orders.OriginDutyLocation.Address.PostalCode != "" { + pickupPostal = orders.OriginDutyLocation.Address.PostalCode + } else { + return nil, apperror.NewNotFoundError(ppmShipment.ID, " No postal code for origin duty location on orders when comparing postal codes") + } - // Same for destination - if ppmShipment.ActualDestinationPostalCode != nil { - destPostal = *ppmShipment.ActualDestinationPostalCode - } else if ppmShipment.DestinationAddress.PostalCode != "" { - destPostal = ppmShipment.DestinationAddress.PostalCode + if orders.NewDutyLocation.Address.PostalCode != "" { + destPostal = orders.NewDutyLocation.Address.PostalCode + } else { + return nil, apperror.NewNotFoundError(ppmShipment.ID, " No postal code for destination duty location on orders when comparing postal codes") + } + } else { + if ppmShipment.ActualPickupPostalCode != nil { + pickupPostal = *ppmShipment.ActualPickupPostalCode + } else if ppmShipment.PickupAddress.PostalCode != "" { + pickupPostal = ppmShipment.PickupAddress.PostalCode + } + + if ppmShipment.ActualDestinationPostalCode != nil { + destPostal = *ppmShipment.ActualDestinationPostalCode + } else if ppmShipment.DestinationAddress.PostalCode != "" { + destPostal = ppmShipment.DestinationAddress.PostalCode + } } if pickupPostal[0:3] == destPostal[0:3] { @@ -400,7 +434,11 @@ func (f estimatePPM) calculatePrice(appCtx appcontext.AppContext, ppmShipment *m // Reassign ppm shipment fields to their expected location on the mto shipment for dates, addresses, weights ... mtoShipment = MapPPMShipmentFinalFields(*ppmShipment, totalWeight) } else if totalWeight > 0 && isMaxIncentiveCheck { - mtoShipment = MapPPMShipmentMaxIncentiveFields(*ppmShipment, totalWeight) + mtoShipment, err = MapPPMShipmentMaxIncentiveFields(appCtx, *ppmShipment, totalWeight) + if err != nil { + logger.Error("unable to map PPM fields for max incentive", zap.Error(err)) + return nil, err + } } else { // Reassign ppm shipment fields to their expected location on the mto shipment for dates, addresses, weights ... mtoShipment, err = MapPPMShipmentEstimatedFields(appCtx, *ppmShipment) @@ -908,44 +946,40 @@ func priceAdditionalDaySIT(appCtx appcontext.AppContext, pricer services.ParamsP // mapPPMShipmentEstimatedFields remaps our PPMShipment specific information into the fields where the service param lookups // expect to find them on the MTOShipment model. This is only in-memory and shouldn't get saved to the database. func MapPPMShipmentEstimatedFields(appCtx appcontext.AppContext, ppmShipment models.PPMShipment) (models.MTOShipment, error) { - // we have access to the MoveTaskOrderID in the ppmShipment object so we can use that to get the customer's maximum weight entitlement - var move models.Move - err := appCtx.DB().Q().Eager( - "Orders.Entitlement", - ).Where("show = TRUE").Find(&move, ppmShipment.Shipment.MoveTaskOrderID) - if err != nil { - return models.MTOShipment{}, apperror.NewNotFoundError(ppmShipment.ID, " error querying move") - } - orders := move.Orders - if orders.Entitlement.DBAuthorizedWeight == nil { - return models.MTOShipment{}, apperror.NewNotFoundError(ppmShipment.ID, " DB authorized weight cannot be nil") - } ppmShipment.Shipment.ActualPickupDate = &ppmShipment.ExpectedDepartureDate ppmShipment.Shipment.RequestedPickupDate = &ppmShipment.ExpectedDepartureDate ppmShipment.Shipment.PickupAddress = &models.Address{PostalCode: ppmShipment.PickupAddress.PostalCode} ppmShipment.Shipment.DestinationAddress = &models.Address{PostalCode: ppmShipment.DestinationAddress.PostalCode} - // checking for status because PPM closeout uses the entitlement weight and the estimated incentive uses the estimated weight - if ppmShipment.Status == models.PPMShipmentStatusDraft || ppmShipment.Status == models.PPMShipmentStatusSubmitted { - ppmShipment.Shipment.PrimeActualWeight = ppmShipment.EstimatedWeight - } else { - ppmShipment.Shipment.PrimeActualWeight = (*unit.Pound)(orders.Entitlement.DBAuthorizedWeight) - } + ppmShipment.Shipment.PrimeActualWeight = ppmShipment.EstimatedWeight return ppmShipment.Shipment, nil } // MapPPMShipmentMaxIncentiveFields remaps our PPMShipment specific information into the fields where the service param lookups // expect to find them on the MTOShipment model. This is only in-memory and shouldn't get saved to the database. -func MapPPMShipmentMaxIncentiveFields(ppmShipment models.PPMShipment, totalWeight unit.Pound) models.MTOShipment { +func MapPPMShipmentMaxIncentiveFields(appCtx appcontext.AppContext, ppmShipment models.PPMShipment, totalWeight unit.Pound) (models.MTOShipment, error) { + var move models.Move + err := appCtx.DB().Q().Eager( + "Orders.Entitlement", + "Orders.OriginDutyLocation.Address", + "Orders.NewDutyLocation.Address", + ).Where("show = TRUE").Find(&move, ppmShipment.Shipment.MoveTaskOrderID) + if err != nil { + return models.MTOShipment{}, apperror.NewNotFoundError(ppmShipment.ID, " error querying move") + } + orders := move.Orders + if orders.Entitlement.DBAuthorizedWeight == nil { + return models.MTOShipment{}, apperror.NewNotFoundError(ppmShipment.ID, " DB authorized weight cannot be nil") + } ppmShipment.Shipment.ActualPickupDate = &ppmShipment.ExpectedDepartureDate ppmShipment.Shipment.RequestedPickupDate = &ppmShipment.ExpectedDepartureDate - ppmShipment.Shipment.PickupAddress = &models.Address{PostalCode: ppmShipment.PickupAddress.PostalCode} - ppmShipment.Shipment.DestinationAddress = &models.Address{PostalCode: ppmShipment.DestinationAddress.PostalCode} + ppmShipment.Shipment.PickupAddress = &models.Address{PostalCode: orders.OriginDutyLocation.Address.PostalCode} + ppmShipment.Shipment.DestinationAddress = &models.Address{PostalCode: orders.NewDutyLocation.Address.PostalCode} ppmShipment.Shipment.PrimeActualWeight = &totalWeight - return ppmShipment.Shipment + return ppmShipment.Shipment, nil } // mapPPMShipmentFinalFields remaps our PPMShipment specific information into the fields where the service param lookups diff --git a/pkg/services/ppmshipment/ppm_estimator_test.go b/pkg/services/ppmshipment/ppm_estimator_test.go index 873b21274c1..de689f0782d 100644 --- a/pkg/services/ppmshipment/ppm_estimator_test.go +++ b/pkg/services/ppmshipment/ppm_estimator_test.go @@ -755,6 +755,7 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() { suite.Run("Final Incentive", func() { actualMoveDate := time.Date(2020, time.March, 14, 0, 0, 0, 0, time.UTC) + suite.Run("Final Incentive - Success", func() { setupPricerData() weightOverride := unit.Pound(19500) @@ -807,6 +808,109 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() { suite.Equal(unit.Cents(70064364), *ppmFinal) }) + suite.Run("Final Incentive - Success with allowable weight less than net weight", func() { + setupPricerData() + weightOverride := unit.Pound(19500) + oldPPMShipment := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.PPMShipment{ + ActualPickupPostalCode: models.StringPointer("50309"), + ActualDestinationPostalCode: models.StringPointer("30813"), + ActualMoveDate: models.TimePointer(actualMoveDate), + Status: models.PPMShipmentStatusWaitingOnCustomer, + }, + }, + }, []factory.Trait{factory.GetTraitApprovedPPMWithActualInfo}) + + oldPPMShipment.WeightTickets = models.WeightTickets{ + factory.BuildWeightTicket(suite.DB(), []factory.Customization{ + { + Model: models.WeightTicket{ + FullWeight: &weightOverride, + }, + }, + }, nil), + } + + newPPM := oldPPMShipment + updatedMoveDate := time.Date(2020, time.March, 15, 0, 0, 0, 0, time.UTC) + newPPM.ActualMoveDate = models.TimePointer(updatedMoveDate) + + mockedPaymentRequestHelper.On( + "FetchServiceParamsForServiceItems", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("[]models.MTOServiceItem")).Return(serviceParams, nil) + + // DTOD distance is going to be less than the HHG Rand McNally distance of 2361 miles + mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "50309", "30813").Return(2294, nil) + + ppmFinal, err := ppmEstimator.FinalIncentiveWithDefaultChecks(suite.AppContextForTest(), oldPPMShipment, &newPPM) + suite.NilOrNoVerrs(err) + + mockedPlanner.AssertCalled(suite.T(), "ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "50309", "30813") + mockedPaymentRequestHelper.AssertCalled(suite.T(), "FetchServiceParamsForServiceItems", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("[]models.MTOServiceItem")) + + suite.Equal(oldPPMShipment.ActualPickupPostalCode, newPPM.ActualPickupPostalCode) + suite.NotEqual(*oldPPMShipment.ActualMoveDate, newPPM.ActualMoveDate) + originalWeight, newWeight := SumWeightTickets(oldPPMShipment, newPPM) + suite.Equal(unit.Pound(5000), originalWeight) + suite.Equal(unit.Pound(5000), newWeight) + suite.Equal(unit.Cents(70064364), *ppmFinal) + + // Repeat the above shipment with an allowable weight less than the net weight + weightOverride = unit.Pound(19500) + allowableWeightOverride := unit.Pound(4000) + oldPPMShipment = factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.PPMShipment{ + ActualPickupPostalCode: models.StringPointer("50309"), + ActualDestinationPostalCode: models.StringPointer("30813"), + ActualMoveDate: models.TimePointer(actualMoveDate), + Status: models.PPMShipmentStatusWaitingOnCustomer, + AllowableWeight: &allowableWeightOverride, + }, + }, + }, []factory.Trait{factory.GetTraitApprovedPPMWithActualInfo}) + + oldPPMShipment.WeightTickets = models.WeightTickets{ + factory.BuildWeightTicket(suite.DB(), []factory.Customization{ + { + Model: models.WeightTicket{ + FullWeight: &weightOverride, + }, + }, + }, nil), + } + + newPPM = oldPPMShipment + updatedMoveDate = time.Date(2020, time.March, 15, 0, 0, 0, 0, time.UTC) + newPPM.ActualMoveDate = models.TimePointer(updatedMoveDate) + + mockedPaymentRequestHelper.On( + "FetchServiceParamsForServiceItems", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("[]models.MTOServiceItem")).Return(serviceParams, nil) + + // DTOD distance is going to be less than the HHG Rand McNally distance of 2361 miles + mockedPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), + "50309", "30813").Return(2294, nil) + + ppmFinalIncentiveLimitedByAllowableWeight, err := ppmEstimator.FinalIncentiveWithDefaultChecks(suite.AppContextForTest(), oldPPMShipment, &newPPM) + suite.NilOrNoVerrs(err) + + mockedPlanner.AssertCalled(suite.T(), "ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "50309", "30813") + mockedPaymentRequestHelper.AssertCalled(suite.T(), "FetchServiceParamsForServiceItems", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("[]models.MTOServiceItem")) + + suite.Equal(oldPPMShipment.ActualPickupPostalCode, newPPM.ActualPickupPostalCode) + suite.NotEqual(*oldPPMShipment.ActualMoveDate, newPPM.ActualMoveDate) + originalWeight, newWeight = SumWeightTickets(oldPPMShipment, newPPM) + suite.Equal(unit.Pound(5000), originalWeight) + suite.Equal(unit.Pound(5000), newWeight) + + // Confirm the incentive is less than if all of the weight was allowable + suite.Less(*ppmFinalIncentiveLimitedByAllowableWeight, *ppmFinal) + }) + suite.Run("Final Incentive - Success with updated weights", func() { setupPricerData() moveDate := time.Date(2020, time.March, 15, 0, 0, 0, 0, time.UTC) diff --git a/pkg/services/ppmshipment/ppm_shipment_creator.go b/pkg/services/ppmshipment/ppm_shipment_creator.go index f04b549e6b8..75f8e50f7ae 100644 --- a/pkg/services/ppmshipment/ppm_shipment_creator.go +++ b/pkg/services/ppmshipment/ppm_shipment_creator.go @@ -27,6 +27,7 @@ func NewPPMShipmentCreator(estimator services.PPMEstimator, addressCreator servi checkShipmentID(), checkPPMShipmentID(), checkRequiredFields(), + checkPPMShipmentSequenceValidForCreate(), }, } } diff --git a/pkg/services/ppmshipment/ppm_shipment_new_submitter.go b/pkg/services/ppmshipment/ppm_shipment_new_submitter.go index abfebcd0b3f..4f9950530c3 100644 --- a/pkg/services/ppmshipment/ppm_shipment_new_submitter.go +++ b/pkg/services/ppmshipment/ppm_shipment_new_submitter.go @@ -8,6 +8,7 @@ import ( "github.com/transcom/mymove/pkg/apperror" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/unit" ) // ppmShipmentNewSubmitter is the concrete struct implementing the services.PPMShipmentNewSubmitter interface @@ -71,6 +72,15 @@ func (p *ppmShipmentNewSubmitter) SubmitNewCustomerCloseOut(appCtx appcontext.Ap return nil, err } + // initial allowable weight is equal to net weight of all shipments, E-05722 + var allowableWeight = unit.Pound(0) + if len(updatedPPMShipment.WeightTickets) >= 1 { + for _, weightTicket := range ppmShipment.WeightTickets { + allowableWeight += *weightTicket.FullWeight - *weightTicket.EmptyWeight + } + } + updatedPPMShipment.AllowableWeight = &allowableWeight + txErr := appCtx.NewTransaction(func(txnAppCtx appcontext.AppContext) error { updatedPPMShipment.SignedCertification, err = p.SignedCertificationCreator.CreateSignedCertification(txnAppCtx, signedCertification) diff --git a/pkg/services/ppmshipment/ppm_shipment_new_submitter_test.go b/pkg/services/ppmshipment/ppm_shipment_new_submitter_test.go index f6c81b95100..2bc85255a40 100644 --- a/pkg/services/ppmshipment/ppm_shipment_new_submitter_test.go +++ b/pkg/services/ppmshipment/ppm_shipment_new_submitter_test.go @@ -15,6 +15,7 @@ import ( "github.com/transcom/mymove/pkg/services" "github.com/transcom/mymove/pkg/services/mocks" "github.com/transcom/mymove/pkg/testdatagen" + "github.com/transcom/mymove/pkg/unit" ) func (suite *PPMShipmentSuite) TestSubmitNewCustomerCloseOut() { @@ -26,14 +27,14 @@ func (suite *PPMShipmentSuite) TestSubmitNewCustomerCloseOut() { var ppmShipment models.PPMShipment - err := suite.DB().EagerPreload(EagerPreloadAssociationShipment).Find(&ppmShipment, ppmShipmentID) + err := suite.DB().EagerPreload(EagerPreloadAssociationShipment, EagerPreloadAssociationWeightTickets).Find(&ppmShipment, ppmShipmentID) suite.FatalNoError(err) return &ppmShipment } - setUpPPMShipmentFetcherMock := func(returnValue ...interface{}) services.PPMShipmentFetcher { + setUpPPMShipmentFetcherMock := func(returnValue interface{}, err error) services.PPMShipmentFetcher { mockFetcher := &mocks.PPMShipmentFetcher{} mockFetcher.On( @@ -42,7 +43,7 @@ func (suite *PPMShipmentSuite) TestSubmitNewCustomerCloseOut() { mock.AnythingOfType("uuid.UUID"), mock.AnythingOfType("[]string"), mock.AnythingOfType("[]string"), - ).Return(returnValue...) + ).Return(returnValue, err) return mockFetcher } @@ -182,7 +183,7 @@ func (suite *PPMShipmentSuite) TestSubmitNewCustomerCloseOut() { } }) - suite.Run("Can create a signed certification and route the PPMShipment properly", func() { + suite.Run("Can create a signed certification, route the PPMShipment, and calculate allowable weight properly", func() { existingPPMShipment := factory.BuildPPMShipmentReadyForFinalCustomerCloseOut(suite.DB(), nil, nil) appCtx := suite.AppContextWithSessionForTest(&auth.Session{ @@ -259,6 +260,16 @@ func (suite *PPMShipmentSuite) TestSubmitNewCustomerCloseOut() { mock.AnythingOfType("*models.PPMShipment"), ) + var expectedAllowableWeight = unit.Pound(0) + if len(existingPPMShipment.WeightTickets) >= 1 { + for _, weightTicket := range existingPPMShipment.WeightTickets { + expectedAllowableWeight += *weightTicket.FullWeight - *weightTicket.EmptyWeight + } + } + if suite.NotNil(updatedPPMShipment.AllowableWeight) { + suite.Equal(*updatedPPMShipment.AllowableWeight, expectedAllowableWeight) + } + return nil } diff --git a/pkg/services/ppmshipment/ppm_shipment_updated_submitter.go b/pkg/services/ppmshipment/ppm_shipment_updated_submitter.go index d9bd8dcfb8b..146bc76afca 100644 --- a/pkg/services/ppmshipment/ppm_shipment_updated_submitter.go +++ b/pkg/services/ppmshipment/ppm_shipment_updated_submitter.go @@ -8,6 +8,7 @@ import ( "github.com/transcom/mymove/pkg/apperror" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/unit" ) // ppmShipmentUpdatedSubmitter is the concrete struct implementing the services.PPMShipmentUpdatedSubmitter interface @@ -46,6 +47,15 @@ func (p *ppmShipmentUpdatedSubmitter) SubmitUpdatedCustomerCloseOut(appCtx appco return nil, err } + // initial allowable weight is equal to net weight of all shipments, E-05722 + var allowableWeight = unit.Pound(0) + if len(updatedPPMShipment.WeightTickets) >= 1 { + for _, weightTicket := range ppmShipment.WeightTickets { + allowableWeight += *weightTicket.FullWeight - *weightTicket.EmptyWeight + } + } + updatedPPMShipment.AllowableWeight = &allowableWeight + txErr := appCtx.NewTransaction(func(txnAppCtx appcontext.AppContext) error { updatedPPMShipment.SignedCertification, err = p.SignedCertificationUpdater.UpdateSignedCertification(txnAppCtx, signedCertification, eTag) diff --git a/pkg/services/ppmshipment/ppm_shipment_updated_submitter_test.go b/pkg/services/ppmshipment/ppm_shipment_updated_submitter_test.go index 888b1dfba03..b7408717f2c 100644 --- a/pkg/services/ppmshipment/ppm_shipment_updated_submitter_test.go +++ b/pkg/services/ppmshipment/ppm_shipment_updated_submitter_test.go @@ -17,6 +17,7 @@ import ( "github.com/transcom/mymove/pkg/services/mocks" mtoshipment "github.com/transcom/mymove/pkg/services/mto_shipment" "github.com/transcom/mymove/pkg/testdatagen" + "github.com/transcom/mymove/pkg/unit" ) func (suite *PPMShipmentSuite) TestSubmitCustomerCloseOut() { @@ -231,6 +232,16 @@ func (suite *PPMShipmentSuite) TestSubmitCustomerCloseOut() { eTag, ) + var expectedAllowableWeight = unit.Pound(0) + if len(existingPPMShipment.WeightTickets) >= 1 { + for _, weightTicket := range existingPPMShipment.WeightTickets { + expectedAllowableWeight += *weightTicket.FullWeight - *weightTicket.EmptyWeight + } + } + if suite.NotNil(updatedPPMShipment.AllowableWeight) { + suite.Equal(*updatedPPMShipment.AllowableWeight, expectedAllowableWeight) + } + return nil } diff --git a/pkg/services/ppmshipment/ppm_shipment_updater.go b/pkg/services/ppmshipment/ppm_shipment_updater.go index a1b09eefae4..f8cc99b8d30 100644 --- a/pkg/services/ppmshipment/ppm_shipment_updater.go +++ b/pkg/services/ppmshipment/ppm_shipment_updater.go @@ -24,6 +24,7 @@ var PPMShipmentUpdaterChecks = []ppmShipmentValidator{ checkPPMShipmentID(), checkRequiredFields(), checkAdvanceAmountRequested(), + checkPPMShipmentSequenceValidForUpdate(), } func NewPPMShipmentUpdater(ppmEstimator services.PPMEstimator, addressCreator services.AddressCreator, addressUpdater services.AddressUpdater) services.PPMShipmentUpdater { @@ -125,11 +126,17 @@ func (f *ppmShipmentUpdater) updatePPMShipment(appCtx appcontext.AppContext, ppm updatedPPMShipment.EstimatedIncentive = estimatedIncentive updatedPPMShipment.SITEstimatedCost = estimatedSITCost - maxIncentive, err := f.estimator.MaxIncentive(appCtx, *oldPPMShipment, updatedPPMShipment) - if err != nil { - return err + // if the PPM shipment is past closeout then we should not calculate the max incentive, it is already set in stone + if oldPPMShipment.Status != models.PPMShipmentStatusWaitingOnCustomer && + oldPPMShipment.Status != models.PPMShipmentStatusCloseoutComplete && + oldPPMShipment.Status != models.PPMShipmentStatusComplete && + oldPPMShipment.Status != models.PPMShipmentStatusNeedsCloseout { + maxIncentive, err := f.estimator.MaxIncentive(appCtx, *oldPPMShipment, updatedPPMShipment) + if err != nil { + return err + } + updatedPPMShipment.MaxIncentive = maxIncentive } - updatedPPMShipment.MaxIncentive = maxIncentive if appCtx.Session() != nil { if appCtx.Session().IsOfficeUser() { diff --git a/pkg/services/ppmshipment/ppm_shipment_updater_test.go b/pkg/services/ppmshipment/ppm_shipment_updater_test.go index 75f85f97646..899106df376 100644 --- a/pkg/services/ppmshipment/ppm_shipment_updater_test.go +++ b/pkg/services/ppmshipment/ppm_shipment_updater_test.go @@ -742,6 +742,35 @@ func (suite *PPMShipmentSuite) TestUpdatePPMShipment() { suite.Equal(*newFakeEstimatedIncentive, *updatedPPM.EstimatedIncentive) }) + suite.Run("Can successfully update a PPMShipment - edit just allowable weight", func() { + appCtx := suite.AppContextWithSessionForTest(&auth.Session{}) + + subtestData := setUpForTests(nil, nil, nil, nil) + + originalPPM := factory.BuildMinimalPPMShipment(appCtx.DB(), []factory.Customization{ + { + Model: models.PPMShipment{ + EstimatedWeight: models.PoundPointer(4000), + AllowableWeight: models.PoundPointer(3000), + }, + }, + }, nil) + + newPPM := models.PPMShipment{ + AllowableWeight: models.PoundPointer(4545), + } + + updatedPPM, err := subtestData.ppmShipmentUpdater.UpdatePPMShipmentWithDefaultCheck(appCtx, &newPPM, originalPPM.ShipmentID) + + suite.NilOrNoVerrs(err) + + // Fields that shouldn't have changed + suite.Equal(originalPPM.EstimatedWeight, updatedPPM.EstimatedWeight) + + // Fields that should now be updated + suite.Equal(*newPPM.AllowableWeight, *updatedPPM.AllowableWeight) + }) + suite.Run("Can successfully update a PPMShipment - add advance info - no advance", func() { appCtx := suite.AppContextWithSessionForTest(&auth.Session{}) diff --git a/pkg/services/ppmshipment/rules.go b/pkg/services/ppmshipment/rules.go index 47b48c34913..072746eeab6 100644 --- a/pkg/services/ppmshipment/rules.go +++ b/pkg/services/ppmshipment/rules.go @@ -55,6 +55,75 @@ func checkPPMShipmentID() ppmShipmentValidator { }) } +// helper function to check if the secondary address is empty, but the tertiary is not +func isPPMShipmentAddressCreateSequenceValid(ppmShipmentToCheck models.PPMShipment) bool { + bothPickupAddressesEmpty := (models.IsAddressEmpty(ppmShipmentToCheck.SecondaryPickupAddress) && models.IsAddressEmpty(ppmShipmentToCheck.TertiaryPickupAddress)) + bothDestinationAddressesEmpty := (models.IsAddressEmpty(ppmShipmentToCheck.SecondaryDestinationAddress) && models.IsAddressEmpty(ppmShipmentToCheck.TertiaryDestinationAddress)) + bothPickupAddressesNotEmpty := !bothPickupAddressesEmpty + bothDestinationAddressesNotEmpty := !bothDestinationAddressesEmpty + hasNoSecondaryHasTertiaryPickup := (models.IsAddressEmpty(ppmShipmentToCheck.SecondaryPickupAddress) && !models.IsAddressEmpty(ppmShipmentToCheck.TertiaryPickupAddress)) + hasNoSecondaryHasTertiaryDestination := (models.IsAddressEmpty(ppmShipmentToCheck.SecondaryDestinationAddress) && !models.IsAddressEmpty(ppmShipmentToCheck.TertiaryDestinationAddress)) + + // need an explicit case to capture when both are empty or not empty + if (bothPickupAddressesEmpty && bothDestinationAddressesEmpty) || (bothPickupAddressesNotEmpty && bothDestinationAddressesNotEmpty) { + return true + } + if hasNoSecondaryHasTertiaryPickup || hasNoSecondaryHasTertiaryDestination { + return false + } + return true +} + +/* Checks if a valid address sequence is being maintained. This will return false if the tertiary address is being updated while the secondary address remains empty +* + */ +func isPPMAddressUpdateSequenceValid(shipmentToUpdateWith *models.PPMShipment, currentShipment *models.PPMShipment) bool { + // if the incoming model has both fields, then we know the model will be updated with both secondary and tertiary addresses. therefore return true + if !models.IsAddressEmpty(shipmentToUpdateWith.SecondaryPickupAddress) && !models.IsAddressEmpty(shipmentToUpdateWith.TertiaryPickupAddress) { + return true + } + if !models.IsAddressEmpty(shipmentToUpdateWith.SecondaryDestinationAddress) && !models.IsAddressEmpty(shipmentToUpdateWith.TertiaryDestinationAddress) { + return true + } + if currentShipment.SecondaryPickupAddress == nil && shipmentToUpdateWith.TertiaryPickupAddress != nil { + return !hasTertiaryWithNoSecondaryAddress(currentShipment.SecondaryPickupAddress, shipmentToUpdateWith.TertiaryPickupAddress) + } + if currentShipment.SecondaryDestinationAddress == nil && shipmentToUpdateWith.TertiaryDestinationAddress != nil { + return !hasTertiaryWithNoSecondaryAddress(currentShipment.SecondaryDestinationAddress, shipmentToUpdateWith.TertiaryDestinationAddress) + } + // no addresses are being updated, so correct address sequence is maintained, return true + return true +} + +// helper function to check if the secondary address is empty, but the tertiary is not +func hasTertiaryWithNoSecondaryAddress(secondaryAddress *models.Address, tertiaryAddress *models.Address) bool { + return (models.IsAddressEmpty(secondaryAddress) && !models.IsAddressEmpty(tertiaryAddress)) +} + +func checkPPMShipmentSequenceValidForCreate() ppmShipmentValidator { + return ppmShipmentValidatorFunc(func(appCtx appcontext.AppContext, newer models.PPMShipment, _ *models.PPMShipment, _ *models.MTOShipment) error { + verrs := validate.NewErrors() + squenceIsValid := isPPMShipmentAddressCreateSequenceValid(newer) + if !squenceIsValid { + verrs.Add("error validating ppm shipment", "Shipment cannot have a third address without a second address present") + return verrs + } + return nil + }) +} + +func checkPPMShipmentSequenceValidForUpdate() ppmShipmentValidator { + return ppmShipmentValidatorFunc(func(appCtx appcontext.AppContext, newer models.PPMShipment, older *models.PPMShipment, _ *models.MTOShipment) error { + verrs := validate.NewErrors() + sequenceIsValid := isPPMAddressUpdateSequenceValid(&newer, older) + if !sequenceIsValid { + verrs.Add("error validating ppm shipment", "Shipment cannot have a third address without a second address present") + return verrs + } + return nil + }) +} + // checkRequiredFields checks that the required fields are included func checkRequiredFields() ppmShipmentValidator { return ppmShipmentValidatorFunc(func(_ appcontext.AppContext, newPPMShipment models.PPMShipment, _ *models.PPMShipment, _ *models.MTOShipment) error { diff --git a/pkg/services/ppmshipment/rules_test.go b/pkg/services/ppmshipment/rules_test.go index 65fc230cf89..6a7134742e1 100644 --- a/pkg/services/ppmshipment/rules_test.go +++ b/pkg/services/ppmshipment/rules_test.go @@ -7,6 +7,7 @@ import ( "github.com/gobuffalo/validate/v3" "github.com/gofrs/uuid" + "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/unit" ) @@ -86,6 +87,110 @@ func (suite *PPMShipmentSuite) TestValidationRules() { }) }) + suite.Run("checkPPMShipmentSequenceValidForUpdate add tertiary address without secondary Invalid", func() { + tertiaryDestinationAddress := factory.BuildAddress(suite.DB(), nil, nil) + + ppmShipmentToUpdateWith := models.PPMShipment{ + TertiaryDestinationAddress: &tertiaryDestinationAddress, + } + + olderPPMShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) + + checker := checkPPMShipmentSequenceValidForUpdate() + err := checker.Validate(suite.AppContextForTest(), ppmShipmentToUpdateWith, &olderPPMShipment, nil) + suite.Error(err) + }) + + suite.Run("checkPPMShipmentSequenceValidForUpdate add secondary address Valid", func() { + secondaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + + ppmShipmentToUpdateWith := models.PPMShipment{ + SecondaryPickupAddress: &secondaryPickupAddress, + } + + olderPPMShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) + + checker := checkPPMShipmentSequenceValidForUpdate() + err := checker.Validate(suite.AppContextForTest(), ppmShipmentToUpdateWith, &olderPPMShipment, nil) + suite.NoError(err) + }) + + suite.Run("checkPPMShipmentSequenceValidForUpdate remove secondary address Valid", func() { + secondaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + + ppmShipmentToUpdateWith := models.PPMShipment{ + SecondaryPickupAddress: &secondaryPickupAddress, + } + + olderPPMShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) + + checker := checkPPMShipmentSequenceValidForUpdate() + err := checker.Validate(suite.AppContextForTest(), ppmShipmentToUpdateWith, &olderPPMShipment, nil) + suite.NoError(err) + }) + + suite.Run("checkPPMShipmentSequenceValidForUpdate Valid", func() { + tertiaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + + ppmShipmentToUpdateWith := models.PPMShipment{ + SecondaryPickupAddress: &tertiaryPickupAddress, + } + + olderPPMShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) + + checker := checkPPMShipmentSequenceValidForUpdate() + err := checker.Validate(suite.AppContextForTest(), ppmShipmentToUpdateWith, &olderPPMShipment, nil) + suite.NoError(err) + }) + + suite.Run("checkPPMShipmentSequenceValidForCreate No Secondary Address With Tertiary Invalid", func() { + tertiaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + + newPPMShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) + newPPMShipment.TertiaryPickupAddress = &tertiaryPickupAddress + + checker := checkPPMShipmentSequenceValidForCreate() + err := checker.Validate(suite.AppContextForTest(), newPPMShipment, nil, nil) + suite.Error(err) + }) + + suite.Run("checkPPMShipmentSequenceValidForCreate Primary Address Only Valid", func() { + + newPPMShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) + + checker := checkPPMShipmentSequenceValidForCreate() + err := checker.Validate(suite.AppContextForTest(), newPPMShipment, nil, nil) + suite.NoError(err) + }) + + suite.Run("checkPPMShipmentSequenceValidForCreate with Secondary Address Only Valid", func() { + secondaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + + newPPMShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) + newPPMShipment.SecondaryPickupAddress = &secondaryPickupAddress + + checker := checkPPMShipmentSequenceValidForCreate() + err := checker.Validate(suite.AppContextForTest(), newPPMShipment, nil, nil) + suite.NoError(err) + }) + + suite.Run("checkPPMShipmentSequenceValidForCreate AllFields Valid", func() { + secondaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + secondaryDestinationAddress := factory.BuildAddress(suite.DB(), nil, nil) + TertiaryDestinationAddress := factory.BuildAddress(suite.DB(), nil, nil) + tertiaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + + newPPMShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) + newPPMShipment.SecondaryPickupAddress = &secondaryPickupAddress + newPPMShipment.TertiaryPickupAddress = &tertiaryPickupAddress + newPPMShipment.SecondaryDestinationAddress = &secondaryDestinationAddress + newPPMShipment.TertiaryDestinationAddress = &TertiaryDestinationAddress + + checker := checkPPMShipmentSequenceValidForCreate() + err := checker.Validate(suite.AppContextForTest(), newPPMShipment, nil, nil) + suite.NoError(err) + }) + suite.Run("checkPPMShipmentID", func() { suite.Run("success", func() { id := uuid.Must(uuid.NewV4()) diff --git a/pkg/services/ppmshipment/validation.go b/pkg/services/ppmshipment/validation.go index 5073e39f3b4..61af73cb531 100644 --- a/pkg/services/ppmshipment/validation.go +++ b/pkg/services/ppmshipment/validation.go @@ -77,6 +77,7 @@ func mergePPMShipment(newPPMShipment models.PPMShipment, oldPPMShipment *models. ppmShipment.ActualDestinationPostalCode = services.SetOptionalStringField(newPPMShipment.ActualDestinationPostalCode, ppmShipment.ActualDestinationPostalCode) ppmShipment.HasProGear = services.SetNoNilOptionalBoolField(newPPMShipment.HasProGear, ppmShipment.HasProGear) ppmShipment.EstimatedWeight = services.SetNoNilOptionalPoundField(newPPMShipment.EstimatedWeight, ppmShipment.EstimatedWeight) + ppmShipment.AllowableWeight = services.SetOptionalPoundField(newPPMShipment.AllowableWeight, ppmShipment.AllowableWeight) ppmShipment.ProGearWeight = services.SetNoNilOptionalPoundField(newPPMShipment.ProGearWeight, ppmShipment.ProGearWeight) ppmShipment.SpouseProGearWeight = services.SetNoNilOptionalPoundField(newPPMShipment.SpouseProGearWeight, ppmShipment.SpouseProGearWeight) ppmShipment.EstimatedIncentive = services.SetNoNilOptionalCentField(newPPMShipment.EstimatedIncentive, ppmShipment.EstimatedIncentive) diff --git a/pkg/services/pptas_report/pptas_report_list_fetcher.go b/pkg/services/pptas_report/pptas_report_list_fetcher.go index 58063b88c38..b51a14649ab 100644 --- a/pkg/services/pptas_report/pptas_report_list_fetcher.go +++ b/pkg/services/pptas_report/pptas_report_list_fetcher.go @@ -91,7 +91,7 @@ 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.Entitlement.SetWeightAllotment(string(*orders.Grade), orders.OrdersType) } weightAllotment := orders.Entitlement.WeightAllotment() @@ -119,10 +119,6 @@ func (f *pptasReportListFetcher) BuildPPTASReportsFromMoves(appCtx appcontext.Ap report.TravelType = (*string)(orders.OrdersTypeDetail) } - if orders.SAC != nil { - report.OrderNumber = orders.SAC - } - err := populateShipmentFields(&report, appCtx, move, orders, f.tacFetcher, f.loaFetcher, f.estimator) if err != nil { return nil, err @@ -177,7 +173,7 @@ func populateShipmentFields( } // populate TGET data - tacErr := inputReportTAC(&pptasShipment, orders, appCtx, tacFetcher, loaFetcher) + tacErr := inputReportTAC(report, &pptasShipment, orders, appCtx, tacFetcher, loaFetcher) if tacErr != nil { return tacErr } @@ -516,7 +512,7 @@ func buildServiceItemCrate(serviceItem models.MTOServiceItem) pptasmessages.Crat } // inputs all TAC related fields and builds full line of accounting string -func inputReportTAC(pptasShipment *pptasmessages.PPTASShipment, orders models.Order, appCtx appcontext.AppContext, tacFetcher services.TransportationAccountingCodeFetcher, loa services.LineOfAccountingFetcher) error { +func inputReportTAC(report *models.PPTASReport, pptasShipment *pptasmessages.PPTASShipment, orders models.Order, appCtx appcontext.AppContext, tacFetcher services.TransportationAccountingCodeFetcher, loa services.LineOfAccountingFetcher) error { tac, err := tacFetcher.FetchOrderTransportationAccountingCodes(models.DepartmentIndicator(*orders.DepartmentIndicator), orders.IssueDate, *orders.TAC, appCtx) if err != nil { return err @@ -530,15 +526,19 @@ func inputReportTAC(pptasShipment *pptasmessages.PPTASShipment, orders models.Or pptasShipment.Loa = &longLoa pptasShipment.FiscalYear = tac[0].TacFyTxt pptasShipment.Appro = tac[0].LineOfAccounting.LoaBafID - pptasShipment.Subhead = tac[0].LineOfAccounting.LoaObjClsID - pptasShipment.ObjClass = tac[0].LineOfAccounting.LoaAlltSnID - pptasShipment.Bcn = tac[0].LineOfAccounting.LoaSbaltmtRcpntID - pptasShipment.SubAllotCD = tac[0].LineOfAccounting.LoaInstlAcntgActID + pptasShipment.Subhead = tac[0].LineOfAccounting.LoaTrsySfxTx + pptasShipment.ObjClass = tac[0].LineOfAccounting.LoaObjClsID + pptasShipment.Bcn = tac[0].LineOfAccounting.LoaAlltSnID + pptasShipment.SubAllotCD = tac[0].LineOfAccounting.LoaSbaltmtRcpntID pptasShipment.Aaa = tac[0].LineOfAccounting.LoaTrnsnID pptasShipment.TypeCD = tac[0].LineOfAccounting.LoaJbOrdNm - pptasShipment.Paa = tac[0].LineOfAccounting.LoaDocID + pptasShipment.Paa = tac[0].LineOfAccounting.LoaInstlAcntgActID pptasShipment.CostCD = tac[0].LineOfAccounting.LoaPgmElmntID pptasShipment.Ddcd = tac[0].LineOfAccounting.LoaDptID + + if report.OrderNumber == nil { + report.OrderNumber = tac[0].LineOfAccounting.LoaDocID + } } return nil diff --git a/pkg/services/re_service_item.go b/pkg/services/re_service_item.go new file mode 100644 index 00000000000..097227125f7 --- /dev/null +++ b/pkg/services/re_service_item.go @@ -0,0 +1,13 @@ +package services + +import ( + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/models" +) + +// ServiceItemListFetcher is the exported interface for fetching a list of service items +// +//go:generate mockery --name ServiceItemListFetcher +type ServiceItemListFetcher interface { + FetchServiceItemList(appCtx appcontext.AppContext) (*models.ReServiceItems, error) +} diff --git a/pkg/services/requested_office_users.go b/pkg/services/requested_office_users.go index 574d5915c83..7ce3228300d 100644 --- a/pkg/services/requested_office_users.go +++ b/pkg/services/requested_office_users.go @@ -24,6 +24,13 @@ type RequestedOfficeUserFetcher interface { FetchRequestedOfficeUser(appCtx appcontext.AppContext, filters []QueryFilter) (models.OfficeUser, error) } +// RequestedOfficeUserFetcherPop is the exported interface for fetching a single office user +// +//go:generate mockery --name RequestedOfficeUserFetcherPop +type RequestedOfficeUserFetcherPop interface { + FetchRequestedOfficeUserByID(appCtx appcontext.AppContext, id uuid.UUID) (models.OfficeUser, error) +} + // RequestedOfficeUserFetcher is the exported interface for updating a requested office user // //go:generate mockery --name RequestedOfficeUserUpdater diff --git a/pkg/services/requested_office_users/requested_office_user_fetcher.go b/pkg/services/requested_office_users/requested_office_user_fetcher.go index e14bd9e587f..998a5db1857 100644 --- a/pkg/services/requested_office_users/requested_office_user_fetcher.go +++ b/pkg/services/requested_office_users/requested_office_user_fetcher.go @@ -1,9 +1,13 @@ package adminuser import ( + "database/sql" + "github.com/gobuffalo/validate/v3" + "github.com/gofrs/uuid" "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/apperror" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services" ) @@ -28,3 +32,27 @@ func (o *requestedOfficeUserFetcher) FetchRequestedOfficeUser(appCtx appcontext. func NewRequestedOfficeUserFetcher(builder requestedOfficeUserQueryBuilder) services.RequestedOfficeUserFetcher { return &requestedOfficeUserFetcher{builder} } + +type requestedOfficeUserFetcherPop struct { +} + +// FetchOfficeUserByID fetches an office user given an ID +func (o *requestedOfficeUserFetcherPop) FetchRequestedOfficeUserByID(appCtx appcontext.AppContext, id uuid.UUID) (models.OfficeUser, error) { + var officeUser models.OfficeUser + err := appCtx.DB().Eager("TransportationOffice").Find(&officeUser, id) + if err != nil { + switch err { + case sql.ErrNoRows: + return models.OfficeUser{}, apperror.NewNotFoundError(id, "looking for OfficeUser") + default: + return models.OfficeUser{}, apperror.NewQueryError("OfficeUser", err, "") + } + } + + return officeUser, err +} + +// NewOfficeUserFetcherPop return an implementation of the OfficeUserFetcherPop interface +func NewRequestedOfficeUserFetcherPop() services.RequestedOfficeUserFetcherPop { + return &requestedOfficeUserFetcherPop{} +} diff --git a/pkg/services/requested_office_users/requested_office_user_fetcher_test.go b/pkg/services/requested_office_users/requested_office_user_fetcher_test.go index 8f7ec777ebd..7008b9409b5 100644 --- a/pkg/services/requested_office_users/requested_office_user_fetcher_test.go +++ b/pkg/services/requested_office_users/requested_office_user_fetcher_test.go @@ -8,7 +8,10 @@ import ( "github.com/gofrs/uuid" "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/apperror" + "github.com/transcom/mymove/pkg/factory" "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/query" ) @@ -64,3 +67,24 @@ func (suite *RequestedOfficeUsersServiceSuite) TestFetchRequestedOfficeUser() { suite.Equal(models.OfficeUser{}, requestedOfficeUser) }) } + +func (suite *RequestedOfficeUsersServiceSuite) TestFetchRequestedOfficeUserPop() { + suite.Run("returns office user on success", func() { + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + fetcher := NewRequestedOfficeUserFetcherPop() + + fetchedUser, err := fetcher.FetchRequestedOfficeUserByID(suite.AppContextForTest(), officeUser.ID) + + suite.NoError(err) + suite.Equal(officeUser.ID, fetchedUser.ID) + }) + + suite.Run("returns zero value office user on error", func() { + fetcher := NewRequestedOfficeUserFetcherPop() + officeUser, err := fetcher.FetchRequestedOfficeUserByID(suite.AppContextForTest(), uuid.Nil) + + suite.Error(err) + suite.IsType(apperror.NotFoundError{}, err) + suite.Equal(uuid.Nil, officeUser.ID) + }) +} diff --git a/pkg/services/requested_office_users/requested_office_user_updater.go b/pkg/services/requested_office_users/requested_office_user_updater.go index 7bd3349eff9..4a3363290b8 100644 --- a/pkg/services/requested_office_users/requested_office_user_updater.go +++ b/pkg/services/requested_office_users/requested_office_user_updater.go @@ -19,6 +19,7 @@ func (o *requestedOfficeUserUpdater) UpdateRequestedOfficeUser(appCtx appcontext var officeUser models.OfficeUser filters := []services.QueryFilter{query.NewQueryFilter("id", "=", id.String())} err := o.builder.FetchOne(appCtx, &officeUser, filters) + if err != nil { return nil, nil, err } diff --git a/pkg/services/service_item/service_item_fetcher.go b/pkg/services/service_item/service_item_fetcher.go new file mode 100644 index 00000000000..97847f69fbb --- /dev/null +++ b/pkg/services/service_item/service_item_fetcher.go @@ -0,0 +1,26 @@ +package serviceitem + +import ( + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/apperror" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" +) + +type serviceItemFetcher struct { +} + +// NewServiceItemFetcher returns a new service item fetcher +func NewServiceItemFetcher() services.ServiceItemListFetcher { + return &serviceItemFetcher{} +} + +func (s *serviceItemFetcher) FetchServiceItemList(appCtx appcontext.AppContext) (*models.ReServiceItems, error) { + + var serviceItems models.ReServiceItems + err := appCtx.DB().Order("sort asc").Eager("ReService").All(&serviceItems) + if err != nil { + return nil, apperror.NewQueryError("ReServiceItems", err, "") + } + return &serviceItems, err +} diff --git a/pkg/services/service_item/service_item_fetcher_test.go b/pkg/services/service_item/service_item_fetcher_test.go new file mode 100644 index 00000000000..64124929988 --- /dev/null +++ b/pkg/services/service_item/service_item_fetcher_test.go @@ -0,0 +1,52 @@ +package serviceitem + +import "github.com/transcom/mymove/pkg/models" + +func (suite *ServiceItemServiceSuite) TestFetchServiceItem() { + serviceItemFetcher := NewServiceItemFetcher() + + suite.Run("check that service items is not empty", func() { + result, err := serviceItemFetcher.FetchServiceItemList(suite.AppContextForTest()) + suite.NoError(err) + suite.NotEmpty(result) + }) + + suite.Run("POEFSC is auto approved for HHG/International shipments", func() { + + result, _ := serviceItemFetcher.FetchServiceItemList(suite.AppContextForTest()) + var poefscServiceItem models.ReServiceItem + for _, v := range *result { + if models.ReServiceCodePOEFSC == v.ReService.Code && models.MTOShipmentTypeHHG == v.ShipmentType && models.MarketCodeInternational == v.MarketCode { + poefscServiceItem = v + break + } + } + suite.Equal(true, poefscServiceItem.IsAutoApproved) + }) + + suite.Run("PODFSC is auto approved for UB/International shipments", func() { + + result, _ := serviceItemFetcher.FetchServiceItemList(suite.AppContextForTest()) + var podfscServiceItem models.ReServiceItem + for _, v := range *result { + if models.ReServiceCodePODFSC == v.ReService.Code && models.MTOShipmentTypeUnaccompaniedBaggage == v.ShipmentType && models.MarketCodeInternational == v.MarketCode { + podfscServiceItem = v + break + } + } + suite.Equal(true, podfscServiceItem.IsAutoApproved) + }) + + suite.Run("DOFSIT is NOT auto approved for UB/International shipments", func() { + + result, _ := serviceItemFetcher.FetchServiceItemList(suite.AppContextForTest()) + var dofsitServiceItem models.ReServiceItem + for _, v := range *result { + if models.ReServiceCodeDOFSIT == v.ReService.Code && models.MTOShipmentTypeUnaccompaniedBaggage == v.ShipmentType && models.MarketCodeInternational == v.MarketCode { + dofsitServiceItem = v + break + } + } + suite.Equal(false, dofsitServiceItem.IsAutoApproved) + }) +} diff --git a/pkg/services/service_item/service_item_service_test.go b/pkg/services/service_item/service_item_service_test.go new file mode 100644 index 00000000000..c1068766542 --- /dev/null +++ b/pkg/services/service_item/service_item_service_test.go @@ -0,0 +1,26 @@ +package serviceitem + +import ( + "testing" + + "github.com/spf13/afero" + "github.com/stretchr/testify/suite" + + "github.com/transcom/mymove/pkg/testingsuite" +) + +type ServiceItemServiceSuite struct { + *testingsuite.PopTestSuite + fs *afero.Afero +} + +func TestServiceItemServiceSuite(t *testing.T) { + var f = afero.NewMemMapFs() + file := &afero.Afero{Fs: f} + ts := &ServiceItemServiceSuite{ + PopTestSuite: testingsuite.NewPopTestSuite(testingsuite.CurrentPackage(), testingsuite.WithPerTestTransaction()), + fs: file, + } + suite.Run(t, ts) + ts.PopTestSuite.TearDown() +} diff --git a/pkg/services/shipment_address_update/shipment_address_update_requester.go b/pkg/services/shipment_address_update/shipment_address_update_requester.go index 1b75b668336..b67239a40ab 100644 --- a/pkg/services/shipment_address_update/shipment_address_update_requester.go +++ b/pkg/services/shipment_address_update/shipment_address_update_requester.go @@ -465,7 +465,7 @@ func (f *shipmentAddressUpdateRequester) ReviewShipmentAddressChange(appCtx appc if tooApprovalStatus == models.ShipmentAddressUpdateStatusApproved { queryBuilder := query.NewQueryBuilder() - serviceItemUpdater := mtoserviceitem.NewMTOServiceItemUpdater(f.planner, queryBuilder, f.moveRouter, f.shipmentFetcher, f.addressCreator) + serviceItemUpdater := mtoserviceitem.NewMTOServiceItemUpdater(f.planner, queryBuilder, f.moveRouter, f.shipmentFetcher, f.addressCreator, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer(), ghcrateengine.NewDomesticDestinationSITDeliveryPricer(), ghcrateengine.NewDomesticOriginSITFuelSurchargePricer()) serviceItemCreator := mtoserviceitem.NewMTOServiceItemCreator(f.planner, queryBuilder, f.moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) addressUpdate.Status = models.ShipmentAddressUpdateStatusApproved @@ -497,6 +497,40 @@ func (f *shipmentAddressUpdateRequester) ReviewShipmentAddressChange(appCtx appc return nil, apperror.NewQueryError("MTOShipment", err, "") } + shipmentHasApprovedDestSIT := f.doesShipmentContainApprovedDestinationSIT(shipmentDetails) + + for i, serviceItem := range shipmentDetails.MTOServiceItems { + if shipment.PrimeEstimatedWeight != nil || shipment.PrimeActualWeight != nil { + var updatedServiceItem *models.MTOServiceItem + if serviceItem.ReService.Code == models.ReServiceCodeDDP || serviceItem.ReService.Code == models.ReServiceCodeDUPK { + updatedServiceItem, err = serviceItemUpdater.UpdateMTOServiceItemPricingEstimate(appCtx, &serviceItem, shipment, etag.GenerateEtag(serviceItem.UpdatedAt)) + if err != nil { + return nil, apperror.NewUpdateError(serviceItem.ReServiceID, err.Error()) + } + } + + if !shipmentHasApprovedDestSIT { + if serviceItem.ReService.Code == models.ReServiceCodeDLH || serviceItem.ReService.Code == models.ReServiceCodeFSC { + updatedServiceItem, err = serviceItemUpdater.UpdateMTOServiceItemPricingEstimate(appCtx, &serviceItem, shipment, etag.GenerateEtag(serviceItem.UpdatedAt)) + if err != nil { + return nil, apperror.NewUpdateError(serviceItem.ReServiceID, err.Error()) + } + } + } else { + if serviceItem.ReService.Code == models.ReServiceCodeDDSFSC || serviceItem.ReService.Code == models.ReServiceCodeDDDSIT { + updatedServiceItem, err = serviceItemUpdater.UpdateMTOServiceItemPricingEstimate(appCtx, &serviceItem, shipment, etag.GenerateEtag(serviceItem.UpdatedAt)) + if err != nil { + return nil, apperror.NewUpdateError(serviceItem.ReServiceID, err.Error()) + } + } + } + + if updatedServiceItem != nil { + shipmentDetails.MTOServiceItems[i] = *updatedServiceItem + } + } + } + // If the pricing type has changed then we automatically reject the DLH or DSH service item on the shipment since it is now inaccurate var approvedPaymentRequestsExistsForServiceItem bool if haulPricingTypeHasChanged && len(shipment.MTOServiceItems) > 0 { @@ -512,8 +546,6 @@ func (f *shipmentAddressUpdateRequester) ReviewShipmentAddressChange(appCtx appc return nil, apperror.NewQueryError("ServiceItemPaymentRequests", err, "") } - shipmentHasApprovedDestSIT := f.doesShipmentContainApprovedDestinationSIT(shipmentDetails) - // do NOT regenerate any service items if the following conditions exist: // payment has already been approved for DLH or DSH service item // destination SIT is on shipment and any of the service items have an appproved status diff --git a/pkg/services/shipment_address_update/shipment_address_update_requester_test.go b/pkg/services/shipment_address_update/shipment_address_update_requester_test.go index 863e1851d21..b2edfc521dd 100644 --- a/pkg/services/shipment_address_update/shipment_address_update_requester_test.go +++ b/pkg/services/shipment_address_update/shipment_address_update_requester_test.go @@ -14,8 +14,44 @@ import ( "github.com/transcom/mymove/pkg/services/address" moveservices "github.com/transcom/mymove/pkg/services/move" "github.com/transcom/mymove/pkg/testdatagen" + "github.com/transcom/mymove/pkg/unit" ) +func (suite *ShipmentAddressUpdateServiceSuite) setupServiceItemData() { + startDate := time.Date(2020, time.January, 1, 12, 0, 0, 0, time.UTC) + endDate := time.Date(2020, time.December, 31, 12, 0, 0, 0, time.UTC) + + testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: startDate, + EndDate: endDate, + }, + }) + + originalDomesticServiceArea := testdatagen.FetchOrMakeReDomesticServiceArea(suite.AppContextForTest().DB(), testdatagen.Assertions{ + ReDomesticServiceArea: models.ReDomesticServiceArea{ + ServiceArea: "004", + ServicesSchedule: 2, + }, + ReContract: testdatagen.FetchOrMakeReContract(suite.AppContextForTest().DB(), testdatagen.Assertions{}), + }) + + testdatagen.FetchOrMakeReDomesticLinehaulPrice(suite.DB(), testdatagen.Assertions{ + ReDomesticLinehaulPrice: models.ReDomesticLinehaulPrice{ + Contract: originalDomesticServiceArea.Contract, + ContractID: originalDomesticServiceArea.ContractID, + DomesticServiceArea: originalDomesticServiceArea, + DomesticServiceAreaID: originalDomesticServiceArea.ID, + WeightLower: unit.Pound(500), + WeightUpper: unit.Pound(9999), + MilesLower: 500, + MilesUpper: 9999, + PriceMillicents: unit.Millicents(606800), + IsPeakPeriod: false, + }, + }) +} + func (suite *ShipmentAddressUpdateServiceSuite) TestCreateApprovedShipmentAddressUpdate() { setupTestData := func() models.Move { testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ @@ -587,6 +623,8 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestTOOApprovedShipmentAddressUp suite.Run("TOO approves address change", func() { + suite.setupServiceItemData() + addressChange := factory.BuildShipmentAddressUpdate(suite.DB(), nil, []factory.Trait{ factory.GetTraitAvailableToPrimeMove, }) @@ -849,6 +887,9 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestTOOApprovedShipmentAddressUp "90210", ).Return(2500, nil).Once() move := setupTestData() + + suite.setupServiceItemData() + shipment := factory.BuildMTOShipmentWithMove(&move, suite.DB(), []factory.Customization{ { Model: models.Address{ @@ -915,6 +956,9 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestTOOApprovedShipmentAddressUp "90210", ).Return(2500, nil).Once() move := setupTestData() + + suite.setupServiceItemData() + shipment := factory.BuildMTOShipmentWithMove(&move, suite.DB(), []factory.Customization{ { Model: models.Address{ @@ -970,6 +1014,9 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestTOOApprovedShipmentAddressUp suite.Run("Service items are not rejected when pricing type does not change post TOO approval", func() { move := setupTestData() + + suite.setupServiceItemData() + shipment := factory.BuildMTOShipmentWithMove(&move, suite.DB(), nil, nil) //Generate service items to test their statuses upon approval @@ -982,6 +1029,11 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestTOOApprovedShipmentAddressUp State: "CA", PostalCode: shipment.DestinationAddress.PostalCode, } + mockPlanner.On("ZipTransitDistance", + mock.AnythingOfType("*appcontext.appContext"), + "90210", + "94535", + ).Return(2500, nil).Once() mockPlanner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), "94535", @@ -1014,6 +1066,9 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestTOOApprovedShipmentAddressUp "90210", ).Return(2500, nil).Once() move := setupTestData() + + suite.setupServiceItemData() + shipment := factory.BuildMTOShipmentWithMove(&move, suite.DB(), []factory.Customization{ { Model: models.Address{ @@ -1081,6 +1136,9 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestTOOApprovedShipmentAddressUp PostalCode: "90210", } move := setupTestData() + + suite.setupServiceItemData() + shipment := factory.BuildMTOShipmentWithMove(&move, suite.DB(), []factory.Customization{ { Model: models.Address{ diff --git a/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet.go b/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet.go index 6363d60e470..faa4bf6e3b3 100644 --- a/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet.go +++ b/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet.go @@ -194,9 +194,9 @@ 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) models.SSWMaxWeightEntitlement { +func SSWGetEntitlement(grade internalmessages.OrderPayGrade, hasDependents bool, spouseHasProGear bool, ordersType internalmessages.OrdersType) models.SSWMaxWeightEntitlement { sswEntitlements := SSWMaxWeightEntitlement{} - entitlements := models.GetWeightAllotment(grade) + entitlements := models.GetWeightAllotment(grade, ordersType) sswEntitlements.addLineItem("ProGear", entitlements.ProGearWeight) sswEntitlements.addLineItem("SpouseProGear", entitlements.ProGearWeightSpouse) if !hasDependents { @@ -1074,7 +1074,7 @@ 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) + weightAllotment := SSWGetEntitlement(*ppmShipment.Shipment.MoveTaskOrder.Orders.Grade, ppmShipment.Shipment.MoveTaskOrder.Orders.HasDependents, ppmShipment.Shipment.MoveTaskOrder.Orders.SpouseHasProGear, ppmShipment.Shipment.MoveTaskOrder.Orders.OrdersType) 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 137846bb46a..664c0765cc5 100644 --- a/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet_test.go +++ b/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet_test.go @@ -91,12 +91,12 @@ 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) + gradeWtgAllotment := models.GetWeightAllotment(grade, ordersType) 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) + weightAllotment := models.GetWeightAllotment(*ssd.Order.Grade, ssd.Order.OrdersType) // E_9 rank, no dependents, with spouse pro-gear totalWeight := weightAllotment.TotalWeightSelf + weightAllotment.ProGearWeight + weightAllotment.ProGearWeightSpouse suite.Require().Nil(err) @@ -263,12 +263,12 @@ 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) + gradeWtgAllotment := models.GetWeightAllotment(grade, ordersType) 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) + weightAllotment := models.GetWeightAllotment(*ssd.Order.Grade, ssd.Order.OrdersType) // E_9 rank, no dependents, with spouse pro-gear totalWeight := weightAllotment.TotalWeightSelf + weightAllotment.ProGearWeight + weightAllotment.ProGearWeightSpouse suite.Equal(unit.Pound(totalWeight), ssd.WeightAllotment.TotalWeight) @@ -884,9 +884,10 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestGroupExpenses() { func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatSSWGetEntitlement() { spouseHasProGear := true hasDependants := true - allotment := models.GetWeightAllotment(models.ServiceMemberGradeE1) + ordersType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION + allotment := models.GetWeightAllotment(models.ServiceMemberGradeE1, ordersType) expectedTotalWeight := allotment.TotalWeightSelfPlusDependents + allotment.ProGearWeight + allotment.ProGearWeightSpouse - sswEntitlement := SSWGetEntitlement(models.ServiceMemberGradeE1, hasDependants, spouseHasProGear) + sswEntitlement := SSWGetEntitlement(models.ServiceMemberGradeE1, hasDependants, spouseHasProGear, ordersType) suite.Equal(unit.Pound(expectedTotalWeight), sswEntitlement.TotalWeight) suite.Equal(unit.Pound(allotment.TotalWeightSelfPlusDependents), sswEntitlement.Entitlement) @@ -897,9 +898,10 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatSSWGetEntitlement() func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatSSWGetEntitlementNoDependants() { spouseHasProGear := false hasDependants := false - allotment := models.GetWeightAllotment(models.ServiceMemberGradeE1) + ordersType := internalmessages.OrdersTypePERMANENTCHANGEOFSTATION + allotment := models.GetWeightAllotment(models.ServiceMemberGradeE1, ordersType) expectedTotalWeight := allotment.TotalWeightSelf + allotment.ProGearWeight + allotment.ProGearWeightSpouse - sswEntitlement := SSWGetEntitlement(models.ServiceMemberGradeE1, hasDependants, spouseHasProGear) + sswEntitlement := SSWGetEntitlement(models.ServiceMemberGradeE1, hasDependants, spouseHasProGear, ordersType) suite.Equal(unit.Pound(expectedTotalWeight), sswEntitlement.TotalWeight) suite.Equal(unit.Pound(allotment.TotalWeightSelf), sswEntitlement.Entitlement) diff --git a/pkg/services/sit_extension/sit_extension_denier.go b/pkg/services/sit_extension/sit_extension_denier.go index 5d677c353b4..bc0ddf8c100 100644 --- a/pkg/services/sit_extension/sit_extension_denier.go +++ b/pkg/services/sit_extension/sit_extension_denier.go @@ -15,6 +15,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/ghcrateengine" mtoserviceitem "github.com/transcom/mymove/pkg/services/mto_service_item" mtoshipment "github.com/transcom/mymove/pkg/services/mto_shipment" "github.com/transcom/mymove/pkg/services/query" @@ -33,7 +34,7 @@ func NewSITExtensionDenier(moveRouter services.MoveRouter) services.SITExtension mock.Anything, mock.Anything, ).Return(400, nil) - return &sitExtensionDenier{moveRouter, mtoserviceitem.NewMTOServiceItemUpdater(planner, query.NewQueryBuilder(), moveRouter, mtoshipment.NewMTOShipmentFetcher(), address.NewAddressCreator())} + return &sitExtensionDenier{moveRouter, mtoserviceitem.NewMTOServiceItemUpdater(planner, query.NewQueryBuilder(), moveRouter, mtoshipment.NewMTOShipmentFetcher(), address.NewAddressCreator(), ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer(), ghcrateengine.NewDomesticDestinationSITDeliveryPricer(), ghcrateengine.NewDomesticOriginSITFuelSurchargePricer())} } // DenySITExtension denies the SIT Extension diff --git a/pkg/services/transportation_office/transportation_office_fetcher.go b/pkg/services/transportation_office/transportation_office_fetcher.go index af0a6983ffe..be942890c75 100644 --- a/pkg/services/transportation_office/transportation_office_fetcher.go +++ b/pkg/services/transportation_office/transportation_office_fetcher.go @@ -2,6 +2,7 @@ package transportationoffice import ( "database/sql" + "fmt" "github.com/gofrs/uuid" "github.com/pkg/errors" @@ -12,6 +13,12 @@ import ( "github.com/transcom/mymove/pkg/services" ) +type oconusGblocDepartmentIndicator struct { + Gbloc string `db:"gbloc" rw:"r"` + RateAreaName string `db:"rate_area_name" rw:"r"` + DepartmentIndicator *string `db:"department_indicator" rw:"r"` +} + type transportationOfficesFetcher struct { } @@ -21,9 +28,11 @@ func NewTransportationOfficesFetcher() services.TransportationOfficesFetcher { func (o transportationOfficesFetcher) GetTransportationOffice(appCtx appcontext.AppContext, transportationOfficeID uuid.UUID, includeOnlyPPMCloseoutOffices bool) (*models.TransportationOffice, error) { var transportationOffice models.TransportationOffice - err := appCtx.DB().EagerPreload("Address", "Address.Country"). - Where("provides_ppm_closeout = ?", includeOnlyPPMCloseoutOffices). - Find(&transportationOffice, transportationOfficeID) + q := appCtx.DB().EagerPreload("Address", "Address.Country") + if includeOnlyPPMCloseoutOffices { + q.Where("provides_ppm_closeout = ?", includeOnlyPPMCloseoutOffices) + } + err := q.Find(&transportationOffice, transportationOfficeID) if err != nil { switch err { @@ -137,30 +146,87 @@ func (o transportationOfficesFetcher) GetCounselingOffices(appCtx appcontext.App func findCounselingOffice(appCtx appcontext.AppContext, dutyLocationID uuid.UUID) (models.TransportationOffices, error) { var officeList []models.TransportationOffice - sqlQuery := ` - with counseling_offices as ( - SELECT transportation_offices.id, transportation_offices.name, transportation_offices.address_id as counseling_address, substring(addresses.postal_code, 1,3 ) as origin_zip, substring(a2.postal_code, 1,3 ) as dest_zip - FROM postal_code_to_gblocs - JOIN addresses on postal_code_to_gblocs.postal_code = addresses.postal_code - JOIN duty_locations on addresses.id = duty_locations.address_id - JOIN transportation_offices on postal_code_to_gblocs.gbloc = transportation_offices.gbloc + duty_location, err := models.FetchDutyLocation(appCtx.DB(), dutyLocationID) + if err != nil { + return officeList, err + } + + var sqlQuery string + + // ******************************** + // Find for oconus duty location + // ******************************** + if *duty_location.Address.IsOconus { + gblocDepartmentIndicator, err := findOconusGblocDepartmentIndicator(appCtx, duty_location) + if err != nil { + return officeList, err + } + + sqlQuery = ` + with counseling_offices as ( + SELECT transportation_offices.id, transportation_offices.name, transportation_offices.address_id as counseling_address, + substring(a.postal_code, 1,3 ) as origin_zip, substring(a2.postal_code, 1,3 ) as dest_zip + FROM duty_locations + JOIN addresses a on duty_locations.address_id = a.id + JOIN v_locations v on (a.us_post_region_cities_id = v.uprc_id or v.uprc_id is null) + and a.country_id = v.country_id + JOIN re_oconus_rate_areas r on r.us_post_region_cities_id = v.uprc_id + JOIN gbloc_aors on gbloc_aors.oconus_rate_area_id = r.id + JOIN jppso_regions j on gbloc_aors.jppso_regions_id = j.id + JOIN transportation_offices on j.code = transportation_offices.gbloc join addresses a2 on a2.id = transportation_offices.address_id - WHERE duty_locations.provides_services_counseling = true and duty_locations.id = $1 - ) - SELECT counseling_offices.id, counseling_offices.name - FROM counseling_offices - JOIN duty_locations duty_locations2 on counseling_offices.id = duty_locations2.transportation_office_id - JOIN addresses on counseling_offices.counseling_address = addresses.id - LEFT JOIN zip3_distances ON ( - (substring(addresses.postal_code,1 ,3) = zip3_distances.to_zip3 - AND counseling_offices.origin_zip = zip3_distances.from_zip3) - OR - (substring(addresses.postal_code,1 ,3) = zip3_distances.from_zip3 - AND counseling_offices.origin_zip = zip3_distances.to_zip3) - ) - WHERE duty_locations2.provides_services_counseling = true - group by counseling_offices.id, counseling_offices.name, zip3_distances.distance_miles - ORDER BY coalesce(zip3_distances.distance_miles,0), counseling_offices.name asc` + WHERE duty_locations.provides_services_counseling = true and duty_locations.id = $1 and j.code = $2 + and transportation_offices.provides_ppm_closeout = true + ) + SELECT counseling_offices.id, counseling_offices.name + FROM counseling_offices + JOIN addresses cnsl_address on counseling_offices.counseling_address = cnsl_address.id + LEFT JOIN zip3_distances ON ( + (substring(cnsl_address.postal_code,1 ,3) = zip3_distances.to_zip3 + AND counseling_offices.origin_zip = zip3_distances.from_zip3) + OR + (substring(cnsl_address.postal_code,1 ,3) = zip3_distances.from_zip3 + AND counseling_offices.origin_zip = zip3_distances.to_zip3) + ) + group by counseling_offices.id, counseling_offices.name, zip3_distances.distance_miles + ORDER BY coalesce(zip3_distances.distance_miles,0) asc` + + query := appCtx.DB().Q().RawQuery(sqlQuery, dutyLocationID, gblocDepartmentIndicator.Gbloc) + if err := query.All(&officeList); err != nil { + if errors.Cause(err).Error() != models.RecordNotFoundErrorString { + return officeList, err + } + } + return officeList, nil + } + + // ******************************** + // Find for conus duty location + // ******************************** + sqlQuery = ` + with counseling_offices as ( + SELECT transportation_offices.id, transportation_offices.name, transportation_offices.address_id as counseling_address, substring(addresses.postal_code, 1,3 ) as pickup_zip + FROM postal_code_to_gblocs + JOIN addresses on postal_code_to_gblocs.postal_code = addresses.postal_code + JOIN duty_locations on addresses.id = duty_locations.address_id + JOIN transportation_offices on postal_code_to_gblocs.gbloc = transportation_offices.gbloc + WHERE duty_locations.provides_services_counseling = true and duty_locations.id = $1 + ) + SELECT counseling_offices.id, counseling_offices.name + FROM counseling_offices + JOIN duty_locations duty_locations2 on counseling_offices.id = duty_locations2.transportation_office_id + JOIN addresses on counseling_offices.counseling_address = addresses.id + JOIN re_us_post_regions on addresses.postal_code = re_us_post_regions.uspr_zip_id + LEFT JOIN zip3_distances ON ( + (re_us_post_regions.zip3 = zip3_distances.to_zip3 + AND counseling_offices.pickup_zip = zip3_distances.from_zip3) + OR + (re_us_post_regions.zip3 = zip3_distances.from_zip3 + AND counseling_offices.pickup_zip = zip3_distances.to_zip3) + ) + WHERE duty_locations2.provides_services_counseling = true + group by counseling_offices.id, counseling_offices.name, zip3_distances.distance_miles + ORDER BY coalesce(zip3_distances.distance_miles,0), counseling_offices.name asc` query := appCtx.DB().Q().RawQuery(sqlQuery, dutyLocationID) if err := query.All(&officeList); err != nil { @@ -171,3 +237,71 @@ func findCounselingOffice(appCtx appcontext.AppContext, dutyLocationID uuid.UUID return officeList, nil } + +func findOconusGblocDepartmentIndicator(appCtx appcontext.AppContext, dutyLocation models.DutyLocation) (*oconusGblocDepartmentIndicator, error) { + serviceMember, err := models.FetchServiceMember(appCtx.DB(), appCtx.Session().ServiceMemberID) + if err != nil { + return nil, err + } + + var oconusGblocDepartmentIndicator []oconusGblocDepartmentIndicator + + sqlQuery := ` + select j.code gbloc, r.name rate_area_name, g.department_indicator + from addresses a, + v_locations v, + re_oconus_rate_areas o, + re_rate_areas r, + jppso_regions j, + gbloc_aors g + where a.id = $1 + and a.us_post_region_cities_id = v.uprc_id + and v.uprc_id = o.us_post_region_cities_id + and o.rate_area_id = r.id + and o.id = g.oconus_rate_area_id + and j.id = g.jppso_regions_id` + + query := appCtx.DB().Q().RawQuery(sqlQuery, dutyLocation.Address.ID) + err = query.All(&oconusGblocDepartmentIndicator) + if err != nil { + return nil, err + } + + // Determine departmentIndicator based on service member's affiliation + var departmentIndicator *string = nil + if serviceMember.Affiliation != nil && (*serviceMember.Affiliation == models.AffiliationAIRFORCE || *serviceMember.Affiliation == models.AffiliationSPACEFORCE) { + departmentIndicator = models.StringPointer(models.DepartmentIndicatorAIRANDSPACEFORCE.String()) + } else if serviceMember.Affiliation != nil && (*serviceMember.Affiliation == models.AffiliationNAVY || *serviceMember.Affiliation == models.AffiliationMARINES) { + departmentIndicator = models.StringPointer(models.DepartmentIndicatorNAVYANDMARINES.String()) + } else if serviceMember.Affiliation != nil && (*serviceMember.Affiliation == models.AffiliationARMY) { + departmentIndicator = models.StringPointer(models.DepartmentIndicatorARMY.String()) + } else if serviceMember.Affiliation != nil && (*serviceMember.Affiliation == models.AffiliationCOASTGUARD) { + departmentIndicator = models.StringPointer(models.DepartmentIndicatorCOASTGUARD.String()) + } + + // Is there a matching GBLOC for duty location address specifically for user's affiliation and duty location Zone(I-V)? + // sample oconusGblocAffiliationInfo[]: + // Gbloc RateAreaName DepartmentIndicator + // JEAT Alaska (Zone) II NULL (default) + // MBFL Alaska (Zone) II AIR_AND_SPACE_FORCE + for _, info := range oconusGblocDepartmentIndicator { + if (info.DepartmentIndicator != nil && departmentIndicator != nil) && (*info.DepartmentIndicator == *departmentIndicator) { + appCtx.Logger().Debug(fmt.Sprintf("Found specific department match -- serviceMember.Affiliation: %s, DutyLocaton: %s, GBLOC: %s, departmentIndicator: %s, RateAreaName: %s, dutyLocation.Address.ID: %s", + serviceMember.Affiliation, dutyLocation.Name, info.Gbloc, *departmentIndicator, info.RateAreaName, dutyLocation.Address.ID)) + return &info, nil + } + } + + // These were no departmentIndicator specific GBLOC for duty location -- return default GBLOC + for _, info := range oconusGblocDepartmentIndicator { + if info.DepartmentIndicator == nil { + appCtx.Logger().Debug(fmt.Sprintf("Did not find specific department match, return default -- serviceMember.Affiliation: %s, DutyLocaton: %s, GBLOC: %s, departmentIndicator: %s, RateAreaName: %s, dutyLocation.Address.ID: %s", + serviceMember.Affiliation, dutyLocation.Name, info.Gbloc, "NIL/NULL", info.RateAreaName, dutyLocation.Address.ID)) + return &info, nil + } + } + + // There is no default and department specific oconusGblocDepartmentIndicator. There is nothing in system to support. This should never happen. + return nil, apperror.NewImplementationError(fmt.Sprintf("Error: Cannot determine GBLOC -- serviceMember.Affiliation: %s, DutyLocaton: %s, departmentIndicator: %s, dutyLocation.Address.ID: %s", + serviceMember.Affiliation, dutyLocation.Name, *departmentIndicator, dutyLocation.Address.ID)) +} diff --git a/pkg/services/transportation_office/transportation_office_fetcher_test.go b/pkg/services/transportation_office/transportation_office_fetcher_test.go index a6425e4d78f..ab07d8c5e39 100644 --- a/pkg/services/transportation_office/transportation_office_fetcher_test.go +++ b/pkg/services/transportation_office/transportation_office_fetcher_test.go @@ -1,18 +1,24 @@ package transportationoffice import ( + "fmt" "testing" + "time" "github.com/gofrs/uuid" "github.com/stretchr/testify/suite" + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/auth" "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" "github.com/transcom/mymove/pkg/testingsuite" ) type TransportationOfficeServiceSuite struct { *testingsuite.PopTestSuite + toFetcher services.TransportationOfficesFetcher } func TestTransportationOfficeServiceSuite(t *testing.T) { @@ -182,3 +188,492 @@ func (suite *TransportationOfficeServiceSuite) Test_FindCounselingOffices() { suite.Equal(offices[0].Name, "PPPO Hill AFB - USAF") suite.Equal(offices[1].Name, "PPPO Travis AFB - USAF") } + +func (suite *TransportationOfficeServiceSuite) Test_Oconus_AK_FindCounselingOffices() { + testContractName := "Test_findOconusGblocDepartmentIndicator" + testContractCode := "Test_findOconusGblocDepartmentIndicator_Code" + testPostalCode := "99790" + testPostalCode2 := "99701" + testGbloc := "ABCD" + testGbloc2 := "EFGH" + testTransportationName := "TEST - PPO" + testTransportationName2 := "TEST - PPO2" + + serviceAffiliations := []models.ServiceMemberAffiliation{models.AffiliationARMY, + models.AffiliationNAVY, models.AffiliationMARINES, models.AffiliationAIRFORCE, models.AffiliationCOASTGUARD, + models.AffiliationSPACEFORCE} + + setupServiceMember := func(serviceMemberAffiliation models.ServiceMemberAffiliation) models.ServiceMember { + customServiceMember := models.ServiceMember{ + FirstName: models.StringPointer("Gregory"), + LastName: models.StringPointer("Van der Heide"), + Telephone: models.StringPointer("999-999-9999"), + SecondaryTelephone: models.StringPointer("123-555-9999"), + PersonalEmail: models.StringPointer("peyton@example.com"), + Edipi: models.StringPointer("1000011111"), + Affiliation: &serviceMemberAffiliation, + Suffix: models.StringPointer("Random suffix string"), + PhoneIsPreferred: models.BoolPointer(false), + EmailIsPreferred: models.BoolPointer(false), + } + + customAddress := models.Address{ + StreetAddress1: "987 Another Street", + } + + customUser := models.User{ + OktaEmail: "test_email@email.com", + } + + serviceMember := factory.BuildServiceMember(suite.DB(), []factory.Customization{ + {Model: customServiceMember}, + {Model: customAddress}, + {Model: customUser}, + }, nil) + + return serviceMember + } + + createContract := func(appCtx appcontext.AppContext, contractCode string, contractName string) (*models.ReContract, error) { + // See if contract code already exists. + exists, err := appCtx.DB().Where("code = ?", contractCode).Exists(&models.ReContract{}) + if err != nil { + return nil, fmt.Errorf("could not determine if contract code [%s] existed: %w", contractCode, err) + } + if exists { + return nil, fmt.Errorf("the provided contract code [%s] already exists", contractCode) + } + + // Contract code is new; insert it. + contract := models.ReContract{ + Code: contractCode, + Name: contractName, + } + verrs, err := appCtx.DB().ValidateAndSave(&contract) + if verrs.HasAny() { + return nil, fmt.Errorf("validation errors when saving contract [%+v]: %w", contract, verrs) + } + if err != nil { + return nil, fmt.Errorf("could not save contract [%+v]: %w", contract, err) + } + return &contract, nil + } + + setupDataForOconusSearchCounselingOffice := func(contract models.ReContract, postalCode string, gbloc string, transportationName string) (models.ReRateArea, models.OconusRateArea, models.UsPostRegionCity, models.DutyLocation) { + rateAreaCode := uuid.Must(uuid.NewV4()).String()[0:5] + rateArea := models.ReRateArea{ + ID: uuid.Must(uuid.NewV4()), + ContractID: contract.ID, + IsOconus: true, + Code: rateAreaCode, + Name: fmt.Sprintf("Alaska-%s", rateAreaCode), + Contract: contract, + } + verrs, err := suite.DB().ValidateAndCreate(&rateArea) + if verrs.HasAny() { + suite.Fail(verrs.Error()) + } + if err != nil { + suite.Fail(err.Error()) + } + + us_country, err := models.FetchCountryByCode(suite.DB(), "US") + suite.NotNil(us_country) + suite.Nil(err) + + usprc, err := models.FindByZipCode(suite.AppContextForTest().DB(), postalCode) + suite.NotNil(usprc) + suite.FatalNoError(err) + + oconusRateArea := models.OconusRateArea{ + ID: uuid.Must(uuid.NewV4()), + RateAreaId: rateArea.ID, + CountryId: us_country.ID, + UsPostRegionCityId: usprc.ID, + Active: true, + } + verrs, err = suite.DB().ValidateAndCreate(&oconusRateArea) + if verrs.HasAny() { + suite.Fail(verrs.Error()) + } + if err != nil { + suite.Fail(err.Error()) + } + + address := models.Address{ + StreetAddress1: "n/a", + City: "SomeCity", + State: "AK", + PostalCode: postalCode, + County: "SomeCounty", + IsOconus: models.BoolPointer(true), + UsPostRegionCityId: &usprc.ID, + CountryId: models.UUIDPointer(us_country.ID), + } + suite.MustSave(&address) + + origDutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + AddressID: address.ID, + ProvidesServicesCounseling: true, + }, + }, + { + Model: models.TransportationOffice{ + Name: transportationName, + Gbloc: gbloc, + ProvidesCloseout: true, + }, + }, + }, nil) + suite.MustSave(&origDutyLocation) + + found_duty_location, _ := models.FetchDutyLocation(suite.DB(), origDutyLocation.ID) + + return rateArea, oconusRateArea, *usprc, found_duty_location + } + + suite.Run("success - findOconusGblocDepartmentIndicator - returns default GLOC for departmentAffiliation if no specific departmentAffilation mapping is defined", func() { + contract, err := createContract(suite.AppContextForTest(), testContractCode, testContractName) + suite.NotNil(contract) + suite.FatalNoError(err) + + const fairbanksAlaskaPostalCode = "99790" + _, oconusRateArea, _, dutylocation := setupDataForOconusSearchCounselingOffice(*contract, fairbanksAlaskaPostalCode, testGbloc, testTransportationName) + + // setup department affiliation to GBLOC mappings + expected_gbloc := "TEST-GBLOC" + jppsoRegion := models.JppsoRegions{ + Code: expected_gbloc, + Name: "TEST PPM", + } + suite.MustSave(&jppsoRegion) + + gblocAors := models.GblocAors{ + JppsoRegionID: jppsoRegion.ID, + OconusRateAreaID: oconusRateArea.ID, + // DepartmentIndicator is nil, + } + suite.MustSave(&gblocAors) + + serviceAffiliations := []models.ServiceMemberAffiliation{models.AffiliationARMY, + models.AffiliationNAVY, models.AffiliationMARINES, models.AffiliationAIRFORCE, models.AffiliationCOASTGUARD, + models.AffiliationSPACEFORCE} + + // loop through and make sure all branches are using expected default GBLOC + for _, affiliation := range serviceAffiliations { + serviceMember := setupServiceMember(affiliation) + appCtx := suite.AppContextWithSessionForTest(&auth.Session{ + ServiceMemberID: serviceMember.ID, + }) + suite.Nil(err) + departmentIndictor, err := findOconusGblocDepartmentIndicator(appCtx, dutylocation) + suite.NotNil(departmentIndictor) + suite.Nil(err) + suite.Nil(departmentIndictor.DepartmentIndicator) + suite.Equal(expected_gbloc, departmentIndictor.Gbloc) + } + }) + + suite.Run("success - findOconusGblocDepartmentIndicator - returns specific GLOC for departmentAffiliation when a specific departmentAffilation mapping is defined", func() { + contract, err := createContract(suite.AppContextForTest(), testContractCode, testContractName) + suite.NotNil(contract) + suite.FatalNoError(err) + + _, oconusRateArea, _, dutylocation := setupDataForOconusSearchCounselingOffice(*contract, testPostalCode, testGbloc, testTransportationName) + + departmentIndicators := []models.DepartmentIndicator{models.DepartmentIndicatorARMY, + models.DepartmentIndicatorARMYCORPSOFENGINEERS, models.DepartmentIndicatorCOASTGUARD, + models.DepartmentIndicatorNAVYANDMARINES, models.DepartmentIndicatorAIRANDSPACEFORCE} + + expectedAffiliationToDepartmentIndicatorMap := make(map[string]string, 0) + expectedAffiliationToDepartmentIndicatorMap[models.AffiliationARMY.String()] = models.DepartmentIndicatorARMY.String() + expectedAffiliationToDepartmentIndicatorMap[models.AffiliationNAVY.String()] = models.DepartmentIndicatorNAVYANDMARINES.String() + expectedAffiliationToDepartmentIndicatorMap[models.AffiliationMARINES.String()] = models.DepartmentIndicatorNAVYANDMARINES.String() + expectedAffiliationToDepartmentIndicatorMap[models.AffiliationAIRFORCE.String()] = models.DepartmentIndicatorAIRANDSPACEFORCE.String() + expectedAffiliationToDepartmentIndicatorMap[models.AffiliationCOASTGUARD.String()] = models.DepartmentIndicatorCOASTGUARD.String() + expectedAffiliationToDepartmentIndicatorMap[models.AffiliationSPACEFORCE.String()] = models.DepartmentIndicatorAIRANDSPACEFORCE.String() + + // setup department affiliation to GBLOC mappings + expected_gbloc := "TEST-GBLOC" + jppsoRegion := models.JppsoRegions{ + Code: expected_gbloc, + Name: "TEST PPM", + } + suite.MustSave(&jppsoRegion) + + defaultGblocAors := models.GblocAors{ + JppsoRegionID: jppsoRegion.ID, + OconusRateAreaID: oconusRateArea.ID, + //DepartmentIndicator is nil, + } + suite.MustSave(&defaultGblocAors) + + // setup specific departmentAffiliation mapping for each branch + for _, departmentIndicator := range departmentIndicators { + gblocAors := models.GblocAors{ + JppsoRegionID: jppsoRegion.ID, + OconusRateAreaID: oconusRateArea.ID, + DepartmentIndicator: models.StringPointer(departmentIndicator.String()), + } + suite.MustSave(&gblocAors) + } + + // loop through and make sure all branches are using it's own dedicated GBLOC and not default + for _, affiliation := range serviceAffiliations { + serviceMember := setupServiceMember(affiliation) + appCtx := suite.AppContextWithSessionForTest(&auth.Session{ + ServiceMemberID: serviceMember.ID, + }) + suite.Nil(err) + departmentIndictor, err := findOconusGblocDepartmentIndicator(appCtx, dutylocation) + suite.NotNil(departmentIndictor) + suite.Nil(err) + suite.NotNil(departmentIndictor.DepartmentIndicator) + if match, ok := expectedAffiliationToDepartmentIndicatorMap[affiliation.String()]; ok { + // verify service member's affiliation matches on specific departmentIndicator mapping record + suite.Equal(match, *departmentIndictor.DepartmentIndicator) + } else { + suite.Fail(fmt.Sprintf("key does not exist for %s", affiliation.String())) + } + suite.Equal(expected_gbloc, departmentIndictor.Gbloc) + } + }) + + suite.Run("failure -- returns error when there are default and no department specific GBLOC", func() { + contract, err := createContract(suite.AppContextForTest(), testContractCode, testContractName) + suite.NotNil(contract) + suite.FatalNoError(err) + + _, _, _, dutylocation := setupDataForOconusSearchCounselingOffice(*contract, testPostalCode, testGbloc, testTransportationName) + + // No specific departmentAffiliation mapping or default were created. + // Expect an error response when nothing is found. + serviceMember := setupServiceMember(models.AffiliationAIRFORCE) + appCtx := suite.AppContextWithSessionForTest(&auth.Session{ + ServiceMemberID: serviceMember.ID, + }) + suite.Nil(err) + departmentIndictor, err := findOconusGblocDepartmentIndicator(appCtx, dutylocation) + suite.Nil(departmentIndictor) + suite.NotNil(err) + }) + + suite.Run("failure - findOconusGblocDepartmentIndicator - returns error when find service member ID fails", func() { + contract, err := createContract(suite.AppContextForTest(), testContractCode, testContractName) + suite.NotNil(contract) + suite.FatalNoError(err) + + _, _, _, dutylocation := setupDataForOconusSearchCounselingOffice(*contract, testPostalCode, testGbloc, testTransportationName) + + appCtx := suite.AppContextWithSessionForTest(&auth.Session{ + // create fake service member ID to raise NOT found error + ServiceMemberID: uuid.Must(uuid.NewV4()), + }) + + suite.Nil(err) + departmentIndictor, err := findOconusGblocDepartmentIndicator(appCtx, dutylocation) + suite.Nil(departmentIndictor) + suite.NotNil(err) + }) + + suite.Run("failure - not found duty location returns error", func() { + appCtx := suite.AppContextWithSessionForTest(&auth.Session{ + ServiceMemberID: uuid.Must(uuid.NewV4()), + }) + unknown_duty_location_id := uuid.Must(uuid.NewV4()) + offices, err := findCounselingOffice(appCtx, unknown_duty_location_id) + suite.Nil(offices) + suite.NotNil(err) + }) + + suite.Run("success - offices using default departmentIndicator mapping", func() { + contract, err := createContract(suite.AppContextForTest(), testContractCode, testContractName) + suite.NotNil(contract) + suite.FatalNoError(err) + + _, oconusRateArea, _, dutylocation := setupDataForOconusSearchCounselingOffice(*contract, testPostalCode, testGbloc, testTransportationName) + + // setup department affiliation to GBLOC mappings + jppsoRegion := models.JppsoRegions{ + Code: testGbloc, + Name: "TEST PPM", + } + suite.MustSave(&jppsoRegion) + + gblocAors := models.GblocAors{ + JppsoRegionID: jppsoRegion.ID, + OconusRateAreaID: oconusRateArea.ID, + // DepartmentIndicator is nil, + } + suite.MustSave(&gblocAors) + + postalCodeToGBLOC := models.PostalCodeToGBLOC{ + PostalCode: testPostalCode, + GBLOC: testGbloc, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + suite.MustSave(&postalCodeToGBLOC) + + serviceMember := setupServiceMember(models.AffiliationAIRFORCE) + appCtx := suite.AppContextWithSessionForTest(&auth.Session{ + ServiceMemberID: serviceMember.ID, + }) + + suite.Nil(err) + offices, err := findCounselingOffice(appCtx, dutylocation.ID) + suite.NotNil(offices) + suite.Nil(err) + suite.Equal(1, len(offices)) + suite.Equal(testTransportationName, offices[0].Name) + + // add another transportation office + factory.BuildTransportationOffice(suite.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{ + Name: testTransportationName2, + ProvidesCloseout: true, + Gbloc: testGbloc, + }, + }, + }, nil) + offices, err = findCounselingOffice(appCtx, dutylocation.ID) + suite.NotNil(offices) + suite.Nil(err) + suite.Equal(2, len(offices)) + }) + + suite.Run("success - returns correct office based on service affiliation -- simulate Zone 2 scenerio", func() { + contract, err := createContract(suite.AppContextForTest(), testContractCode, testContractName) + suite.NotNil(contract) + suite.FatalNoError(err) + + _, oconusRateArea, _, dutylocation := setupDataForOconusSearchCounselingOffice(*contract, testPostalCode, testGbloc, testTransportationName) + + // ****************************************************************************** + // setup department affiliation to GBLOC mappings for AF/SF + // ****************************************************************************** + jppsoRegion_AFSF := models.JppsoRegions{ + Code: testGbloc, + Name: "TEST PPO", + } + suite.MustSave(&jppsoRegion_AFSF) + + gblocAors_AFSF := models.GblocAors{ + JppsoRegionID: jppsoRegion_AFSF.ID, + OconusRateAreaID: oconusRateArea.ID, + DepartmentIndicator: models.StringPointer(models.DepartmentIndicatorAIRANDSPACEFORCE.String()), + } + suite.MustSave(&gblocAors_AFSF) + + postalCodeToGBLOC_AFSF := models.PostalCodeToGBLOC{ + PostalCode: testPostalCode, + GBLOC: testGbloc, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + suite.MustSave(&postalCodeToGBLOC_AFSF) + // ****************************************************************************** + + // ****************************************************************************** + // setup department affiliation to GBLOC mappings for other branches NOT AF/SF + // ****************************************************************************** + jppsoRegion_not_AFSF := models.JppsoRegions{ + Code: testGbloc2, + Name: "TEST PPO 2", + } + suite.MustSave(&jppsoRegion_not_AFSF) + + gblocAors_not_AFSF := models.GblocAors{ + JppsoRegionID: jppsoRegion_not_AFSF.ID, + OconusRateAreaID: oconusRateArea.ID, + } + suite.MustSave(&gblocAors_not_AFSF) + + postalCodeToGBLOC_not_AFSF := models.PostalCodeToGBLOC{ + PostalCode: testPostalCode2, + GBLOC: testGbloc2, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + suite.MustSave(&postalCodeToGBLOC_not_AFSF) + + // add transportation office for other branches not AF/SF + factory.BuildTransportationOffice(suite.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{ + Name: testTransportationName2, + ProvidesCloseout: true, + Gbloc: testGbloc2, + }, + }, + }, nil) + // ****************************************************************************** + + for _, affiliation := range serviceAffiliations { + serviceMember := setupServiceMember(affiliation) + if affiliation == models.AffiliationAIRFORCE || affiliation == models.AffiliationSPACEFORCE { + appCtx := suite.AppContextWithSessionForTest(&auth.Session{ + ServiceMemberID: serviceMember.ID, + }) + offices, err := findCounselingOffice(appCtx, dutylocation.ID) + suite.NotNil(offices) + suite.Nil(err) + suite.Equal(1, len(offices)) + // verify expected office is for AF/SF and not for Navy ..etc.. + suite.Equal(testTransportationName, offices[0].Name) + suite.NotEqual(testTransportationName2, offices[0].Name) + } else { + appCtx := suite.AppContextWithSessionForTest(&auth.Session{ + ServiceMemberID: serviceMember.ID, + }) + offices, err := findCounselingOffice(appCtx, dutylocation.ID) + suite.NotNil(offices) + suite.Nil(err) + suite.Equal(1, len(offices)) + // verify expected office is for Navy ..etc.. and not AF/SF + suite.Equal(testTransportationName2, offices[0].Name) + suite.NotEqual(testTransportationName, offices[0].Name) + } + } + }) +} + +func (suite *TransportationOfficeServiceSuite) Test_GetTransportationOffice() { + suite.toFetcher = NewTransportationOfficesFetcher() + transportationOffice1 := factory.BuildTransportationOffice(suite.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{ + Name: "OFFICE ONE", + ProvidesCloseout: true, + }, + }, + }, nil) + + transportationOffice2 := factory.BuildTransportationOffice(suite.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{ + Name: "OFFICE TWO", + ProvidesCloseout: false, + }, + }, + }, nil) + + office1t, err1t := suite.toFetcher.GetTransportationOffice(suite.AppContextForTest(), transportationOffice1.ID, true) + office1f, err1f := suite.toFetcher.GetTransportationOffice(suite.AppContextForTest(), transportationOffice1.ID, false) + + _, err2t := suite.toFetcher.GetTransportationOffice(suite.AppContextForTest(), transportationOffice2.ID, true) + office2f, err2f := suite.toFetcher.GetTransportationOffice(suite.AppContextForTest(), transportationOffice2.ID, false) + + suite.NoError(err1t) + suite.NoError(err1f) + // Should return an error since no office matches the ID and provides closeout + suite.Error(err2t) + suite.NoError(err2f) + + suite.Equal("OFFICE ONE", office1t.Name) + suite.Equal("OFFICE ONE", office1f.Name) + suite.Equal("OFFICE TWO", office2f.Name) +} diff --git a/pkg/services/transportation_office_assignments.go b/pkg/services/transportation_office_assignments.go new file mode 100644 index 00000000000..782e4a9a960 --- /dev/null +++ b/pkg/services/transportation_office_assignments.go @@ -0,0 +1,22 @@ +package services + +import ( + "github.com/gofrs/uuid" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/models" +) + +// TransportaionOfficeAssignmentFetcher is the service object interface for FetchTransportaionOfficeAssignmentsByOfficeUserID +// +//go:generate mockery --name TransportaionOfficeAssignmentFetcher +type TransportaionOfficeAssignmentFetcher interface { + FetchTransportaionOfficeAssignmentsByOfficeUserID(appCtx appcontext.AppContext, officeUserId uuid.UUID) (models.TransportationOfficeAssignments, error) +} + +// TransportaionOfficeAssignmentUpdater is the service object interface for UpdateTransportaionOfficeAssignments +// +//go:generate mockery --name TransportaionOfficeAssignmentUpdater +type TransportaionOfficeAssignmentUpdater interface { + UpdateTransportaionOfficeAssignments(appCtx appcontext.AppContext, officeUserId uuid.UUID, transportationOfficeAssignments models.TransportationOfficeAssignments) (models.TransportationOfficeAssignments, error) +} diff --git a/pkg/services/transportation_office_assignments/transportation_office_assignments_fetcher.go b/pkg/services/transportation_office_assignments/transportation_office_assignments_fetcher.go new file mode 100644 index 00000000000..fcf965e6ba3 --- /dev/null +++ b/pkg/services/transportation_office_assignments/transportation_office_assignments_fetcher.go @@ -0,0 +1,36 @@ +package transportationofficeassignments + +import ( + "github.com/gofrs/uuid" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" +) + +type transportaionOfficeAssignmentFetcher struct { +} + +// NewTransportaionOfficeAssignmentUpdater creates a new struct with the service dependencies +func NewTransportaionOfficeAssignmentFetcher() services.TransportaionOfficeAssignmentFetcher { + return transportaionOfficeAssignmentFetcher{} +} + +func (fetcher transportaionOfficeAssignmentFetcher) FetchTransportaionOfficeAssignmentsByOfficeUserID( + appCtx appcontext.AppContext, + officeUserId uuid.UUID, +) (models.TransportationOfficeAssignments, error) { + + var transportationOfficeAssignments models.TransportationOfficeAssignments + + err := appCtx.DB().Q().EagerPreload("TransportationOffice"). + Join("transportation_offices", "transportation_office_assignments.transportation_office_id = transportation_offices.id"). + Where("transportation_office_assignments.id = ?", (officeUserId)). + All(&transportationOfficeAssignments) + + if err != nil { + return models.TransportationOfficeAssignments{}, err + } + + return transportationOfficeAssignments, nil +} diff --git a/pkg/services/transportation_office_assignments/transportation_office_assignments_updater.go b/pkg/services/transportation_office_assignments/transportation_office_assignments_updater.go new file mode 100644 index 00000000000..69bc562e1c9 --- /dev/null +++ b/pkg/services/transportation_office_assignments/transportation_office_assignments_updater.go @@ -0,0 +1,56 @@ +package transportationofficeassignments + +import ( + "github.com/gofrs/uuid" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" +) + +type transportaionOfficeAssignmentUpdater struct { +} + +// NewTransportaionOfficeAssignmentUpdater creates a new struct with the service dependencies +func NewTransportaionOfficeAssignmentUpdater() services.TransportaionOfficeAssignmentUpdater { + return transportaionOfficeAssignmentUpdater{} +} + +func (updater transportaionOfficeAssignmentUpdater) UpdateTransportaionOfficeAssignments( + appCtx appcontext.AppContext, + officeUserId uuid.UUID, + newAssignments models.TransportationOfficeAssignments, +) (models.TransportationOfficeAssignments, error) { + + var existingAssignments models.TransportationOfficeAssignments + err := appCtx.DB().Where("id = ?", officeUserId).All(&existingAssignments) + + if err != nil { + return models.TransportationOfficeAssignments{}, err + } + + var assignmentsToCreate models.TransportationOfficeAssignments + + for _, newAssignment := range newAssignments { + newAssignment.ID = officeUserId + assignmentsToCreate = append(assignmentsToCreate, newAssignment) + } + + err = appCtx.DB().Destroy(existingAssignments) + if err != nil { + return models.TransportationOfficeAssignments{}, err + } + + err = appCtx.DB().Create(assignmentsToCreate) + if err != nil { + return models.TransportationOfficeAssignments{}, err + } + + var assignments models.TransportationOfficeAssignments + err = appCtx.DB().Where("id = ?", officeUserId).All(&assignments) + if err != nil { + return models.TransportationOfficeAssignments{}, err + } + + return assignments, nil +} diff --git a/pkg/services/user/user_updater.go b/pkg/services/user/user_updater.go index 7026cf096a7..d1c9985e4d8 100644 --- a/pkg/services/user/user_updater.go +++ b/pkg/services/user/user_updater.go @@ -75,7 +75,7 @@ func (o *userUpdater) UpdateUser(appCtx appcontext.AppContext, id uuid.UUID, use payload := adminmessages.OfficeUserUpdate{ Active: &user.Active, } - _, verrs, err = o.officeUserUpdater.UpdateOfficeUser(appCtx, foundOfficeUser.ID, &payload) + _, verrs, err = o.officeUserUpdater.UpdateOfficeUser(appCtx, foundOfficeUser.ID, &payload, uuid.Nil) if verrs != nil { appCtx.Logger().Error("Could not update office user", zap.Error(verrs)) diff --git a/pkg/services/weight_ticket/weight_ticket_updater.go b/pkg/services/weight_ticket/weight_ticket_updater.go index 9a78d3288af..7366e7a1450 100644 --- a/pkg/services/weight_ticket/weight_ticket_updater.go +++ b/pkg/services/weight_ticket/weight_ticket_updater.go @@ -122,7 +122,6 @@ func mergeWeightTicket(weightTicket models.WeightTicket, originalWeightTicket mo mergedWeightTicket.EmptyWeight = services.SetNoNilOptionalPoundField(weightTicket.EmptyWeight, mergedWeightTicket.EmptyWeight) mergedWeightTicket.MissingEmptyWeightTicket = services.SetNoNilOptionalBoolField(weightTicket.MissingEmptyWeightTicket, mergedWeightTicket.MissingEmptyWeightTicket) mergedWeightTicket.FullWeight = services.SetNoNilOptionalPoundField(weightTicket.FullWeight, mergedWeightTicket.FullWeight) - mergedWeightTicket.AllowableWeight = services.SetNoNilOptionalPoundField(weightTicket.AllowableWeight, mergedWeightTicket.AllowableWeight) mergedWeightTicket.MissingFullWeightTicket = services.SetNoNilOptionalBoolField(weightTicket.MissingFullWeightTicket, mergedWeightTicket.MissingFullWeightTicket) mergedWeightTicket.OwnsTrailer = services.SetNoNilOptionalBoolField(weightTicket.OwnsTrailer, mergedWeightTicket.OwnsTrailer) mergedWeightTicket.TrailerMeetsCriteria = services.SetNoNilOptionalBoolField(weightTicket.TrailerMeetsCriteria, mergedWeightTicket.TrailerMeetsCriteria) diff --git a/pkg/services/weight_ticket/weight_ticket_updater_test.go b/pkg/services/weight_ticket/weight_ticket_updater_test.go index 98394e6d7c8..82a883b991c 100644 --- a/pkg/services/weight_ticket/weight_ticket_updater_test.go +++ b/pkg/services/weight_ticket/weight_ticket_updater_test.go @@ -192,7 +192,6 @@ func (suite *WeightTicketSuite) TestUpdateWeightTicket() { OwnsTrailer: models.BoolPointer(false), TrailerMeetsCriteria: models.BoolPointer(false), AdjustedNetWeight: models.PoundPointer(1200), - AllowableWeight: models.PoundPointer(1200), NetWeightRemarks: models.StringPointer("Weight has been adjusted"), } diff --git a/pkg/testdatagen/make_entitlement.go b/pkg/testdatagen/make_entitlement.go index c8e1a2d3146..e71bf02cec1 100644 --- a/pkg/testdatagen/make_entitlement.go +++ b/pkg/testdatagen/make_entitlement.go @@ -14,6 +14,7 @@ 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 @@ -32,7 +33,7 @@ func makeEntitlement(db *pop.Connection, assertions Assertions) models.Entitleme RequiredMedicalEquipmentWeight: rmeWeight, OrganizationalClothingAndIndividualEquipment: ocie, } - entitlement.SetWeightAllotment(string(*grade)) + entitlement.SetWeightAllotment(string(*grade), ordersType) dBAuthorizedWeight := entitlement.AuthorizedWeight() entitlement.DBAuthorizedWeight = dBAuthorizedWeight diff --git a/pkg/testdatagen/make_re_contract_year.go b/pkg/testdatagen/make_re_contract_year.go index 2f9c2c2e378..e8a74cf332f 100644 --- a/pkg/testdatagen/make_re_contract_year.go +++ b/pkg/testdatagen/make_re_contract_year.go @@ -20,7 +20,7 @@ func MakeReContractYear(db *pop.Connection, assertions Assertions) models.ReCont reContractYear := models.ReContractYear{ ContractID: reContract.ID, - Name: "Test Contract Year", + Name: "Base Period Year 1", StartDate: time.Date(TestYear, time.January, 1, 0, 0, 0, 0, time.UTC), EndDate: time.Date(TestYear, time.December, 31, 0, 0, 0, 0, time.UTC), Escalation: 1.0197, diff --git a/pkg/testdatagen/scenario/shared.go b/pkg/testdatagen/scenario/shared.go index 44f2bce1b66..7b4b2bb4e22 100644 --- a/pkg/testdatagen/scenario/shared.go +++ b/pkg/testdatagen/scenario/shared.go @@ -1203,6 +1203,7 @@ func createApprovedMoveWithPPMExcessWeight(appCtx appcontext.AppContext, userUpl AdvanceAmountReceived: models.CentPointer(unit.Cents(340000)), AdvanceStatus: (*models.PPMAdvanceStatus)(models.StringPointer(string(models.PPMAdvanceStatusApproved))), W2Address: &address, + AllowableWeight: models.PoundPointer(19000), }, } @@ -1365,6 +1366,7 @@ func createApprovedMoveWithPPMCloseoutComplete(appCtx appcontext.AppContext, use approvedAt := time.Date(2022, 4, 15, 12, 30, 0, 0, time.UTC) address := factory.BuildAddress(appCtx.DB(), nil, nil) approvedAdvanceStatus := models.PPMAdvanceStatusApproved + allowableWeight := unit.Pound(4000) assertions := testdatagen.Assertions{ UserUploader: userUploader, @@ -1387,6 +1389,7 @@ func createApprovedMoveWithPPMCloseoutComplete(appCtx appcontext.AppContext, use HasReceivedAdvance: models.BoolPointer(true), AdvanceAmountReceived: models.CentPointer(unit.Cents(340000)), W2Address: &address, + AllowableWeight: &allowableWeight, }, } @@ -1425,6 +1428,7 @@ func createApprovedMoveWithPPMCloseoutCompleteMultipleWeightTickets(appCtx appco approvedAt := time.Date(2022, 4, 15, 12, 30, 0, 0, time.UTC) address := factory.BuildAddress(appCtx.DB(), nil, nil) approvedAdvanceStatus := models.PPMAdvanceStatusApproved + allowableWeight := unit.Pound(8000) assertions := testdatagen.Assertions{ UserUploader: userUploader, @@ -1447,6 +1451,7 @@ func createApprovedMoveWithPPMCloseoutCompleteMultipleWeightTickets(appCtx appco HasReceivedAdvance: models.BoolPointer(true), AdvanceAmountReceived: models.CentPointer(unit.Cents(340000)), W2Address: &address, + AllowableWeight: &allowableWeight, }, } @@ -1503,6 +1508,7 @@ func createApprovedMoveWithPPMCloseoutCompleteWithExpenses(appCtx appcontext.App approvedAt := time.Date(2022, 4, 15, 12, 30, 0, 0, time.UTC) address := factory.BuildAddress(appCtx.DB(), nil, nil) approvedAdvanceStatus := models.PPMAdvanceStatusApproved + allowableWeight := unit.Pound(4000) assertions := testdatagen.Assertions{ UserUploader: userUploader, @@ -1525,6 +1531,7 @@ func createApprovedMoveWithPPMCloseoutCompleteWithExpenses(appCtx appcontext.App HasReceivedAdvance: models.BoolPointer(true), AdvanceAmountReceived: models.CentPointer(unit.Cents(340000)), W2Address: &address, + AllowableWeight: &allowableWeight, }, } @@ -1594,6 +1601,7 @@ func createApprovedMoveWithPPMCloseoutCompleteWithAllDocTypes(appCtx appcontext. approvedAt := time.Date(2022, 4, 15, 12, 30, 0, 0, time.UTC) address := factory.BuildAddress(appCtx.DB(), nil, nil) approvedAdvanceStatus := models.PPMAdvanceStatusApproved + allowableWeight := unit.Pound(4000) assertions := testdatagen.Assertions{ UserUploader: userUploader, @@ -1616,6 +1624,7 @@ func createApprovedMoveWithPPMCloseoutCompleteWithAllDocTypes(appCtx appcontext. HasReceivedAdvance: models.BoolPointer(true), AdvanceAmountReceived: models.CentPointer(unit.Cents(340000)), W2Address: &address, + AllowableWeight: &allowableWeight, }, } @@ -4274,7 +4283,7 @@ func createHHGWithOriginSITServiceItems( mock.Anything, mock.Anything, ).Return(400, nil) - serviceItemUpdator := mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator) + serviceItemUpdator := mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer(), ghcrateengine.NewDomesticDestinationSITDeliveryPricer(), ghcrateengine.NewDomesticOriginSITFuelSurchargePricer()) var originFirstDaySIT models.MTOServiceItem var originAdditionalDaySIT models.MTOServiceItem @@ -4535,7 +4544,7 @@ func createHHGWithDestinationSITServiceItems(appCtx appcontext.AppContext, prime mock.Anything, mock.Anything, ).Return(400, nil) - serviceItemUpdator := mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator) + serviceItemUpdator := mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer(), ghcrateengine.NewDomesticDestinationSITDeliveryPricer(), ghcrateengine.NewDomesticOriginSITFuelSurchargePricer()) var destinationFirstDaySIT models.MTOServiceItem var destinationAdditionalDaySIT models.MTOServiceItem @@ -5014,7 +5023,7 @@ func createHHGWithPaymentServiceItems( mock.Anything, mock.Anything, ).Return(400, nil) - serviceItemUpdater := mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator) + serviceItemUpdater := mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer(), ghcrateengine.NewDomesticDestinationSITDeliveryPricer(), ghcrateengine.NewDomesticOriginSITFuelSurchargePricer()) var originFirstDaySIT models.MTOServiceItem var originAdditionalDaySIT models.MTOServiceItem @@ -10427,7 +10436,8 @@ func CreateNeedsServicesCounseling(appCtx appcontext.AppContext, ordersType inte requestedPickupDate = submittedAt.Add(30 * 24 * time.Hour) requestedDeliveryDate = requestedPickupDate.Add(7 * 24 * time.Hour) - regularMTOShipment := factory.BuildMTOShipment(db, []factory.Customization{ + + factory.BuildMTOShipment(db, []factory.Customization{ { Model: move, LinkOnly: true, @@ -10459,29 +10469,6 @@ func CreateNeedsServicesCounseling(appCtx appcontext.AppContext, ordersType inte }, }, nil) - if shipmentType == models.MTOShipmentTypeMobileHome { - factory.BuildMobileHomeShipment(appCtx.DB(), []factory.Customization{ - { - Model: models.MobileHome{ - Year: models.IntPointer(2000), - Make: models.StringPointer("Boat Make"), - Model: models.StringPointer("Boat Model"), - LengthInInches: models.IntPointer(300), - WidthInInches: models.IntPointer(108), - HeightInInches: models.IntPointer(72), - }, - }, - { - Model: move, - LinkOnly: true, - }, - { - Model: regularMTOShipment, - LinkOnly: true, - }, - }, nil) - } - return move } diff --git a/pkg/utils/utility.go b/pkg/utils/utility.go new file mode 100644 index 00000000000..af8ea208632 --- /dev/null +++ b/pkg/utils/utility.go @@ -0,0 +1,10 @@ +package utils + +import ( + "strings" +) + +// checks if string is null, empty, or whitespace +func IsNullOrWhiteSpace(s *string) bool { + return s == nil || len(strings.TrimSpace(*s)) == 0 +} diff --git a/pkg/utils/utility_test.go b/pkg/utils/utility_test.go new file mode 100644 index 00000000000..c7976aa4f5d --- /dev/null +++ b/pkg/utils/utility_test.go @@ -0,0 +1,34 @@ +package utils_test + +import ( + "github.com/transcom/mymove/pkg/testingsuite" + "github.com/transcom/mymove/pkg/utils" +) + +type UtilitySuite struct { + *testingsuite.PopTestSuite +} + +func (suite *UtilitySuite) TestStringIsNilEmptyOrWhitespace() { + suite.Run("nil string", func() { + actual := utils.IsNullOrWhiteSpace(nil) + suite.True(actual) + }) + + suite.Run("empty string", func() { + testString := "" + actual := utils.IsNullOrWhiteSpace(&testString) + suite.True(actual) + }) + + suite.Run("whitespace string", func() { + testString := " " + actual := utils.IsNullOrWhiteSpace(&testString) + suite.True(actual) + }) + suite.Run("valid string", func() { + testString := "hello" + actual := utils.IsNullOrWhiteSpace(&testString) + suite.False(actual) + }) +} diff --git a/playwright/tests/admin/officeUsers.spec.js b/playwright/tests/admin/officeUsers.spec.js index 224e6de92ac..cbf1aa7fbab 100644 --- a/playwright/tests/admin/officeUsers.spec.js +++ b/playwright/tests/admin/officeUsers.spec.js @@ -32,7 +32,15 @@ test.describe('Office Users List Page', () => { expect(page.url()).toContain('/system/office-users'); await expect(page.locator('header')).toContainText('Office Users'); - const columnLabels = ['Id', 'Email', 'First name', 'Last name', 'Transportation Office', 'User Id', 'Active']; + const columnLabels = [ + 'Id', + 'Email', + 'First name', + 'Last name', + 'Primary Transportation Office', + 'User Id', + 'Active', + ]; await adminPage.expectRoleLabelsByText('columnheader', columnLabels); }); @@ -77,9 +85,10 @@ test.describe('Office User Create Page', () => { await page.getByText('Services Counselor').click(); await page.getByText('Supervisor').click(); + // The autocomplete form results in multiple matching elements, so // pick the input element - await page.getByLabel('Transportation Office').fill('JPPSO Testy McTest'); + await page.getByLabel('Transportation Office').nth(0).fill('JPPSO Testy McTest'); // the autocomplete might return multiples because of concurrent // tests running that are adding offices await page.getByRole('option', { name: 'JPPSO Testy McTest' }).first().click(); @@ -135,7 +144,7 @@ test.describe('Office Users Show Page', () => { 'Last name', 'Telephone', 'Active', - 'Transportation Office', + 'Transportation Offices', 'Created at', 'Updated at', ]; @@ -177,7 +186,20 @@ test.describe('Office Users Edit Page', () => { // The autocomplete form results in multiple matching elements, so // pick the input element - await expect(page.getByLabel('Transportation Office')).toBeEditable(); + await expect(page.getByLabel('Transportation Office').nth(0)).toBeEditable(); + + // Add a Transportation Office Assignment + await page.getByTestId('addTransportationOfficeButton').click(); + // n = 2 because of the disabled GBLOC input + await expect(page.getByLabel('Transportation Office').nth(2)).toBeEditable(); + await page.getByLabel('Transportation Office').nth(2).fill('AGFM'); + // the autocomplete might return multiples because of concurrent + // tests running that are adding offices + await page.getByRole('option', { name: 'JPPSO - North East (AGFM) - USAF' }).first().click(); + // Set as primary transportation office + await page.getByLabel('Primary Office').nth(1).click(); + await page.getByText('You cannot designate more than one primary transportation office.'); + await page.getByLabel('Primary Office').nth(1).click(); // set the user to the active status they did NOT have before const activeStatus = await page.locator('div:has(label :text-is("Active")) >> input[name="active"]').inputValue(); diff --git a/playwright/tests/my/milmove/ppms/review.spec.js b/playwright/tests/my/milmove/ppms/review.spec.js index e7efa3730a4..1be8a7d6623 100644 --- a/playwright/tests/my/milmove/ppms/review.spec.js +++ b/playwright/tests/my/milmove/ppms/review.spec.js @@ -13,8 +13,8 @@ const fullPPMShipmentFields = [ ['Expected departure', '15 Mar 2020'], ['Origin ZIP', '90210'], ['Second origin ZIP', '90211'], - ['Destination ZIP', '30813'], - ['Second destination ZIP', '30814'], + ['Delivery Address ZIP', '30813'], + ['Second Delivery Address ZIP', '30814'], ['Closeout office', 'Creech AFB'], ['Storage expected? (SIT)', 'No'], ['Estimated weight', '4,000 lbs'], diff --git a/playwright/tests/my/mymove/hhg.spec.js b/playwright/tests/my/mymove/hhg.spec.js index bbd3811ad3a..e663aa5f0af 100644 --- a/playwright/tests/my/mymove/hhg.spec.js +++ b/playwright/tests/my/mymove/hhg.spec.js @@ -42,7 +42,7 @@ test.describe('HHG', () => { await customerPage.waitForPage.hhgShipment(); // Update form (adding pickup and delivery address) - const pickupAddress = await page.getByRole('group', { name: 'Pickup location' }); + const pickupAddress = await page.getByRole('group', { name: 'Pickup Address' }); await pickupAddress.getByLabel('Address 1').fill('7 Q St'); await pickupAddress.getByLabel('Address 2').clear(); await pickupAddress.getByLabel('City').fill('Atco'); @@ -56,7 +56,7 @@ test.describe('HHG', () => { await pickupAddress.getByLabel('State').nth(1).selectOption({ label: 'NJ' }); await pickupAddress.getByLabel('ZIP').nth(1).fill('08004'); - const deliveryAddress = await page.getByRole('group', { name: 'Delivery location' }); + const deliveryAddress = await page.getByRole('group', { name: 'Delivery Address' }); await deliveryAddress.getByText('Yes').nth(0).click(); await deliveryAddress.getByLabel('Address 1').nth(0).fill('9 W 2nd Ave'); await deliveryAddress.getByLabel('Address 2').nth(0).fill('P.O. Box 456'); @@ -147,7 +147,7 @@ test.describe('(MultiMove) HHG', () => { await customerPage.waitForPage.hhgShipment(); // Update form (adding pickup and delivery address) - const pickupAddress = await page.getByRole('group', { name: 'Pickup location' }); + const pickupAddress = await page.getByRole('group', { name: 'Pickup Address' }); await pickupAddress.getByLabel('Address 1').fill('7 Q St'); await pickupAddress.getByLabel('Address 2').clear(); await pickupAddress.getByLabel('City').fill('Atco'); @@ -161,7 +161,7 @@ test.describe('(MultiMove) HHG', () => { await pickupAddress.getByLabel('State').nth(1).selectOption({ label: 'NJ' }); await pickupAddress.getByLabel('ZIP').nth(1).fill('08004'); - const deliveryAddress = await page.getByRole('group', { name: 'Delivery location' }); + const deliveryAddress = await page.getByRole('group', { name: 'Delivery Address' }); await deliveryAddress.getByText('Yes').nth(0).click(); await deliveryAddress.getByLabel('Address 1').nth(0).fill('9 W 2nd Ave'); await deliveryAddress.getByLabel('Address 2').nth(0).fill('P.O. Box 456'); diff --git a/playwright/tests/my/mymove/mobileHomes.spec.js b/playwright/tests/my/mymove/mobileHomes.spec.js new file mode 100644 index 00000000000..e87642c224d --- /dev/null +++ b/playwright/tests/my/mymove/mobileHomes.spec.js @@ -0,0 +1,141 @@ +import { test, expect } from '../../utils/my/customerTest'; + +const multiMoveEnabled = process.env.FEATURE_FLAG_MULTI_MOVE; + +test.describe('Mobile Home shipment', () => { + test.skip(multiMoveEnabled === 'true', 'Skip if MultiMove workflow is enabled.'); + + test('A customer can create a Mobile Home shipment', async ({ page, customerPage }) => { + // Generate a new onboarded user with orders and log in + const move = await customerPage.testHarness.buildMoveWithOrders(); + const userId = move.Orders.ServiceMember.user_id; + await customerPage.signInAsExistingCustomer(userId); + + // Navigate to create a new shipment + await customerPage.waitForPage.home(); + await page.getByTestId('shipment-selection-btn').click(); + await customerPage.waitForPage.aboutShipments(); + await customerPage.navigateForward(); + await customerPage.waitForPage.selectShipmentType(); + + // Create an Mobile Home shipment + await page.getByText('Move a Mobile Home').click(); + await customerPage.navigateForward(); + + // Fill in form to create Mobile Home shipment + await customerPage.waitForPage.mobileHomeShipment(); + await page.getByLabel('Year').fill('2022'); + await page.getByLabel('Make').fill('make'); + await page.getByLabel('Model').fill('model'); + await page.getByTestId('lengthFeet').fill('22'); + await page.getByTestId('widthFeet').fill('22'); + await page.getByTestId('heightFeet').fill('22'); + await page.getByRole('button', { name: 'Continue' }).click(); + + await expect(page.getByTestId('tag')).toContainText('Mobile Home'); + + await expect(page.getByText('Pickup info')).toBeVisible(); + await page.getByLabel('Preferred pickup date').fill('25 Dec 2022'); + await page.getByLabel('Preferred pickup date').blur(); + await page.getByText('Use my current address').click(); + await page.getByLabel('Preferred delivery date').fill('25 Dec 2022'); + await page.getByLabel('Preferred delivery date').blur(); + await page.getByRole('button', { name: 'Save & Continue' }).click(); + await customerPage.waitForPage.reviewShipments(); + }); +}); + +test.describe('(MultiMove) Mobile Home shipment', () => { + test.skip(multiMoveEnabled === 'false', 'Skip if MultiMove workflow is not enabled.'); + + test('A customer can create a Mobile Home shipment', async ({ page, customerPage }) => { + // Generate a new onboarded user with orders and log in + const move = await customerPage.testHarness.buildMoveWithOrders(); + const userId = move.Orders.ServiceMember.user_id; + await customerPage.signInAsExistingCustomer(userId); + + // Navigate from MM Dashboard to Move + await customerPage.navigateFromMMDashboardToMove(move); + + // Navigate to create a new shipment + await customerPage.waitForPage.home(); + await page.getByTestId('shipment-selection-btn').click(); + await customerPage.waitForPage.aboutShipments(); + await customerPage.navigateForward(); + await customerPage.waitForPage.selectShipmentType(); + + // Create an Mobile Home shipment + await page.getByText('Move a mobile home').click(); + await customerPage.navigateForward(); + + // Fill in form to create Mobile Home shipment + await customerPage.waitForPage.mobileHomeShipment(); + await page.getByLabel('Year').fill('2022'); + await page.getByLabel('Make').fill('make'); + await page.getByLabel('Model').fill('model'); + await page.getByTestId('lengthFeet').fill('22'); + await page.getByTestId('widthFeet').fill('22'); + await page.getByTestId('heightFeet').fill('22'); + await page.getByRole('button', { name: 'Continue' }).click(); + + await expect(page.getByTestId('tag')).toContainText('Mobile Home'); + + await expect(page.getByText('Pickup info')).toBeVisible(); + await page.getByLabel('Preferred pickup date').fill('25 Dec 2022'); + await page.getByLabel('Preferred pickup date').blur(); + await page.getByText('Use my current address').click(); + await page.getByLabel('Preferred delivery date').fill('25 Dec 2022'); + await page.getByLabel('Preferred delivery date').blur(); + await page.getByRole('button', { name: 'Save & Continue' }).click(); + await customerPage.waitForPage.reviewShipments(); + }); + + test('Is able to delete a Mobile Home shipment', async ({ page, customerPage }) => { + // Generate a new onboarded user with orders and log in + const move = await customerPage.testHarness.buildMoveWithOrders(); + const userId = move.Orders.ServiceMember.user_id; + await customerPage.signInAsExistingCustomer(userId); + + // Navigate from MM Dashboard to Move + await customerPage.navigateFromMMDashboardToMove(move); + + // Navigate to create a new shipment + await customerPage.waitForPage.home(); + await page.getByTestId('shipment-selection-btn').click(); + await customerPage.waitForPage.aboutShipments(); + await customerPage.navigateForward(); + await customerPage.waitForPage.selectShipmentType(); + + // Create an Mobile Home shipment + await page.getByText('Move a mobile home').click(); + await customerPage.navigateForward(); + + // Fill in form to create Mobile Home shipment + await customerPage.waitForPage.mobileHomeShipment(); + await page.getByLabel('Year').fill('2022'); + await page.getByLabel('Make').fill('make'); + await page.getByLabel('Model').fill('model'); + await page.getByTestId('lengthFeet').fill('22'); + await page.getByTestId('widthFeet').fill('22'); + await page.getByTestId('heightFeet').fill('22'); + await page.getByRole('button', { name: 'Continue' }).click(); + + await expect(page.getByTestId('tag')).toContainText('Mobile Home'); + + await expect(page.getByText('Pickup info')).toBeVisible(); + await page.getByLabel('Preferred pickup date').fill('25 Dec 2022'); + await page.getByLabel('Preferred pickup date').blur(); + await page.getByText('Use my current address').click(); + await page.getByLabel('Preferred delivery date').fill('25 Dec 2022'); + await page.getByLabel('Preferred delivery date').blur(); + await page.getByRole('button', { name: 'Save & Continue' }).click(); + await customerPage.waitForPage.reviewShipments(); + + await expect(page.getByRole('heading', { name: 'Mobile Home 1' })).toBeVisible(); + await page.getByTestId('deleteShipmentButton').click(); + await expect(page.getByRole('heading', { name: 'Delete this?' })).toBeVisible(); + await page.getByText('Yes, Delete').click(); + + await expect(page.getByRole('heading', { name: 'Mobile Home 1' })).not.toBeVisible(); + }); +}); diff --git a/playwright/tests/office/documentViewer.spec.js b/playwright/tests/office/documentViewer.spec.js index 29618b0cb94..51550c9356e 100644 --- a/playwright/tests/office/documentViewer.spec.js +++ b/playwright/tests/office/documentViewer.spec.js @@ -16,4 +16,68 @@ test.describe('The document viewer', () => { await expect(page.locator('#main').getByRole('button', { name: 'Sign in' })).toBeVisible(); }); }); + + test.describe('When logged in', () => { + test('displays a PDF file correctly', async ({ page, officePage }) => { + test.slow(); // flaky no flaky + + // Build a move that has a PDF document + const move = await officePage.testHarness.buildHHGWithAmendedOrders(); + + // Sign in as a TOO user (Any office user works) + await officePage.signInAsNewTOOUser(); + + // Navigate to the move + await officePage.tooNavigateToMove(move.locator); + await officePage.waitForLoading(); + + // Navigate to the document viewer + await page.getByTestId('edit-orders').click(); + await officePage.waitForLoading(); + + // Verify that the document viewer content is displayed + await expect(page.getByTestId('DocViewerContent')).toBeVisible(); // This can load but the PDF still fail + + // Wait for the PDF canvas to load <- This is the meat and potatoes + const pdfCanvas = page.locator('.pdf-canvas canvas').first(); + await pdfCanvas.waitFor({ state: 'visible', timeout: 10000 }); + + // Verify that the canvas box has dimensions + const canvasBox = await pdfCanvas.boundingBox(); + expect(canvasBox.width).toBeGreaterThan(0); + expect(canvasBox.height).toBeGreaterThan(0); + + // Test zoom functionality + + // Verify that the zoom in and zoom out buttons are visible + await expect(page.getByRole('button', { name: 'Zoom in' })).toBeVisible(); + await expect(page.getByRole('button', { name: 'Zoom out' })).toBeVisible(); + + // Capture the current canvas dimensions, we compare this later for zoom + const initialWidth = canvasBox.width; + const initialHeight = canvasBox.height; + + // Make it zoom + await page.getByRole('button', { name: 'Zoom in' }).click(); + + // Check that it zoomed in + const zoomedCanvasBox = await pdfCanvas.boundingBox(); + expect(zoomedCanvasBox.width).toBeGreaterThan(initialWidth); + expect(zoomedCanvasBox.height).toBeGreaterThan(initialHeight); + + // Test rotation functionality + + // Verify that rotate buttons are visible + await expect(page.getByRole('button', { name: 'Rotate left' })).toBeVisible(); + await expect(page.getByRole('button', { name: 'Rotate right' })).toBeVisible(); + + // Make it rotate + await page.getByRole('button', { name: 'Rotate right' }).click(); + + // Testing the rotation degree change isn't possible within playwright so instead we verify nothing broke + // The canvas will unload if the rotation breaks something + const rotatedPdfCanvas = page.locator('.pdf-canvas canvas').first(); + await rotatedPdfCanvas.waitFor({ state: 'visible', timeout: 10000 }); + }); + }); }); diff --git a/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js b/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js index 3c7fefb4de5..a31189aa518 100644 --- a/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js +++ b/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js @@ -428,6 +428,8 @@ test.describe('Services counselor user', () => { await page.getByRole('button', { name: 'Confirm' }).click(); await scPage.waitForPage.moveDetails(); + await expect(page.getByText('PACKET READY FOR DOWNLOAD')).toBeVisible(); + // Navigate to the "View documents" page await expect(page.getByRole('button', { name: /View documents/i })).toBeVisible(); await page.getByRole('button', { name: 'View documents' }).click(); @@ -460,7 +462,6 @@ test.describe('Services counselor user', () => { await scPage.waitForPage.reviewWeightTicket(); // Edit Actual Move Start Date - await page.getByTestId('shipmentInfo').getByTestId('shipmentInfo-showRequestDetailsButton').click(); await page.getByTestId('actualMoveDate').getByTestId('editTextButton').click(); await page.waitForSelector('text="Edit Shipment Info"'); await page.getByRole('button', { name: 'Save' }).click(); @@ -477,7 +478,6 @@ test.describe('Services counselor user', () => { await scPage.waitForPage.reviewWeightTicket(); // Edit Starting Address - await page.getByTestId('shipmentInfo').getByTestId('shipmentInfo-showRequestDetailsButton').click(); await page.getByTestId('pickupAddress').getByTestId('editTextButton').click(); await page.waitForSelector('text="Edit Shipment Info"'); await page.getByRole('button', { name: 'Save' }).click(); @@ -494,7 +494,6 @@ test.describe('Services counselor user', () => { await scPage.waitForPage.reviewWeightTicket(); // Edit Ending Address - await page.getByTestId('shipmentInfo').getByTestId('shipmentInfo-showRequestDetailsButton').click(); await page.getByTestId('destinationAddress').getByTestId('editTextButton').click(); await page.waitForSelector('text="Edit Shipment Info"'); await page.getByRole('button', { name: 'Save' }).click(); @@ -584,7 +583,7 @@ test.describe('Services counselor user', () => { await page.getByTestId('submitForm').click(); await expect(page.getByTestId('payGrade')).toContainText('E-1'); - await expect(page.getByTestId('ShipmentContainer').getByTestId('tag')).toContainText( + await expect(page.getByTestId('ShipmentContainer').getByTestId('actualReimbursementTag')).toContainText( 'actual expense reimbursement', ); @@ -602,7 +601,6 @@ test.describe('Services counselor user', () => { await page.getByText('Review documents').click(); await expect(page.getByRole('heading', { name: 'View documents' })).toBeVisible(); - await page.getByTestId('shipmentInfo-showRequestDetailsButton').click(); expect(await page.locator('[data-testid="tag"]').count()).toBe(0); await expect(page.locator('label').getByText('Actual Expense Reimbursement')).toBeVisible(); @@ -653,7 +651,6 @@ test.describe('Services counselor user', () => { await expect(page.getByRole('heading', { name: 'View documents' })).toBeVisible(); await expect(page.getByTestId('tag')).toContainText('actual expense reimbursement'); - await page.getByTestId('shipmentInfo-showRequestDetailsButton').click(); await expect(page.locator('label').getByText('Actual Expense Reimbursement')).toBeVisible(); expect(await page.getByTestId('isActualExpenseReimbursement').getByTestId('editTextButton').isDisabled()).toBe( true, diff --git a/playwright/tests/office/servicescounseling/servicesCounselingWeightTickets.spec.js b/playwright/tests/office/servicescounseling/servicesCounselingWeightTickets.spec.js index 52b9c8cb710..7bd3389f69d 100644 --- a/playwright/tests/office/servicescounseling/servicesCounselingWeightTickets.spec.js +++ b/playwright/tests/office/servicescounseling/servicesCounselingWeightTickets.spec.js @@ -50,13 +50,6 @@ test('A services counselor can reduce PPM weights for a move with excess weight' await page.getByRole('button', { name: 'Review shipment weights' }).click(); await scPage.waitForPage.reviewShipmentWeights(); - await expect(page.getByText('Weight allowance', { exact: true })).toBeVisible(); - await expect(page.getByText('Estimated weight (total)', { exact: true })).toBeVisible(); - await expect(page.getByText('Max billable weight', { exact: true })).toBeVisible(); - await expect(page.getByText('Move weight (total)', { exact: true })).toBeVisible(); - await expect(page.getByText('Weight moved by customer', { exact: true })).toBeVisible(); - await expect(page.getByText('Weight moved', { exact: true })).toBeVisible(); - // verify that the excess weight alert is visible, since the move has excess weight await expect( page.getByText('This move has excess weight. Review PPM weight ticket documents to resolve.'), @@ -69,6 +62,7 @@ test('A services counselor can reduce PPM weights for a move with excess weight' await page.getByTestId('fullWeight').clear(); await page.getByTestId('fullWeight').fill('8000'); await page.getByTestId('fullWeight').blur(); + await page.getByText('Accept').click(); await page.getByRole('button', { name: 'Continue' }).click(); await page.getByTestId('closeSidebar').click(); @@ -82,6 +76,33 @@ test('A services counselor can reduce PPM weights for a move with excess weight' ).toHaveCount(0); }); +test('A services counselor can edit allowable weight', async ({ page, scPage }) => { + const move = await scPage.testHarness.buildApprovedMoveWithPPMShipmentAndExcessWeight(); + await scPage.navigateToCloseoutMove(move.locator); + + await page.getByRole('button', { name: 'Review shipment weights' }).click(); + await scPage.waitForPage.reviewShipmentWeights(); + + await page.getByRole('link', { name: 'Review Documents' }).click(); + await scPage.waitForPage.reviewWeightTicket(); + + await page.getByTestId('editAllowableWeightButton').click(); + await page.getByText('Cancel').click(); + + await page.getByTestId('editAllowableWeightButton').click(); + await page.getByTestId('editAllowableWeightInput').focus(); + await page.getByTestId('editAllowableWeightInput').fill('8000'); + await page.getByTestId('editAllowableWeightInput').blur(); + await page.getByText('Save').click(); + await expect(page.getByText('8,000 lbs')).toBeVisible(); + + await page.getByTestId('closeSidebar').click(); + + // Ensure change appears in audit history + await page.getByText('Move History').click(); + await expect(page.getByText('Allowable Weight: 8,000 lbs')).toBeVisible(); +}); + test('A service counselor can see HHG weights when reviewing weight tickets', async ({ page, scPage }) => { // Create a move with TestHarness, and then navigate to the move details page for it const move = await scPage.testHarness.buildApprovedMoveWithPPMWeightTicketOfficeWithHHG(); diff --git a/playwright/tests/office/txo/tioFlows.spec.js b/playwright/tests/office/txo/tioFlows.spec.js index 53d6bdd03df..ea4b8791bc5 100644 --- a/playwright/tests/office/txo/tioFlows.spec.js +++ b/playwright/tests/office/txo/tioFlows.spec.js @@ -19,13 +19,16 @@ class TioFlowPage extends OfficePage { /** * @param {OfficePage} officePage * @param {Object} move + * @param {Boolean} usePaymentRequest * @override */ - constructor(officePage, move) { + constructor(officePage, move, usePaymentRequest) { super(officePage.page, officePage.request); this.move = move; this.moveLocator = move.locator; - this.paymentRequest = this.findPaymentRequestBySequenceNumber(1); + if (usePaymentRequest !== false) { + this.paymentRequest = this.findPaymentRequestBySequenceNumber(1); + } } /** @@ -182,7 +185,7 @@ test.describe('TIO user', () => { testMove = await officePage.testHarness.buildHHGMoveWithServiceItemsandPaymentRequestsForTIO(); await officePage.signInAsNewTIOUser(); - tioFlowPage = new TioFlowPage(officePage, testMove); + tioFlowPage = new TioFlowPage(officePage, testMove, true); const searchTab = officePage.page.getByTitle(TIOTabsTitles[1]); await searchTab.click(); @@ -330,7 +333,7 @@ test.describe('TIO user', () => { const move = await officePage.testHarness.buildHHGMoveWithServiceItemsandPaymentRequestsForTIO(); await officePage.signInAsNewTIOUser(); - tioFlowPage = new TioFlowPage(officePage, move); + tioFlowPage = new TioFlowPage(officePage, move, true); await tioFlowPage.waitForLoading(); await officePage.tioNavigateToMove(tioFlowPage.moveLocator); await officePage.page.getByRole('heading', { name: 'Payment Requests', exact: true }).waitFor(); @@ -646,7 +649,7 @@ test.describe('TIO user', () => { const move = await officePage.testHarness.buildNTSRMoveWithPaymentRequest(); await officePage.signInAsNewTIOUser(); - tioFlowPage = new TioFlowPage(officePage, move); + tioFlowPage = new TioFlowPage(officePage, move, true); await tioFlowPage.waitForLoading(); await officePage.tioNavigateToMove(tioFlowPage.moveLocator); await officePage.page.getByRole('heading', { name: 'Payment Requests', exact: true }).waitFor(); @@ -778,7 +781,7 @@ test.describe('TIO user', () => { const move = await officePage.testHarness.buildNTSRMoveWithServiceItemsAndPaymentRequest(); await officePage.signInAsNewTIOUser(); - tioFlowPage = new TioFlowPage(officePage, move); + tioFlowPage = new TioFlowPage(officePage, move, true); await tioFlowPage.waitForLoading(); await officePage.tioNavigateToMove(tioFlowPage.moveLocator); await officePage.page.getByRole('heading', { name: 'Payment Requests', exact: true }).waitFor(); @@ -864,4 +867,45 @@ test.describe('TIO user', () => { await expect(page.getByRole('heading', { name: 'Payment requests' })).toBeVisible(); }); }); + + test.describe('with PPM moves with weight tickets and documents', () => { + test.beforeEach(async ({ officePage }) => { + testMove = await officePage.testHarness.buildApprovedMoveWithPPMMovingExpenseOffice(); + await officePage.signInAsNewTIOUser(); + tioFlowPage = new TioFlowPage(officePage, testMove, false); + const searchTab = officePage.page.getByTitle(TIOTabsTitles[1]); + await searchTab.click(); + }); + + test('can view PPM review documents', async ({ page }) => { + const locator = `PPM ${testMove.locator}`; + const selectedRadio = page.getByRole('group').locator(`label:text("${SearchRBSelection[0]}")`); + await selectedRadio.click(); + await page.getByTestId('searchText').fill(testMove.locator); + await page.getByTestId('searchTextSubmit').click(); + + await expect(page.getByText('Results')).toBeVisible(); + await expect(page.getByTestId('locator-0')).toContainText(testMove.locator); + await page.getByTestId('locator-0').click(); + await page.getByRole('button', { name: 'Review shipment weights' }).click(); + await page.getByRole('button', { name: 'Review shipment weights' }).click(); + await expect(page.getByText(locator)).toBeVisible(); + await expect(page.getByText('Shipment Info')).toBeVisible(); + + await expect(page.getByText('Planned Move Start Date')).toBeVisible(); + await expect(page.getByText('Actual Move Start Date')).toBeVisible(); + await expect(page.getByText('Starting Address')).toBeVisible(); + await expect(page.getByText('Ending Address')).toBeVisible(); + await expect(page.getByText('Miles')).toBeVisible(); + await expect(page.getByText('Estimated Net Weight')).toBeVisible(); + await expect(page.getByText('Actual Net Weight')).toBeVisible(); + await expect(page.getByText('Allowable Weight')).toBeVisible(); + await expect(page.getByText('SENT TO CUSTOMER')).toBeVisible(); + await expect(page.getByText('TRIP 1')).toBeVisible(); + await expect(page.getByText('RECEIPT 1')).toBeVisible(); + await expect(page.getByText('RECEIPT 2')).toBeVisible(); + + await page.getByRole('button', { name: 'Done' }).click(); + }); + }); }); diff --git a/public/static/react-file-viewer/.gitkeep b/public/static/react-file-viewer/.gitkeep new file mode 100644 index 00000000000..e69de29bb2d diff --git a/scripts/README.md b/scripts/README.md index 708f1c88ea2..46aa61a74c1 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -141,6 +141,9 @@ This subset of development scripts is used primarily for building the app. | Script Name | Description | | ----------------- | ----------------------------------------------------------------- | | `copy-swagger-ui` | Copies the assets (other than xxx.html) into the public directory | +| `copy-react-file-viewer` | Copies react-file-viewer assets into the public directory to support dynamic importing of the pdfjs-dist library | +| `rebuild-dependencies-without-binaries` | Creates binaries for installed dependencies that don't come with one | +| `fetch-react-file-viewer-from-yarn` | Fetches react file viewer version from yarn.lock, extracts the /dist/ folder, and stores the output into public/static/react-file-viewer in order to have the ESM chunk be served to the client during runtime | | `gen-server` | generate swagger code from yaml files | | `openapi` | invokes the openapi redoc swagger tool | diff --git a/scripts/copy-react-file-viewer b/scripts/copy-react-file-viewer new file mode 100755 index 00000000000..db2d56aec00 --- /dev/null +++ b/scripts/copy-react-file-viewer @@ -0,0 +1,12 @@ +#! /usr/bin/env bash + +# Copies the react file viewer webpack chunks to a public static directory to enable serving +# Without this, the client will attempt to HTTP GET the chunk file, but if it cannot be found (Because it isn't served) +# it will default to MilMove's index.html +# This doesn't serve it, but it enables the backend to serve the static chunk file +# README!: CI/CD does not use this script. +# If you are wondering the diff between this and CI/CD, it's this: +# This script works at any point in the configuration. It enables the public folder to hold the client dependency properly +# CI/CD needs a separate script "`fetch-react-file-viewer-from-yarn" because CI/CD must have its deps configured DURING compile +# This script only works AFTER compile. This is a dev friendly script +cp node_modules/@transcom/react-file-viewer/dist/*.js public/static/react-file-viewer diff --git a/scripts/db-truncate b/scripts/db-truncate index 6844ae0a77a..e49be4f92d3 100755 --- a/scripts/db-truncate +++ b/scripts/db-truncate @@ -9,7 +9,7 @@ DO \$\$ DECLARE r RECORD; BEGIN FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = current_schema() - AND tablename NOT IN ('us_post_region_cities', 're_countries', 're_states', 're_cities', 're_us_post_regions', 're_oconus_rate_areas', 're_rate_areas', 're_intl_transit_times','re_services','re_service_items')) LOOP + AND tablename NOT IN ('us_post_region_cities', 're_countries', 're_states', 're_cities', 're_us_post_regions', 're_oconus_rate_areas', 're_rate_areas', 're_intl_transit_times','re_services','re_service_items', 'ub_allowances', 'ports', 're_fsc_multipliers')) LOOP EXECUTE 'TRUNCATE TABLE ' || quote_ident(r.tablename) || ' CASCADE'; END LOOP; END \$\$; diff --git a/scripts/fetch-react-file-viewer-from-yarn b/scripts/fetch-react-file-viewer-from-yarn new file mode 100755 index 00000000000..c52216463dd --- /dev/null +++ b/scripts/fetch-react-file-viewer-from-yarn @@ -0,0 +1,67 @@ +#!/bin/bash +set -e + +YARN_LOCK_PATH="yarn.lock" +TARGET_PACKAGE="@transcom/react-file-viewer" +ORIGINAL_DIR="$(pwd)" +SERVER_PUBLIC_DIR="$ORIGINAL_DIR/public/static/react-file-viewer" + +# Ensure yarn.lock exists +if [ ! -f "$YARN_LOCK_PATH" ]; then + echo "Error: $YARN_LOCK_PATH not found!" + exit 1 +fi + +# Extract the version of @transcom/react-file-viewer from yarn.lock using awk +# We use awk to parse the file instead of yarn because yarn is a client dependency +# and this script is a backend dependency. Yarn will not be available +PACKAGE_VERSION=$(awk -v package="$TARGET_PACKAGE" ' + $0 ~ "^\"" package "@" { + getline + if ($1 == "version") { + gsub(/"/, "", $2) + print $2 + exit + } + }' "$YARN_LOCK_PATH") + +if [ -z "$PACKAGE_VERSION" ]; then + echo "Error: Could not find $TARGET_PACKAGE version in $YARN_LOCK_PATH" + exit 1 +fi + +echo "Found $TARGET_PACKAGE version: $PACKAGE_VERSION" + +# Create a temporary directory to clone into +# We download the react file-viewer version outlined in yarn +# and then serve the ESM chunk +TEMP_DIR=$(mktemp -d) +echo "Created temporary directory at $TEMP_DIR" + +# Clone the repository +cd "$TEMP_DIR" +git clone https://github.com/transcom/react-file-viewer.git +cd react-file-viewer + +# Checkout the specific version +git checkout "v$PACKAGE_VERSION" 2>/dev/null || git checkout "$PACKAGE_VERSION" 2>/dev/null + +# Check if dist directory exists +if [ ! -d "dist" ]; then + echo "Error: 'dist' directory not found" + exit 1 +fi + +# Create the target directory if it doesn't exist +mkdir -p "$SERVER_PUBLIC_DIR" + +# Copy dist/*.js to the server's public directory +cp -r dist/*.js "$SERVER_PUBLIC_DIR" +echo "Copied dist/*.js to $SERVER_PUBLIC_DIR" + +# Remove temp +cd "$ORIGINAL_DIR" +rm -rf "$TEMP_DIR" +echo "Cleaned up temporary files" + +echo "Script completed successfully" diff --git a/scripts/rebuild-dependencies-without-binaries b/scripts/rebuild-dependencies-without-binaries new file mode 100755 index 00000000000..388e6d83de5 --- /dev/null +++ b/scripts/rebuild-dependencies-without-binaries @@ -0,0 +1,22 @@ +#!/bin/bash + +# Canvas does not release with pre-built binaries at this time +# Currently, they have a pre-relase that does come with it so eventually this script can be ignored +# once it is finalized. +# https://github.com/Automattic/node-canvas/releases/tag/v3.0.0-rc2 + +# Check if the canvas package is installed +if ! npm list canvas &> /dev/null; then + echo "Canvas is not installed but it is marked as a dependency meant for rebuild" + exit 1 +fi + +echo "Initiating canvas dependency rebuild..." +if npm rebuild canvas; then + echo "Successfully rebuilt canvas" +else + echo "Canvas failed to rebuild" + exit 1 +fi + +# Empty space here for if we encoutner this issue with another dependency \ No newline at end of file diff --git a/src/.eslintrc.js b/src/.eslintrc.js index 9973b986a7f..9d562ed979e 100644 --- a/src/.eslintrc.js +++ b/src/.eslintrc.js @@ -1,4 +1,5 @@ module.exports = { + ignorePatterns: ['public/static/react-file-viewer/*'], rules: { // okay to disable because the threat actor (web application user) already controls the execution environment (web browser) 'security/detect-object-injection': 'off', diff --git a/src/components/Customer/BoatShipment/BoatShipmentConfirmationModal/BoatShipmentConfirmationModal.jsx b/src/components/Customer/BoatShipment/BoatShipmentConfirmationModal/BoatShipmentConfirmationModal.jsx index fee1a4533a8..428be14583b 100644 --- a/src/components/Customer/BoatShipment/BoatShipmentConfirmationModal/BoatShipmentConfirmationModal.jsx +++ b/src/components/Customer/BoatShipment/BoatShipmentConfirmationModal/BoatShipmentConfirmationModal.jsx @@ -40,7 +40,7 @@ const boatConfirmationMessage = (isDimensionsMeetReq, boatShipmentType, isEditPa message = (

Your boat qualifies to move as its own shipment and has an accompanying trailer that can be used to tow it - to your destination, a Boat Tow-Away (BTA) shipment. Click "Continue" to proceed. + to your delivery address, a Boat Tow-Away (BTA) shipment. Click "Continue" to proceed.

); break; @@ -49,7 +49,7 @@ const boatConfirmationMessage = (isDimensionsMeetReq, boatShipmentType, isEditPa message = (

Your boat qualifies to move as its own shipment and requires additional equipment to haul it to your - destination, a Boat Haul-Away (BHA) shipment. Click "Continue" to proceed. + delivery address, a Boat Haul-Away (BHA) shipment. Click "Continue" to proceed.

); break; diff --git a/src/components/Customer/BoatShipment/BoatShipmentForm/BoatShipmentForm.jsx b/src/components/Customer/BoatShipment/BoatShipmentForm/BoatShipmentForm.jsx index 3e5b6697b40..c6a8848c41a 100644 --- a/src/components/Customer/BoatShipment/BoatShipmentForm/BoatShipmentForm.jsx +++ b/src/components/Customer/BoatShipment/BoatShipmentForm/BoatShipmentForm.jsx @@ -337,7 +337,7 @@ const BoatShipmentForm = ({ mtoShipment, onBack, onSubmit }) => { their individual dimensions -
  • Access info for your origin or destination address/marina
  • +
  • Access info for your pickup or delivery address/marina
  • diff --git a/src/components/Customer/EditOrdersForm/EditOrdersForm.jsx b/src/components/Customer/EditOrdersForm/EditOrdersForm.jsx index bcbf1a86e3f..666c1ea40fc 100644 --- a/src/components/Customer/EditOrdersForm/EditOrdersForm.jsx +++ b/src/components/Customer/EditOrdersForm/EditOrdersForm.jsx @@ -4,21 +4,26 @@ import { Formik, Field } from 'formik'; import * as Yup from 'yup'; import { Radio, FormGroup, Label, Link as USWDSLink } from '@trussworks/react-uswds'; +import { isBooleanFlagEnabled } from '../../../utils/featureFlags'; + import styles from './EditOrdersForm.module.scss'; -import { ORDERS_PAY_GRADE_OPTIONS } from 'constants/orders'; +import MaskedTextField from 'components/form/fields/MaskedTextField/MaskedTextField'; +import ToolTip from 'shared/ToolTip/ToolTip'; +import { ORDERS_PAY_GRADE_OPTIONS, ORDERS_TYPE } from 'constants/orders'; import { Form } from 'components/form/Form'; import FileUpload from 'components/FileUpload/FileUpload'; import UploadsTable from 'components/UploadsTable/UploadsTable'; +import LoadingPlaceholder from 'shared/LoadingPlaceholder'; import SectionWrapper from 'components/Customer/SectionWrapper'; -import { documentSizeLimitMsg } from 'shared/constants'; +import { FEATURE_FLAG_KEYS, documentSizeLimitMsg } from 'shared/constants'; import profileImage from 'scenes/Review/images/profile.png'; import { DropdownArrayOf } from 'types'; import { ExistingUploadsShape } from 'types/uploads'; import { DropdownInput, DatePickerInput, DutyLocationInput } from 'components/form/fields'; import WizardNavigation from 'components/Customer/WizardNavigation/WizardNavigation'; import Callout from 'components/Callout'; -import { formatLabelReportByDate, dropdownInputOptions } from 'utils/formatters'; +import { formatLabelReportByDate, dropdownInputOptions, formatYesNoAPIValue } from 'utils/formatters'; import formStyles from 'styles/form.module.scss'; import { showCounselingOffices } from 'services/internalApi'; @@ -33,7 +38,22 @@ const EditOrdersForm = ({ onCancel, }) => { const [officeOptions, setOfficeOptions] = useState(null); - const [dutyLocation, setDutyLocation] = useState(initialValues.origin_duty_location); + const [currentDutyLocation, setDutyLocation] = useState(initialValues.origin_duty_location); + const [newDutyLocation, setNewDutyLocation] = useState(initialValues.new_duty_location); + const [showAccompaniedTourField, setShowAccompaniedTourField] = useState(false); + const [showDependentAgeFields, setShowDependentAgeFields] = useState(false); + const [hasDependents, setHasDependents] = useState(formatYesNoAPIValue(initialValues.has_dependents)); + const [isOconusMove, setIsOconusMove] = useState(false); + const [enableUB, setEnableUB] = useState(false); + const [isLoading, setIsLoading] = useState(true); + const [finishedFetchingFF, setFinishedFetchingFF] = useState(false); + + const isInitialHasDependentsDisabled = + initialValues.orders_type === ORDERS_TYPE.STUDENT_TRAVEL || + initialValues.orders_type === ORDERS_TYPE.EARLY_RETURN_OF_DEPENDENTS; + const [isHasDependentsDisabled, setHasDependentsDisabled] = useState(isInitialHasDependentsDisabled); + const [prevOrderType, setPrevOrderType] = useState(initialValues.orders_type); + const validationSchema = Yup.object().shape({ orders_type: Yup.mixed() .oneOf(ordersTypeOptions.map((i) => i.key)) @@ -59,9 +79,18 @@ const EditOrdersForm = ({ .min(1), grade: Yup.mixed().oneOf(Object.keys(ORDERS_PAY_GRADE_OPTIONS)).required('Required'), origin_duty_location: Yup.object().nullable().required('Required'), - counseling_office_id: dutyLocation?.provides_services_counseling + counseling_office_id: currentDutyLocation?.provides_services_counseling ? Yup.string().required('Required') : Yup.string().notRequired(), + accompanied_tour: showAccompaniedTourField + ? Yup.mixed().oneOf(['yes', 'no']).required('Required') + : Yup.string().notRequired(), + dependents_under_twelve: showDependentAgeFields + ? Yup.number().min(0).required('Required') + : Yup.number().notRequired(), + dependents_twelve_and_over: showDependentAgeFields + ? Yup.number().min(0).required('Required') + : Yup.number().notRequired(), }); const enableDelete = () => { @@ -75,7 +104,18 @@ const EditOrdersForm = ({ let newDutyMeta = ''; useEffect(() => { - showCounselingOffices(dutyLocation?.id).then((fetchedData) => { + // Only check the FF on load + const checkUBFeatureFlag = async () => { + const enabled = await isBooleanFlagEnabled(FEATURE_FLAG_KEYS.UNACCOMPANIED_BAGGAGE); + if (enabled) { + setEnableUB(true); + } + setFinishedFetchingFF(true); + }; + checkUBFeatureFlag(); + }, []); + useEffect(() => { + showCounselingOffices(currentDutyLocation?.id).then((fetchedData) => { if (fetchedData.body) { const counselingOffices = fetchedData.body.map((item) => ({ key: item.id, @@ -84,25 +124,108 @@ const EditOrdersForm = ({ setOfficeOptions(counselingOffices); } }); - }, [dutyLocation]); + // Check if either currentDutyLocation or newDutyLocation is OCONUS + if (currentDutyLocation?.address?.isOconus || newDutyLocation?.address?.isOconus) { + setIsOconusMove(true); + } else { + setIsOconusMove(false); + } + if (currentDutyLocation?.address && newDutyLocation?.address && enableUB) { + // Only if one of the duty locations is OCONUS should accompanied tour and dependent + // age fields display + if (isOconusMove && hasDependents) { + setShowAccompaniedTourField(true); + setShowDependentAgeFields(true); + } else { + setShowAccompaniedTourField(false); + setShowDependentAgeFields(false); + } + } + if (isLoading && finishedFetchingFF) { + // If the form is still loading and the FF has finished fetching, + // then the form is done loading + setIsLoading(false); + } + }, [currentDutyLocation, newDutyLocation, isOconusMove, hasDependents, enableUB, finishedFetchingFF, isLoading]); + + if (isLoading) { + return ; + } return ( - {({ isValid, isSubmitting, handleSubmit, values }) => { + {({ isValid, isSubmitting, handleSubmit, handleChange, setValues, values, setFieldValue }) => { const isRetirementOrSeparation = ['RETIREMENT', 'SEPARATION'].includes(values.orders_type); + const handleCounselingOfficeChange = () => { + setValues({ + ...values, + counseling_office_id: null, + }); + setOfficeOptions(null); + }; if (!values.origin_duty_location) originMeta = 'Required'; else originMeta = null; if (!values.new_duty_location) newDutyMeta = 'Required'; else newDutyMeta = null; + const handleHasDependentsChange = (e) => { + // Declare a duplicate local scope of the field value + // for the form to prevent state race conditions + if (e.target.value === '') { + setFieldValue('has_dependents', ''); + } else { + const fieldValueHasDependents = e.target.value === 'yes'; + setHasDependents(fieldValueHasDependents); + setFieldValue('has_dependents', fieldValueHasDependents ? 'yes' : 'no'); + if (fieldValueHasDependents && isOconusMove && enableUB) { + setShowAccompaniedTourField(true); + setShowDependentAgeFields(true); + } else { + setShowAccompaniedTourField(false); + setShowDependentAgeFields(false); + } + } + }; + + const handleOrderTypeChange = (e) => { + const { value } = e.target; + if (value === ORDERS_TYPE.STUDENT_TRAVEL || value === ORDERS_TYPE.EARLY_RETURN_OF_DEPENDENTS) { + setHasDependentsDisabled(true); + handleHasDependentsChange({ target: { value: 'yes' } }); + } else { + setHasDependentsDisabled(false); + if ( + prevOrderType === ORDERS_TYPE.STUDENT_TRAVEL || + prevOrderType === ORDERS_TYPE.EARLY_RETURN_OF_DEPENDENTS + ) { + handleHasDependentsChange({ target: { value: '' } }); + } + } + setPrevOrderType(value); + }; + return (
    @@ -124,6 +247,10 @@ const EditOrdersForm = ({ options={ordersTypeOptions} required hint="Required" + onChange={(e) => { + handleChange(e); + handleOrderTypeChange(e); + }} /> - - -
    - - -
    -
    - { setDutyLocation(e); + handleCounselingOfficeChange(); }} required metaOverride={originMeta} /> - {dutyLocation?.provides_services_counseling && ( + {currentDutyLocation?.provides_services_counseling && (
    { + setNewDutyLocation(e); + }} /> ) : ( @@ -222,8 +329,124 @@ const EditOrdersForm = ({ label="New duty location" displayAddress={false} metaOverride={newDutyMeta} + onDutyLocationChange={(e) => { + setNewDutyLocation(e); + }} /> )} + + + +
    + { + handleHasDependentsChange(e); + }} + disabled={isHasDependentsDisabled} + /> + { + handleHasDependentsChange(e); + }} + disabled={isHasDependentsDisabled} + /> +
    +
    + + {showAccompaniedTourField && ( + + +
    +
    + + +
    +
    + + +
    +
    +
    + )} + + {showDependentAgeFields && ( + + + + + + )} + ({ ...jest.requireActual('services/internalApi'), @@ -52,6 +56,7 @@ jest.mock('components/LocationSearchBox/api', () => ({ id: '93f0755f-6f35-478b-9a75-35a69211da1c', name: 'Altus AFB', updated_at: '2021-02-11T16:48:04.117Z', + provides_services_counseling: true, }, { address: { @@ -82,6 +87,7 @@ jest.mock('components/LocationSearchBox/api', () => ({ created_at: '2021-02-11T16:48:04.117Z', id: 'a8d6b33c-8370-4e92-8df2-356b8c9d0c1a', name: 'Luke AFB', + provides_services_counseling: true, updated_at: '2021-02-11T16:48:04.117Z', }, { @@ -148,6 +154,10 @@ jest.mock('components/LocationSearchBox/api', () => ({ ), })); +jest.mock('../../../utils/featureFlags', () => ({ + isBooleanFlagEnabled: jest.fn(), +})); + const testProps = { onSubmit: jest.fn().mockImplementation(() => Promise.resolve()), initialValues: { @@ -172,6 +182,8 @@ const testProps = { { key: 'RETIREMENT', value: 'Retirement' }, { key: 'SEPARATION', value: 'Separation' }, { key: 'TEMPORARY_DUTY', value: 'Temporary Duty (TDY)' }, + { key: ORDERS_TYPE.EARLY_RETURN_OF_DEPENDENTS, value: ORDERS_TYPE_OPTIONS.EARLY_RETURN_OF_DEPENDENTS }, + { key: ORDERS_TYPE.STUDENT_TRAVEL, value: ORDERS_TYPE_OPTIONS.STUDENT_TRAVEL }, ], currentDutyLocation: {}, grade: '', @@ -181,9 +193,8 @@ const initialValues = { orders_type: ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION, issue_date: '2020-11-08', report_by_date: '2020-11-26', - has_dependents: 'No', + has_dependents: 'no', origin_duty_location: { - provides_services_counseling: true, address: { city: 'Des Moines', country: 'US', @@ -200,7 +211,9 @@ const initialValues = { id: 'f9299768-16d2-4a13-ae39-7087a58b1f62', name: 'Yuma AFB', updated_at: '2020-10-19T17:01:16.114Z', + provides_services_counseling: true, }, + counseling_office_id: '3e937c1f-5539-4919-954d-017989130584', new_duty_location: { address: { city: 'Des Moines', @@ -230,8 +243,16 @@ const initialValues = { }, ], grade: 'E_1', + accompanied_tour: '', + dependents_under_twelve: '', + dependents_twelve_and_over: '', }; +jest.mock('utils/featureFlags', () => ({ + ...jest.requireActual('utils/featureFlags'), + isBooleanFlagEnabled: jest.fn().mockImplementation(() => Promise.resolve(false)), +})); + describe('EditOrdersForm component', () => { describe('renders each input and checks if the field is required', () => { it.each([ @@ -268,7 +289,11 @@ describe('EditOrdersForm component', () => { ['RETIREMENT', 'RETIREMENT'], ['SEPARATION', 'SEPARATION'], ['TEMPORARY_DUTY', 'TEMPORARY_DUTY'], + [ORDERS_TYPE.EARLY_RETURN_OF_DEPENDENTS, ORDERS_TYPE.EARLY_RETURN_OF_DEPENDENTS], + [ORDERS_TYPE.STUDENT_TRAVEL, ORDERS_TYPE.STUDENT_TRAVEL], ])('rendering the %s option', async (selectionOption, expectedValue) => { + isBooleanFlagEnabled.mockImplementation(() => Promise.resolve(true)); + render(); const ordersTypeDropdown = await screen.findByLabelText(/Orders type/); @@ -282,15 +307,23 @@ describe('EditOrdersForm component', () => { }); it('allows new and current duty location to be the same', async () => { - // Not testing the upload interaction, so give uploaded orders to the props. + // Render the component render( { />, ); + await waitFor(() => expect(screen.queryByText('Loading, please wait...')).not.toBeInTheDocument()); + const submitButton = screen.getByRole('button', { name: 'Save' }); await waitFor(() => { expect(submitButton).not.toBeDisabled(); }); - await userEvent.selectOptions(screen.getByLabelText(/Orders type/), ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION); await userEvent.type(screen.getByLabelText(/Orders date/), '08 Nov 2020'); await userEvent.type(screen.getByLabelText(/Report by date/), '26 Nov 2020'); await userEvent.click(screen.getByLabelText('No')); await userEvent.selectOptions(screen.getByLabelText(/Pay grade/), ['E_5']); - - // Test Current Duty Location Search Box interaction - await userEvent.type(screen.getByLabelText(/Current duty location/), 'AFB', { delay: 100 }); - const selectedOptionCurrent = await screen.findByText(/Altus/); - await userEvent.click(selectedOptionCurrent); - - // Test New Duty Location Search Box interaction - await userEvent.type(screen.getByLabelText(/New duty location/), 'AFB', { delay: 100 }); - const selectedOptionNew = await screen.findByText(/Luke/); - await userEvent.click(selectedOptionNew); + await userEvent.click(screen.getByTestId('hasDependentsYes')); await waitFor(() => { expect(screen.getByRole('form')).toHaveFormValues({ new_duty_location: 'Luke AFB', - origin_duty_location: 'Altus AFB', + origin_duty_location: 'Luke AFB', }); }); - - expect(submitButton).not.toHaveAttribute('disabled'); }); it('shows an error message if the form is invalid', async () => { render(); - const submitButton = screen.getByRole('button', { name: 'Save' }); - - await waitFor(() => { - expect(submitButton).toBeEnabled(); - }); + const submitButton = await screen.findByRole('button', { name: 'Save' }); const ordersTypeDropdown = screen.getByLabelText(/Orders type/); await userEvent.selectOptions(ordersTypeDropdown, ''); @@ -363,8 +382,11 @@ describe('EditOrdersForm component', () => { {...testProps} initialValues={{ origin_duty_location: { + name: 'Altus AFB', provides_services_counseling: true, + address: { isOconus: false }, }, + counseling_office_id: '3e937c1f-5539-4919-954d-017989130584', uploaded_orders: [ { id: '123', @@ -379,22 +401,21 @@ describe('EditOrdersForm component', () => { />, ); + await waitFor(() => expect(screen.queryByText('Loading, please wait...')).not.toBeInTheDocument()); + await userEvent.selectOptions(screen.getByLabelText(/Orders type/), ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION); await userEvent.type(screen.getByLabelText(/Orders date/), '08 Nov 2020'); await userEvent.type(screen.getByLabelText(/Report by date/), '26 Nov 2020'); await userEvent.click(screen.getByLabelText('No')); await userEvent.selectOptions(screen.getByLabelText(/Pay grade/), ['E_5']); - // Test Current Duty Location Search Box interaction - await userEvent.type(screen.getByLabelText(/Current duty location/), 'AFB', { delay: 100 }); - const selectedOptionCurrent = await screen.findByText(/Altus/); - await userEvent.click(selectedOptionCurrent); - // Test New Duty Location Search Box interaction await userEvent.type(screen.getByLabelText(/New duty location/), 'AFB', { delay: 100 }); const selectedOptionNew = await screen.findByText(/Luke/); await userEvent.click(selectedOptionNew); + expect(screen.getByLabelText(/Counseling office/)); + await waitFor(() => expect(screen.getByRole('form')).toHaveFormValues({ new_duty_location: 'Luke AFB', @@ -406,39 +427,51 @@ describe('EditOrdersForm component', () => { expect(submitBtn).not.toBeDisabled(); await userEvent.click(submitBtn); - await waitFor(() => { - expect(testProps.onSubmit).toHaveBeenCalledWith( - expect.objectContaining({ - orders_type: ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION, - has_dependents: 'no', - issue_date: '08 Nov 2020', - report_by_date: '26 Nov 2020', - new_duty_location: { - address: { - city: 'Glendale Luke AFB', - country: 'United States', - id: 'fa51dab0-4553-4732-b843-1f33407f77bc', - postalCode: '85309', - state: 'AZ', - streetAddress1: 'n/a', - }, - address_id: '25be4d12-fe93-47f1-bbec-1db386dfa67f', - affiliation: 'AIR_FORCE', - created_at: '2021-02-11T16:48:04.117Z', - id: 'a8d6b33c-8370-4e92-8df2-356b8c9d0c1a', - name: 'Luke AFB', - updated_at: '2021-02-11T16:48:04.117Z', + expect(testProps.onSubmit).toHaveBeenCalledWith( + expect.objectContaining({ + orders_type: ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION, + has_dependents: 'no', + issue_date: '08 Nov 2020', + report_by_date: '26 Nov 2020', + new_duty_location: { + address: { + city: 'Glendale Luke AFB', + country: 'United States', + id: 'fa51dab0-4553-4732-b843-1f33407f77bc', + postalCode: '85309', + state: 'AZ', + streetAddress1: 'n/a', }, - grade: 'E_5', - }), - expect.anything(), - ); - }); + address_id: '25be4d12-fe93-47f1-bbec-1db386dfa67f', + affiliation: 'AIR_FORCE', + created_at: '2021-02-11T16:48:04.117Z', + id: 'a8d6b33c-8370-4e92-8df2-356b8c9d0c1a', + name: 'Luke AFB', + updated_at: '2021-02-11T16:48:04.117Z', + provides_services_counseling: true, + }, + origin_duty_location: expect.any(Object), + grade: 'E_5', + counseling_office_id: '3e937c1f-5539-4919-954d-017989130584', + uploaded_orders: expect.arrayContaining([ + expect.objectContaining({ + id: '123', + createdAt: '2020-11-08', + bytes: 1, + url: 'url', + filename: 'Test Upload', + contentType: 'application/pdf', + }), + ]), + }), + expect.anything(), + ); }); it('implements the onCancel handler when the Cancel button is clicked', async () => { render(); - const cancelButton = screen.getByRole('button', { name: 'Cancel' }); + + const cancelButton = await screen.findByRole('button', { name: 'Cancel' }); await userEvent.click(cancelButton); @@ -586,6 +619,8 @@ describe('EditOrdersForm component', () => { { key: 'RETIREMENT', value: 'Retirement' }, { key: 'SEPARATION', value: 'Separation' }, { key: 'TEMPORARY_DUTY', value: 'Temporary Duty (TDY)' }, + { key: ORDERS_TYPE.EARLY_RETURN_OF_DEPENDENTS, value: ORDERS_TYPE_OPTIONS.EARLY_RETURN_OF_DEPENDENTS }, + { key: ORDERS_TYPE.STUDENT_TRAVEL, value: ORDERS_TYPE_OPTIONS.STUDENT_TRAVEL }, ], currentDutyLocation: {}, }; @@ -594,7 +629,7 @@ describe('EditOrdersForm component', () => { render(); - const save = screen.getByRole('button', { name: 'Save' }); + const save = await screen.findByRole('button', { name: 'Save' }); await waitFor(() => { expect(save).toBeInTheDocument(); }); @@ -610,8 +645,11 @@ describe('EditOrdersForm component', () => { {...testProps} initialValues={{ origin_duty_location: { + name: 'Altus AFB', provides_services_counseling: true, + address: { isOconus: false }, }, + counseling_office_id: '3e937c1f-5539-4919-954d-017989130584', uploaded_orders: [ { id: '123', @@ -626,19 +664,16 @@ describe('EditOrdersForm component', () => { />, ); + await waitFor(() => expect(screen.queryByText('Loading, please wait...')).not.toBeInTheDocument()); + await userEvent.selectOptions(screen.getByLabelText(/Orders type/), 'TEMPORARY_DUTY'); await userEvent.type(screen.getByLabelText(/Orders date/), '28 Oct 2024'); await userEvent.type(screen.getByLabelText(/Report by date/), '28 Oct 2024'); await userEvent.click(screen.getByLabelText('No')); await userEvent.selectOptions(screen.getByLabelText(/Pay grade/), ['E_8']); - // Test Current Duty Location Search Box interaction - await userEvent.type(screen.getByLabelText(/Current duty location/), 'AFB', { delay: 100 }); - const selectedOptionCurrent = await screen.findByText(/Altus/); - await userEvent.click(selectedOptionCurrent); - // Test New Duty Location Search Box interaction - await userEvent.type(screen.getByLabelText(/New duty location/), 'AFB', { delay: 100 }); + await userEvent.type(screen.getByLabelText(/New duty location/), 'AFB', { delay: 200 }); const selectedOptionNew = await screen.findByText(/Luke/); await userEvent.click(selectedOptionNew); @@ -657,31 +692,117 @@ describe('EditOrdersForm component', () => { expect(testProps.onSubmit).toHaveBeenCalledWith( expect.objectContaining({ orders_type: ORDERS_TYPE.TEMPORARY_DUTY, - has_dependents: 'no', - issue_date: '28 Oct 2024', - report_by_date: '28 Oct 2024', - new_duty_location: { - address: { - city: 'Glendale Luke AFB', - country: 'United States', - id: 'fa51dab0-4553-4732-b843-1f33407f77bc', - postalCode: '85309', - state: 'AZ', - streetAddress1: 'n/a', - }, - address_id: '25be4d12-fe93-47f1-bbec-1db386dfa67f', - affiliation: 'AIR_FORCE', - created_at: '2021-02-11T16:48:04.117Z', - id: 'a8d6b33c-8370-4e92-8df2-356b8c9d0c1a', - name: 'Luke AFB', - updated_at: '2021-02-11T16:48:04.117Z', - }, - grade: 'E_8', }), expect.anything(), ); }); }); + it('has dependents is yes and disabled when order type is student travel', async () => { + isBooleanFlagEnabled.mockImplementation(() => Promise.resolve(true)); + + render(); + + await waitFor(() => expect(screen.queryByText('Loading, please wait...')).not.toBeInTheDocument()); + + await userEvent.selectOptions(screen.getByLabelText(/Orders type/), ORDERS_TYPE.STUDENT_TRAVEL); + + const hasDependentsYes = screen.getByLabelText('Yes'); + const hasDependentsNo = screen.getByLabelText('No'); + + await waitFor(() => { + expect(hasDependentsYes).toBeChecked(); + expect(hasDependentsYes).toBeDisabled(); + expect(hasDependentsNo).toBeDisabled(); + }); + }); + + it('has dependents is yes and disabled when order type is early return', async () => { + render(); + + await waitFor(() => expect(screen.queryByText('Loading, please wait...')).not.toBeInTheDocument()); + + await userEvent.selectOptions(screen.getByLabelText(/Orders type/), ORDERS_TYPE.EARLY_RETURN_OF_DEPENDENTS); + + const hasDependentsYes = screen.getByLabelText('Yes'); + const hasDependentsNo = screen.getByLabelText('No'); + + await waitFor(() => { + expect(hasDependentsYes).toBeChecked(); + expect(hasDependentsYes).toBeDisabled(); + expect(hasDependentsNo).toBeDisabled(); + }); + }); + + it('has dependents becomes disabled and then re-enabled for order type student travel', async () => { + render(); + + await waitFor(() => expect(screen.queryByText('Loading, please wait...')).not.toBeInTheDocument()); + + // set order type to perm change and verify the "has dependents" state + await userEvent.selectOptions(screen.getByLabelText(/Orders type/), 'PERMANENT_CHANGE_OF_STATION'); + + const hasDependentsYesPermChg = screen.getByLabelText('Yes'); + const hasDependentsNoPermChg = screen.getByLabelText('No'); + + await waitFor(() => { + expect(hasDependentsYesPermChg).not.toBeChecked(); + expect(hasDependentsYesPermChg).toBeEnabled(); + expect(hasDependentsNoPermChg).not.toBeChecked(); + expect(hasDependentsNoPermChg).toBeEnabled(); + }); + + // set order type to value that disables and defaults "has dependents" + await userEvent.selectOptions(screen.getByLabelText(/Orders type/), ORDERS_TYPE.STUDENT_TRAVEL); + + // set order type to value the re-enables "has dependents" + await userEvent.selectOptions(screen.getByLabelText(/Orders type/), 'LOCAL_MOVE'); + + const hasDependentsYesLocalMove = screen.getByLabelText('Yes'); + const hasDependentsNoLocalMove = screen.getByLabelText('No'); + + await waitFor(() => { + expect(hasDependentsYesLocalMove).not.toBeChecked(); + expect(hasDependentsYesLocalMove).toBeEnabled(); + expect(hasDependentsNoLocalMove).not.toBeChecked(); + expect(hasDependentsNoLocalMove).toBeEnabled(); + }); + }); + + it('has dependents becomes disabled and then re-enabled for order type early return', async () => { + render(); + + await waitFor(() => expect(screen.queryByText('Loading, please wait...')).not.toBeInTheDocument()); + + // set order type to perm change and verify the "has dependents" state + await userEvent.selectOptions(screen.getByLabelText(/Orders type/), 'PERMANENT_CHANGE_OF_STATION'); + + const hasDependentsYesPermChg = screen.getByLabelText('Yes'); + const hasDependentsNoPermChg = screen.getByLabelText('No'); + + await waitFor(() => { + expect(hasDependentsYesPermChg).not.toBeChecked(); + expect(hasDependentsYesPermChg).toBeEnabled(); + expect(hasDependentsNoPermChg).not.toBeChecked(); + expect(hasDependentsNoPermChg).toBeEnabled(); + }); + + // set order type to value that disables and defaults "has dependents" + await userEvent.selectOptions(screen.getByLabelText(/Orders type/), ORDERS_TYPE.EARLY_RETURN_OF_DEPENDENTS); + + // set order type to value the re-enables "has dependents" + await userEvent.selectOptions(screen.getByLabelText(/Orders type/), 'LOCAL_MOVE'); + + const hasDependentsYesLocalMove = screen.getByLabelText('Yes'); + const hasDependentsNoLocalMove = screen.getByLabelText('No'); + + await waitFor(() => { + expect(hasDependentsYesLocalMove).not.toBeChecked(); + expect(hasDependentsYesLocalMove).toBeEnabled(); + expect(hasDependentsNoLocalMove).not.toBeChecked(); + expect(hasDependentsNoLocalMove).toBeEnabled(); + }); + }); + afterEach(jest.restoreAllMocks); }); diff --git a/src/components/Customer/MtoShipmentForm/MtoShipmentForm.jsx b/src/components/Customer/MtoShipmentForm/MtoShipmentForm.jsx index a8ef7796371..542dd6c5a26 100644 --- a/src/components/Customer/MtoShipmentForm/MtoShipmentForm.jsx +++ b/src/components/Customer/MtoShipmentForm/MtoShipmentForm.jsx @@ -45,7 +45,7 @@ import { AddressShape, SimpleAddressShape } from 'types/address'; import { OrdersShape } from 'types/customerShapes'; import { ShipmentShape } from 'types/shipment'; import { formatMtoShipmentForAPI, formatMtoShipmentForDisplay } from 'utils/formatMtoShipment'; -import { formatWeight } from 'utils/formatters'; +import { formatUBAllowanceWeight, formatWeight } from 'utils/formatters'; import { validateDate } from 'utils/validation'; import withRouter from 'utils/routing'; import { ORDERS_TYPE } from 'constants/orders'; @@ -316,7 +316,9 @@ class MtoShipmentForm extends Component { Remember: You can move {isUB - ? ` up to your UB allowance for this move` + ? ` up to ${formatUBAllowanceWeight( + orders?.entitlement?.ub_allowance, + )} for this UB shipment. The weight of your UB is part of your authorized weight allowance` : ` ${formatWeight(orders.authorizedWeight)} total`} . You’ll be billed for any excess weight you move. @@ -347,7 +349,7 @@ class MtoShipmentForm extends Component { ( <> @@ -360,7 +362,7 @@ class MtoShipmentForm extends Component { id="useCurrentResidenceCheckbox" /> {fields} -

    Second pickup location

    +

    Second Pickup Address

    Do you want movers to pick up any belongings from a second address? (Must be near @@ -374,7 +376,7 @@ class MtoShipmentForm extends Component { label="Yes" name="hasSecondaryPickup" value="yes" - title="Yes, I have a second pickup location" + title="Yes, I have a second pickup address" checked={hasSecondaryPickup === 'yes'} />

    @@ -404,7 +406,7 @@ class MtoShipmentForm extends Component { label="Yes" name="hasTertiaryPickup" value="yes" - title="Yes, I have a third pickup location" + title="Yes, I have a third pickup address" checked={hasTertiaryPickup === 'yes'} /> @@ -425,7 +427,7 @@ class MtoShipmentForm extends Component { hasTertiaryPickup === 'yes' && hasSecondaryPickup === 'yes' && ( <> -

    Third pickup location

    +

    Third Pickup Address

    )} @@ -448,7 +450,7 @@ class MtoShipmentForm extends Component { {showDeliveryFields && ( - {showPickupFields &&

    Destination info

    } + {showPickupFields &&

    Delivery Address info

    }
    You will finalize an actual delivery date later by talking with your Customer Care @@ -468,7 +470,7 @@ class MtoShipmentForm extends Component { />
    -
    +
    {!isNTSR && (
    @@ -286,7 +286,7 @@ const DateAndLocationForm = ({ mtoShipment, destinationDutyLocation, serviceMemb values.hasSecondaryPickupAddress === 'true' && values.hasTertiaryPickupAddress === 'true' && ( <> -

    Third pickup location

    +

    Third Pickup Address

    )} @@ -295,7 +295,7 @@ const DateAndLocationForm = ({ mtoShipment, destinationDutyLocation, serviceMemb /> -

    Destination

    +

    Delivery Address

    ( <> -

    Please input your destination address.

    +

    Please input your delivery address.

    {values.hasSecondaryDestinationAddress === 'true' && ( <> -

    Second delivery location

    +

    Second Delivery Address

    - A second destination address could mean that your final incentive is lower than your + A second delivery address could mean that your final incentive is lower than your estimate.

    @@ -367,7 +367,7 @@ const DateAndLocationForm = ({ mtoShipment, destinationDutyLocation, serviceMemb label="Yes" name="hasTertiaryDestinationAddress" value="true" - title="Yes, I have a third delivery location" + title="Yes, I have a third delivery address" checked={values.hasTertiaryDestinationAddress === 'true'} />

    @@ -388,7 +388,7 @@ const DateAndLocationForm = ({ mtoShipment, destinationDutyLocation, serviceMemb values.hasSecondaryDestinationAddress === 'true' && values.hasTertiaryDestinationAddress === 'true' && ( <> -

    Third delivery location

    +

    Third Delivery Address

    )} diff --git a/src/components/Customer/PPM/Booking/DateAndLocationForm/DateAndLocationForm.test.jsx b/src/components/Customer/PPM/Booking/DateAndLocationForm/DateAndLocationForm.test.jsx index ef1030c347f..fa5fe726a81 100644 --- a/src/components/Customer/PPM/Booking/DateAndLocationForm/DateAndLocationForm.test.jsx +++ b/src/components/Customer/PPM/Booking/DateAndLocationForm/DateAndLocationForm.test.jsx @@ -46,7 +46,7 @@ describe('DateAndLocationForm component', () => { describe('displays form', () => { it('renders blank form on load', async () => { render(); - expect(await screen.getByRole('heading', { level: 2, name: 'Origin' })).toBeInTheDocument(); + expect(await screen.getByRole('heading', { level: 2, name: 'Pickup Address' })).toBeInTheDocument(); const postalCodes = screen.getAllByLabelText(/ZIP/); const address1 = screen.getAllByLabelText(/Address 1/); const address2 = screen.getAllByLabelText('Address 2', { exact: false }); @@ -62,7 +62,7 @@ describe('DateAndLocationForm component', () => { expect(postalCodes[0]).toBeInstanceOf(HTMLInputElement); expect(screen.getAllByLabelText('Yes')[0]).toBeInstanceOf(HTMLInputElement); expect(screen.getAllByLabelText('No')[0]).toBeInstanceOf(HTMLInputElement); - expect(screen.getByRole('heading', { level: 2, name: 'Destination' })).toBeInTheDocument(); + expect(screen.getByRole('heading', { level: 2, name: 'Delivery Address' })).toBeInTheDocument(); expect(address1[1]).toBeInstanceOf(HTMLInputElement); expect(address2[1]).toBeInstanceOf(HTMLInputElement); expect(address3[1]).toBeInstanceOf(HTMLInputElement); @@ -82,22 +82,22 @@ describe('DateAndLocationForm component', () => { }); describe('displays conditional inputs', () => { - it('displays current address when "Use my current origin address" is selected', async () => { + it('displays current address when "Use my current pickup address" is selected', async () => { render(); const postalCodes = screen.getAllByLabelText(/ZIP/); expect(postalCodes[0].value).toBe(''); await act(async () => { - await userEvent.click(screen.getByLabelText('Use my current origin address')); + await userEvent.click(screen.getByLabelText('Use my current pickup address')); }); await waitFor(() => { expect(postalCodes[0].value).toBe(defaultProps.serviceMember.residential_address.postalCode); }); }); - it('removes current Address when "Use my current origin address" is deselected', async () => { + it('removes current Address when "Use my current pickup address" is deselected', async () => { render(); await act(async () => { - await userEvent.click(screen.getByLabelText('Use my current origin address')); + await userEvent.click(screen.getByLabelText('Use my current pickup address')); }); const postalCodes = screen.getAllByLabelText(/ZIP/); @@ -106,7 +106,7 @@ describe('DateAndLocationForm component', () => { }); await act(async () => { - await userEvent.click(screen.getByLabelText('Use my current origin address')); + await userEvent.click(screen.getByLabelText('Use my current pickup address')); }); await waitFor(() => { @@ -135,10 +135,10 @@ describe('DateAndLocationForm component', () => { }); }); - it('displays destination address when "Use my current destination address" is selected', async () => { + it('displays delivery address when "Use my current delivery address" is selected', async () => { await act(async () => { render(); - await userEvent.click(screen.getByLabelText('Use my current destination address')); + await userEvent.click(screen.getByLabelText('Use my current delivery address')); const postalCodes = screen.getAllByLabelText(/ZIP/); const address1 = screen.getAllByLabelText(/Address 1/, { exact: false }); const address2 = screen.getAllByLabelText('Address 2', { exact: false }); @@ -153,7 +153,7 @@ describe('DateAndLocationForm component', () => { }); }); - it('displays secondary destination Address input when hasSecondaryDestinationAddress is true', async () => { + it('displays secondary delivery address input when hasSecondaryDestinationAddress is true', async () => { await act(async () => { render(); const hasSecondaryDestinationAddress = await screen.getAllByLabelText('Yes')[1]; @@ -266,11 +266,11 @@ describe('validates form fields and displays error messages', () => { }); }); - it('destination address 1 is empty passes validation schema - destination street 1 is OPTIONAL', async () => { + it('delivery address 1 is empty passes validation schema - destination street 1 is OPTIONAL', async () => { await act(async () => { render(); - // type something in for destination address 1 + // type something in for delivery address 1 await userEvent.type( document.querySelector('input[name="destinationAddress.address.streetAddress1"]'), '1234 Street', @@ -289,8 +289,8 @@ describe('validates form fields and displays error messages', () => { // only expecting postalCode alert expect(requiredAlerts.length).toBe(1); - // 'Required' labelHint on address display. expecting a total of 7(2 for pickup address and 3 destination address with 2 misc). - // This is to verify Required labelHints are displayed correctly for PPM onboarding/edit for the destination address + // 'Required' labelHint on address display. expecting a total of 7(2 for pickup address and 3 delivery address with 2 misc). + // This is to verify Required labelHints are displayed correctly for PPM onboarding/edit for the delivery address // street 1 is now OPTIONAL. If this fails it means addtional labelHints have been introduced elsewhere within the control. const hints = document.getElementsByClassName('usa-hint'); expect(hints.length).toBe(7); @@ -322,7 +322,7 @@ describe('validates form fields and displays error messages', () => { }); }); }); - it('displays tertiary destination Address input when hasTertiaryDestinationAddress is true', async () => { + it('displays tertiary delivery address input when hasTertiaryDestinationAddress is true', async () => { await act(async () => { render(); const hasTertiaryDestinationAddress = await screen.getAllByLabelText('Yes')[2]; diff --git a/src/components/Customer/PPM/Closeout/AboutForm/AboutForm.jsx b/src/components/Customer/PPM/Closeout/AboutForm/AboutForm.jsx index 055456f4495..b2b5846add5 100644 --- a/src/components/Customer/PPM/Closeout/AboutForm/AboutForm.jsx +++ b/src/components/Customer/PPM/Closeout/AboutForm/AboutForm.jsx @@ -107,7 +107,7 @@ const AboutForm = ({ mtoShipment, onBack, onSubmit }) => { render={(fields) => ( <> {fields} -

    Second pickup location

    +

    Second Pickup Address

    Will you pick up any belongings from a second address? (Must be near the pickup address. @@ -121,7 +121,7 @@ const AboutForm = ({ mtoShipment, onBack, onSubmit }) => { label="Yes" name="hasSecondaryPickupAddress" value="true" - title="Yes, there is a second pickup location" + title="Yes, there is a second pickup address" checked={values.hasSecondaryPickupAddress === 'true'} /> { label="No" name="hasSecondaryPickupAddress" value="false" - title="No, there is not a second pickup location" + title="No, there is not a second pickup address" checked={values.hasSecondaryPickupAddress !== 'true'} /> @@ -148,17 +148,17 @@ const AboutForm = ({ mtoShipment, onBack, onSubmit }) => { /> ( <> {fields} -

    Second destination address

    +

    Second Delivery Address

    - Will you deliver any belongings to a second address? (Must be near the destination address. + Will you deliver any belongings to a second address? (Must be near the delivery address. Subject to approval.)

    @@ -169,7 +169,7 @@ const AboutForm = ({ mtoShipment, onBack, onSubmit }) => { label="Yes" name="hasSecondaryDestinationAddress" value="true" - title="Yes, there is a second destination location" + title="Yes, there is a second delivery address" checked={values.hasSecondaryDestinationAddress === 'true'} /> { label="No" name="hasSecondaryDestinationAddress" value="false" - title="No, there is not a second destination location" + title="No, there is not a second delivery address" checked={values.hasSecondaryDestinationAddress !== 'true'} />
    diff --git a/src/components/Customer/PPM/Closeout/AboutForm/AboutForm.test.jsx b/src/components/Customer/PPM/Closeout/AboutForm/AboutForm.test.jsx index 476a3defdc3..57b35c1906e 100644 --- a/src/components/Customer/PPM/Closeout/AboutForm/AboutForm.test.jsx +++ b/src/components/Customer/PPM/Closeout/AboutForm/AboutForm.test.jsx @@ -241,8 +241,8 @@ describe('AboutForm component', () => { await userEvent.type(input, '123 Street'); await expect(screen.getByRole('button', { name: 'Save & Continue' })).toBeEnabled(); - // 'Optional' labelHint on address display. expecting a total of 9(3 for pickup address, 3 destination address, 3 w2 address). - // This is to verify Required labelHints are displayed correctly for PPM doc uploading for the destination address + // 'Optional' labelHint on address display. expecting a total of 9(3 for pickup address, 3 delivery address, 3 w2 address). + // This is to verify Required labelHints are displayed correctly for PPM doc uploading for the delivery address // street 1 is now OPTIONAL for onboarding but required for PPM doc upload. If this fails it means addtional labelHints // have been introduced elsewhere within the control. const hints = document.getElementsByClassName('usa-hint'); diff --git a/src/components/Customer/Review/OrdersTable/OrdersTable.jsx b/src/components/Customer/Review/OrdersTable/OrdersTable.jsx index fb2ea272af9..6ebe6a603e5 100644 --- a/src/components/Customer/Review/OrdersTable/OrdersTable.jsx +++ b/src/components/Customer/Review/OrdersTable/OrdersTable.jsx @@ -20,6 +20,9 @@ const OrdersTable = ({ payGrade, orderId, counselingOfficeName, + accompaniedTour, + dependentsUnderTwelve, + dependentsTwelveAndOver, }) => { const isRetirementOrSeparation = ['RETIREMENT', 'SEPARATION'].includes(orderType); const editPath = `/move/${moveId}/review/edit-orders/${orderId}`; @@ -79,6 +82,24 @@ const OrdersTable = ({ Dependents {hasDependents ? 'Yes' : 'No'} + {/* Group conditionally rendered OCONUS fields */} + {(accompaniedTour || dependentsUnderTwelve > 0 || dependentsTwelveAndOver > 0) && ( + <> + + Accompanied tour + {accompaniedTour ? 'Yes' : 'No'} + + + Dependents under twelve + {dependentsUnderTwelve || 0} + + + Dependents twelve and over + {dependentsTwelveAndOver || 0} + + + )} + {/* End grouping of UB fields */} Orders diff --git a/src/components/Customer/Review/ShipmentCard/BoatShipmentCard/BoatShipmentCard.test.jsx b/src/components/Customer/Review/ShipmentCard/BoatShipmentCard/BoatShipmentCard.test.jsx index 19515bfc5a0..6640f518f26 100644 --- a/src/components/Customer/Review/ShipmentCard/BoatShipmentCard/BoatShipmentCard.test.jsx +++ b/src/components/Customer/Review/ShipmentCard/BoatShipmentCard/BoatShipmentCard.test.jsx @@ -78,10 +78,10 @@ describe('BoatShipmentCard component', () => { const expectedRows = [ ['Shipment Method', 'BTA'], ['Requested pickup date', '01 Jan 2020'], - ['Pickup location', '17 8th St New York, NY 11111'], + ['Pickup Address', '17 8th St New York, NY 11111'], ['Releasing agent', 'Jo Xi (555) 555-5555 jo.xi@email.com'], ['Requested delivery date', '01 Mar 2020'], - ['Destination', '17 8th St New York, NY 73523'], + ['Delivery Address', '17 8th St New York, NY 73523'], ['Receiving agent', 'Dorothy Lagomarsino (999) 999-9999 dorothy.lagomarsino@email.com'], ['Boat year', '2020'], ['Boat make', 'Test Make'], diff --git a/src/components/Customer/Review/ShipmentCard/DeliveryDisplay.jsx b/src/components/Customer/Review/ShipmentCard/DeliveryDisplay.jsx index f77bae80c32..c5b909b4a47 100644 --- a/src/components/Customer/Review/ShipmentCard/DeliveryDisplay.jsx +++ b/src/components/Customer/Review/ShipmentCard/DeliveryDisplay.jsx @@ -34,18 +34,18 @@ const DeliveryDisplay = ({
    {formatCustomerDate(requestedDeliveryDate)}
    -
    Destination
    +
    Delivery Address
    {formatCustomerDestination(destinationLocation, destinationZIP)}
    {secondaryDeliveryAddress && (
    -
    Second Destination
    +
    Second Delivery Address
    {formatCustomerDestination(secondaryDeliveryAddress)}
    )} {isTertiaryAddressEnabled && secondaryDeliveryAddress && tertiaryDeliveryAddress && (
    -
    Third Destination
    +
    Third Delivery Address
    {formatCustomerDestination(tertiaryDeliveryAddress)}
    )} diff --git a/src/components/Customer/Review/ShipmentCard/HHGShipmentCard/HHGShipmentCard.test.jsx b/src/components/Customer/Review/ShipmentCard/HHGShipmentCard/HHGShipmentCard.test.jsx index 61ff9fd5636..818af2132f1 100644 --- a/src/components/Customer/Review/ShipmentCard/HHGShipmentCard/HHGShipmentCard.test.jsx +++ b/src/components/Customer/Review/ShipmentCard/HHGShipmentCard/HHGShipmentCard.test.jsx @@ -104,10 +104,10 @@ describe('HHGShipmentCard component', () => { const wrapper = mountHHGShipmentCard(); const tableHeaders = [ 'Requested pickup date', - 'Pickup location', + 'Pickup Address', 'Releasing agent', 'Requested delivery date', - 'Destination', + 'Delivery Address', 'Receiving agent', 'Remarks', ]; @@ -140,7 +140,7 @@ describe('HHGShipmentCard component', () => { it('should render without releasing/receiving agents and remarks', () => { const wrapper = mountHHGShipmentCard({ ...defaultProps, releasingAgent: null, receivingAgent: null, remarks: '' }); - const tableHeaders = ['Requested pickup date', 'Pickup location', 'Requested delivery date', 'Destination']; + const tableHeaders = ['Requested pickup date', 'Pickup Address', 'Requested delivery date', 'Delivery Address']; const { streetAddress1, city, state, postalCode } = defaultProps.pickupLocation; const tableData = [ formatCustomerDate(defaultProps.requestedPickupDate), @@ -153,33 +153,33 @@ describe('HHGShipmentCard component', () => { expect(wrapper.find('.remarksCell').length).toBe(0); }); - it('should not render a secondary pickup location if not provided one', async () => { + it('should not render a secondary Pickup Address if not provided one', async () => { render(); - const secondPickupLocation = await screen.queryByText('Second pickup location'); + const secondPickupLocation = await screen.queryByText('Second Pickup Address'); expect(secondPickupLocation).not.toBeInTheDocument(); }); - it('should not render a secondary destination location if not provided one', async () => { + it('should not render a secondary delivery address if not provided one', async () => { render(); - const secondDestination = await screen.queryByText('Second Destination'); + const secondDestination = await screen.queryByText('Second Delivery Address'); expect(secondDestination).not.toBeInTheDocument(); }); - it('should render a secondary pickup location if provided one', async () => { + it('should render a secondary Pickup Address if provided one', async () => { render(); - const secondPickupLocation = await screen.getByText('Second pickup location'); + const secondPickupLocation = await screen.getByText('Second Pickup Address'); expect(secondPickupLocation).toBeInTheDocument(); const secondPickupLocationInformation = await screen.getByText(/Some Other Street Name/); expect(secondPickupLocationInformation).toBeInTheDocument(); }); - it('should render a secondary destination location if provided one', async () => { + it('should render a secondary delivery address if provided one', async () => { render(); - const secondDestination = await screen.getByText('Second Destination'); + const secondDestination = await screen.getByText('Second Delivery Address'); expect(secondDestination).toBeInTheDocument(); const secondDesintationInformation = await screen.getByText(/Some Street Name/); expect(secondDesintationInformation).toBeInTheDocument(); @@ -298,10 +298,10 @@ describe('HHGShipmentCard component can be reused for UB shipment card', () => { const wrapper = mountHHGShipmentCardForUBShipment(); const tableHeaders = [ 'Requested pickup date', - 'Pickup location', + 'Pickup Address', 'Releasing agent', 'Requested delivery date', - 'Destination', + 'Delivery Address', 'Receiving agent', 'Remarks', ]; @@ -339,7 +339,7 @@ describe('HHGShipmentCard component can be reused for UB shipment card', () => { receivingAgent: null, remarks: '', }); - const tableHeaders = ['Requested pickup date', 'Pickup location', 'Requested delivery date', 'Destination']; + const tableHeaders = ['Requested pickup date', 'Pickup Address', 'Requested delivery date', 'Delivery Address']; const { streetAddress1, city, state, postalCode } = ubProps.pickupLocation; const tableData = [ formatCustomerDate(ubProps.requestedPickupDate), @@ -352,33 +352,33 @@ describe('HHGShipmentCard component can be reused for UB shipment card', () => { expect(wrapper.find('.remarksCell').length).toBe(0); }); - it('should not render a secondary pickup location on UB shipment card if not provided one', async () => { + it('should not render a secondary Pickup Address on UB shipment card if not provided one', async () => { render(); - const secondPickupLocation = await screen.queryByText('Second pickup location'); + const secondPickupLocation = await screen.queryByText('Second Pickup Address'); expect(secondPickupLocation).not.toBeInTheDocument(); }); - it('should not render a secondary destination location on UB shipment card if not provided one', async () => { + it('should not render a secondary delivery address on UB shipment card if not provided one', async () => { render(); - const secondDestination = await screen.queryByText('Second Destination'); + const secondDestination = await screen.queryByText('Second Delivery Address'); expect(secondDestination).not.toBeInTheDocument(); }); - it('should render a UB shipment card secondary pickup location if provided one', async () => { + it('should render a UB shipment card secondary Pickup Address if provided one', async () => { render(); - const secondPickupLocation = await screen.getByText('Second pickup location'); + const secondPickupLocation = await screen.getByText('Second Pickup Address'); expect(secondPickupLocation).toBeInTheDocument(); const secondPickupLocationInformation = await screen.getByText(/Some Other Street Name/); expect(secondPickupLocationInformation).toBeInTheDocument(); }); - it('should render a UB shipment card secondary destination location if provided one', async () => { + it('should render a UB shipment card secondary delivery address if provided one', async () => { render(); - const secondDestination = await screen.getByText('Second Destination'); + const secondDestination = await screen.getByText('Second Delivery Address'); expect(secondDestination).toBeInTheDocument(); const secondDesintationInformation = await screen.getByText(/Some Street Name/); expect(secondDesintationInformation).toBeInTheDocument(); diff --git a/src/components/Customer/Review/ShipmentCard/MobileHomeShipmentCard/MobileHomeShipmentCard.test.jsx b/src/components/Customer/Review/ShipmentCard/MobileHomeShipmentCard/MobileHomeShipmentCard.test.jsx index 1da840afe9a..4bfe62f5017 100644 --- a/src/components/Customer/Review/ShipmentCard/MobileHomeShipmentCard/MobileHomeShipmentCard.test.jsx +++ b/src/components/Customer/Review/ShipmentCard/MobileHomeShipmentCard/MobileHomeShipmentCard.test.jsx @@ -61,7 +61,7 @@ describe('MobileHomeShipmentCard component', () => { it('renders component with all fields', () => { render(); - expect(screen.getAllByTestId('ShipmentCardNumber').length).toBe(1); + expect(screen.getByRole('heading', { level: 3 })).toHaveTextContent('Mobile Home 1'); expect(screen.getByText(/^#testMove123-01$/, { selector: 'p' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Edit' })).toBeInTheDocument(); @@ -71,10 +71,10 @@ describe('MobileHomeShipmentCard component', () => { const expectedRows = [ ['Requested pickup date', '01 Jan 2020'], - ['Pickup location', '17 8th St New York, NY 11111'], + ['Pickup Address', '17 8th St New York, NY 11111'], ['Releasing agent', 'Super Mario (555) 555-5555 superMario@gmail.com'], ['Requested delivery date', '01 Mar 2020'], - ['Destination', '17 8th St New York, NY 73523'], + ['Delivery Address', '17 8th St New York, NY 73523'], ['Receiving agent', 'Princess Peach (999) 999-9999 princessPeach@gmail.com'], ['Mobile Home year', '2020'], ['Mobile Home make', 'Test Make'], @@ -139,6 +139,11 @@ describe('MobileHomeShipmentCard component', () => { expect(screen.getByTitle('Help about incomplete shipment')).toBeInTheDocument(); await userEvent.click(screen.getByTitle('Help about incomplete shipment')); - expect(screen.getAllByTestId('ShipmentCardNumber').length).toBe(1); + + expect(incompleteShipmentProps.onIncompleteClick).toHaveBeenCalledWith( + 'Mobile Home 1', + 'testMove123-01', + SHIPMENT_TYPES.MOBILE_HOME, + ); }); }); diff --git a/src/components/Customer/Review/ShipmentCard/NTSRShipmentCard/NTSRShipmentCard.test.jsx b/src/components/Customer/Review/ShipmentCard/NTSRShipmentCard/NTSRShipmentCard.test.jsx index 539ebee276f..4a08a029138 100644 --- a/src/components/Customer/Review/ShipmentCard/NTSRShipmentCard/NTSRShipmentCard.test.jsx +++ b/src/components/Customer/Review/ShipmentCard/NTSRShipmentCard/NTSRShipmentCard.test.jsx @@ -72,7 +72,7 @@ function mountNTSRShipmentCard(props) { describe('NTSRShipmentCard component', () => { it('renders component with all fields', () => { const wrapper = mountNTSRShipmentCard(); - const tableHeaders = ['Requested delivery date', 'Destination', 'Receiving agent', 'Remarks']; + const tableHeaders = ['Requested delivery date', 'Delivery Address', 'Receiving agent', 'Remarks']; const { firstName: receivingFirstName, lastName: receivingLastName, @@ -92,7 +92,7 @@ describe('NTSRShipmentCard component', () => { it('should render without releasing/receiving agents and remarks', () => { const wrapper = mountNTSRShipmentCard({ ...defaultProps, releasingAgent: null, remarks: '' }); - const tableHeaders = ['Requested delivery date', 'Destination']; + const tableHeaders = ['Requested delivery date', 'Delivery Address']; const tableData = [formatCustomerDate(defaultProps.requestedDeliveryDate), defaultProps.destinationZIP]; tableHeaders.forEach((label, index) => expect(wrapper.find('dt').at(index).text()).toBe(label)); tableData.forEach((label, index) => expect(wrapper.find('dd').at(index).text()).toBe(label)); @@ -104,17 +104,17 @@ describe('NTSRShipmentCard component', () => { expect(screen.getByRole('heading', { level: 3 })).toHaveTextContent(`${defaultProps.marketCode}NTS-release`); }); - it('should not render a secondary destination location if not provided one', async () => { + it('should not render a secondary delivery address if not provided one', async () => { render(); - const secondDestination = await screen.queryByText('Second Destination'); + const secondDestination = await screen.queryByText('Second Delivery Address'); expect(secondDestination).not.toBeInTheDocument(); }); - it('should render a secondary destination location if provided one', async () => { + it('should render a secondary delivery address if provided one', async () => { render(); - const secondDestination = await screen.getByText('Second Destination'); + const secondDestination = await screen.getByText('Second Delivery Address'); expect(secondDestination).toBeInTheDocument(); const secondDesintationInformation = await screen.getByText(/Some Street Name/); expect(secondDesintationInformation).toBeInTheDocument(); diff --git a/src/components/Customer/Review/ShipmentCard/NTSShipmentCard/NTSShipmentCard.test.jsx b/src/components/Customer/Review/ShipmentCard/NTSShipmentCard/NTSShipmentCard.test.jsx index 7b790dfff27..92b065177bf 100644 --- a/src/components/Customer/Review/ShipmentCard/NTSShipmentCard/NTSShipmentCard.test.jsx +++ b/src/components/Customer/Review/ShipmentCard/NTSShipmentCard/NTSShipmentCard.test.jsx @@ -78,7 +78,7 @@ const secondaryPickupAddress = { describe('NTSShipmentCard component', () => { it('renders component with all fields', () => { const wrapper = mountNTSShipmentCard(); - const tableHeaders = ['Requested pickup date', 'Pickup location', 'Releasing agent', 'Remarks']; + const tableHeaders = ['Requested pickup date', 'Pickup Address', 'Releasing agent', 'Remarks']; const { streetAddress1, city, state, postalCode } = defaultProps.pickupLocation; const { firstName: releasingFirstName, @@ -104,7 +104,7 @@ describe('NTSShipmentCard component', () => { it('should render without releasing/receiving agents and remarks', () => { const wrapper = mountNTSShipmentCard({ ...defaultProps, releasingAgent: null, remarks: '' }); - const tableHeaders = ['Requested pickup date', 'Pickup location']; + const tableHeaders = ['Requested pickup date', 'Pickup Address']; const { streetAddress1, city, state, postalCode } = defaultProps.pickupLocation; const tableData = [ formatCustomerDate(defaultProps.requestedPickupDate), @@ -115,17 +115,17 @@ describe('NTSShipmentCard component', () => { expect(wrapper.find('.remarksCell').at(0).text()).toBe('—'); }); - it('should not render a secondary pickup location if not provided one', async () => { + it('should not render a secondary Pickup Address if not provided one', async () => { render(); - const secondPickupLocation = await screen.queryByText('Second pickup location'); + const secondPickupLocation = await screen.queryByText('Second Pickup Address'); expect(secondPickupLocation).not.toBeInTheDocument(); }); - it('should render a secondary pickup location if provided one', async () => { + it('should render a secondary Pickup Address if provided one', async () => { render(); - const secondPickupLocation = await screen.getByText('Second pickup location'); + const secondPickupLocation = await screen.getByText('Second Pickup Address'); expect(secondPickupLocation).toBeInTheDocument(); const secondPickupLocationInformation = await screen.getByText(/Some Other Street Name/); expect(secondPickupLocationInformation).toBeInTheDocument(); diff --git a/src/components/Customer/Review/ShipmentCard/PPMShipmentCard/PPMShipmentCard.jsx b/src/components/Customer/Review/ShipmentCard/PPMShipmentCard/PPMShipmentCard.jsx index d4f3ad20b47..3e15ad6e82a 100644 --- a/src/components/Customer/Review/ShipmentCard/PPMShipmentCard/PPMShipmentCard.jsx +++ b/src/components/Customer/Review/ShipmentCard/PPMShipmentCard/PPMShipmentCard.jsx @@ -114,34 +114,34 @@ const PPMShipmentCard = ({
    {formatCustomerDate(expectedDepartureDate)}
    -
    Origin address
    +
    Pickup Address
    {pickupAddress ? formatCustomerContactFullAddress(pickupAddress) : '—'}
    {secondaryPickupAddress && (
    -
    Second origin address
    +
    Second Pickup Address
    {formatCustomerContactFullAddress(secondaryPickupAddress)}
    )} {isTertiaryAddressEnabled && tertiaryPickupAddress && secondaryPickupAddress && (
    -
    Third origin address
    +
    Third Pickup Address
    {formatCustomerContactFullAddress(tertiaryPickupAddress)}
    )}
    -
    Destination address
    +
    Delivery Address
    {destinationAddress ? formatCustomerContactFullAddress(destinationAddress) : '—'}
    {secondaryDestinationAddress && (
    -
    Second destination address
    +
    Second Delivery Address
    {formatCustomerContactFullAddress(secondaryDestinationAddress)}
    )} {isTertiaryAddressEnabled && tertiaryDestinationAddress && secondaryDestinationAddress && (
    -
    Third destination address
    +
    Third Delivery Address
    {formatCustomerContactFullAddress(tertiaryDestinationAddress)}
    )} diff --git a/src/components/Customer/Review/ShipmentCard/PPMShipmentCard/PPMShipmentCard.test.jsx b/src/components/Customer/Review/ShipmentCard/PPMShipmentCard/PPMShipmentCard.test.jsx index 8b5b33e652a..5b491b427f1 100644 --- a/src/components/Customer/Review/ShipmentCard/PPMShipmentCard/PPMShipmentCard.test.jsx +++ b/src/components/Customer/Review/ShipmentCard/PPMShipmentCard/PPMShipmentCard.test.jsx @@ -151,10 +151,10 @@ describe('PPMShipmentCard component', () => { const expectedRows = [ ['Expected departure', '01 Jan 2020'], - ['Origin address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 10001'], - ['Second origin address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 10002'], - ['Destination address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 11111'], - ['Second destination address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 22222'], + ['Pickup Address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 10001'], + ['Second Pickup Address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 10002'], + ['Delivery Address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 11111'], + ['Second Delivery Address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 22222'], ['Storage expected? (SIT)', 'Yes'], ['Estimated weight', '5,999 lbs'], ['Pro-gear', 'Yes, 1,250 lbs'], @@ -193,8 +193,8 @@ describe('PPMShipmentCard component', () => { const expectedRows = [ ['Expected departure', '01 Jan 2020'], - ['Origin address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 10001'], - ['Destination address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 11111'], + ['Pickup Address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 10001'], + ['Delivery Address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 11111'], ['Storage expected? (SIT)', 'No'], ['Estimated weight', '0 lbs'], ['Pro-gear', 'No'], @@ -248,10 +248,10 @@ describe('PPMShipmentCard component', () => { const expectedRows = [ ['Expected departure', '01 Jan 2020'], - ['Origin address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 10001'], - ['Second origin address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 10002'], - ['Destination address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 11111'], - ['Second destination address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 22222'], + ['Pickup Address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 10001'], + ['Second Pickup Address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 10002'], + ['Delivery Address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 11111'], + ['Second Delivery Address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 22222'], ['Closeout office', move.closeoutOffice.name], ['Storage expected? (SIT)', 'Yes'], ['Estimated weight', '5,999 lbs'], @@ -282,10 +282,10 @@ describe('PPMShipmentCard component', () => { const expectedRows = [ ['Expected departure', '01 Jan 2020'], - ['Origin address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 10001'], - ['Second origin address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 10002'], - ['Destination address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 11111'], - ['Second destination address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 22222'], + ['Pickup Address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 10001'], + ['Second Pickup Address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 10002'], + ['Delivery Address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 11111'], + ['Second Delivery Address', '111 Test Street, 222 Test Street, Test Man, Test City, NY 22222'], ['Closeout office', move.closeoutOffice.name], ['Storage expected? (SIT)', 'Yes'], ['Estimated weight', '5,999 lbs'], diff --git a/src/components/Customer/Review/ShipmentCard/PickupDisplay.jsx b/src/components/Customer/Review/ShipmentCard/PickupDisplay.jsx index 717e30798d8..56cfe0246e3 100644 --- a/src/components/Customer/Review/ShipmentCard/PickupDisplay.jsx +++ b/src/components/Customer/Review/ShipmentCard/PickupDisplay.jsx @@ -33,7 +33,7 @@ const PickupDisplay = ({ {pickupLocation && (
    -
    Pickup location
    +
    Pickup Address
    {pickupLocation.streetAddress1} {pickupLocation.streetAddress2}
    @@ -43,7 +43,7 @@ const PickupDisplay = ({ )} {secondaryPickupAddress && (
    -
    Second pickup location
    +
    Second Pickup Address
    {secondaryPickupAddress.streetAddress1} {secondaryPickupAddress.streetAddress2}
    @@ -53,7 +53,7 @@ const PickupDisplay = ({ )} {isTertiaryAddressEnabled && tertiaryPickupAddress && secondaryPickupAddress && (
    -
    Third pickup location
    +
    Third Pickup Address
    {tertiaryPickupAddress.streetAddress1} {tertiaryPickupAddress.streetAddress2}
    diff --git a/src/components/Customer/Review/Summary/Summary.jsx b/src/components/Customer/Review/Summary/Summary.jsx index a7b8001962d..606b940950c 100644 --- a/src/components/Customer/Review/Summary/Summary.jsx +++ b/src/components/Customer/Review/Summary/Summary.jsx @@ -501,6 +501,9 @@ export class Summary extends Component { originDutyLocationName={currentOrders.origin_duty_location.name} orderId={currentOrders.id} counselingOfficeName={currentMove.counselingOffice?.name || ''} + accompaniedTour={currentOrders.entitlement?.accompanied_tour} + dependentsUnderTwelve={currentOrders.entitlement?.dependents_under_twelve} + dependentsTwelveAndOver={currentOrders.entitlement?.dependents_twelve_and_over} /> {thirdSectionHasContent && ( diff --git a/src/components/DocumentViewer/Content/Content.jsx b/src/components/DocumentViewer/Content/Content.jsx index 79ce99d7a52..c67f1869dab 100644 --- a/src/components/DocumentViewer/Content/Content.jsx +++ b/src/components/DocumentViewer/Content/Content.jsx @@ -42,7 +42,7 @@ const DocViewerContent = ({ Zoom in - {['jpg', 'jpeg', 'gif', 'png'].includes(fileType) && ( + {['jpg', 'jpeg', 'gif', 'png', 'pdf'].includes(fileType) && ( <> - + {fileType !== 'pdf' && ( + + )} )}
    diff --git a/src/components/Office/AddOrdersForm/AddOrdersForm.jsx b/src/components/Office/AddOrdersForm/AddOrdersForm.jsx index e8bae9cf10c..88928a6518b 100644 --- a/src/components/Office/AddOrdersForm/AddOrdersForm.jsx +++ b/src/components/Office/AddOrdersForm/AddOrdersForm.jsx @@ -1,20 +1,37 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { Field, Formik } from 'formik'; import * as Yup from 'yup'; import { FormGroup, Label, Radio, Link as USWDSLink } from '@trussworks/react-uswds'; +import { faInfoCircle } from '@fortawesome/free-solid-svg-icons'; +import { isBooleanFlagEnabled } from '../../../utils/featureFlags'; +import { FEATURE_FLAG_KEYS } from '../../../shared/constants'; + +import styles from './AddOrdersForm.module.scss'; + +import ToolTip from 'shared/ToolTip/ToolTip'; import { DatePickerInput, DropdownInput, DutyLocationInput } from 'components/form/fields'; import { Form } from 'components/form/Form'; -import formStyles from 'styles/form.module.scss'; import SectionWrapper from 'components/Customer/SectionWrapper'; import { ORDERS_PAY_GRADE_OPTIONS } from 'constants/orders'; import { dropdownInputOptions } from 'utils/formatters'; import WizardNavigation from 'components/Customer/WizardNavigation/WizardNavigation'; import Callout from 'components/Callout'; import ConnectedFlashMessage from 'containers/FlashMessage/FlashMessage'; +import MaskedTextField from 'components/form/fields/MaskedTextField/MaskedTextField'; +import formStyles from 'styles/form.module.scss'; +let originMeta; +let newDutyMeta = ''; const AddOrdersForm = ({ onSubmit, ordersTypeOptions, initialValues, onBack, isSafetyMoveSelected }) => { const payGradeOptions = dropdownInputOptions(ORDERS_PAY_GRADE_OPTIONS); + const [currentDutyLocation, setCurrentDutyLocation] = useState(''); + const [newDutyLocation, setNewDutyLocation] = useState(''); + const [showAccompaniedTourField, setShowAccompaniedTourField] = useState(false); + const [showDependentAgeFields, setShowDependentAgeFields] = useState(false); + const [hasDependents, setHasDependents] = useState(false); + const [isOconusMove, setIsOconusMove] = useState(false); + const [enableUB, setEnableUB] = useState(false); const validationSchema = Yup.object().shape({ ordersType: Yup.mixed() @@ -30,12 +47,70 @@ const AddOrdersForm = ({ onSubmit, ordersTypeOptions, initialValues, onBack, isS originDutyLocation: Yup.object().nullable().required('Required'), newDutyLocation: Yup.object().nullable().required('Required'), grade: Yup.mixed().oneOf(Object.keys(ORDERS_PAY_GRADE_OPTIONS)).required('Required'), + accompaniedTour: showAccompaniedTourField + ? Yup.mixed().oneOf(['yes', 'no']).required('Required') + : Yup.string().notRequired(), + dependentsUnderTwelve: showDependentAgeFields + ? Yup.number().min(0).required('Required') + : Yup.number().notRequired(), + dependentsTwelveAndOver: showDependentAgeFields + ? Yup.number().min(0).required('Required') + : Yup.number().notRequired(), }); + useEffect(() => { + const checkUBFeatureFlag = async () => { + const enabled = await isBooleanFlagEnabled(FEATURE_FLAG_KEYS.UNACCOMPANIED_BAGGAGE); + if (enabled) { + setEnableUB(true); + } + }; + checkUBFeatureFlag(); + }, []); + + useEffect(() => { + // Check if either currentDutyLocation or newDutyLocation is OCONUS + if (currentDutyLocation?.address?.isOconus || newDutyLocation?.address?.isOconus) { + setIsOconusMove(true); + } else { + setIsOconusMove(false); + } + if (currentDutyLocation?.address && newDutyLocation?.address && enableUB) { + // Only if one of the duty locations is OCONUS should accompanied tour and dependent + // age fields display + if (isOconusMove && hasDependents) { + setShowAccompaniedTourField(true); + setShowDependentAgeFields(true); + } else { + setShowAccompaniedTourField(false); + setShowDependentAgeFields(false); + } + } + }, [currentDutyLocation, newDutyLocation, isOconusMove, hasDependents, enableUB]); + return ( - {({ values, isValid, isSubmitting, handleSubmit }) => { + {({ values, isValid, isSubmitting, handleSubmit, touched, setFieldValue }) => { const isRetirementOrSeparation = ['RETIREMENT', 'SEPARATION'].includes(values.ordersType); + if (!values.origin_duty_location && touched.origin_duty_location) originMeta = 'Required'; + else originMeta = null; + + if (!values.newDutyLocation && touched.newDutyLocation) newDutyMeta = 'Required'; + else newDutyMeta = null; + const handleHasDependentsChange = (e) => { + // Declare a duplicate local scope of the field value + // for the form to prevent state race conditions + const fieldValueHasDependents = e.target.value === 'yes'; + setHasDependents(e.target.value === 'yes'); + setFieldValue('hasDependents', fieldValueHasDependents ? 'yes' : 'no'); + if (fieldValueHasDependents && isOconusMove && enableUB) { + setShowAccompaniedTourField(true); + setShowDependentAgeFields(true); + } else { + setShowAccompaniedTourField(false); + setShowDependentAgeFields(false); + } + }; return ( @@ -51,36 +126,15 @@ const AddOrdersForm = ({ onSubmit, ordersTypeOptions, initialValues, onBack, isS /> - - -
    - - -
    -
    { + setCurrentDutyLocation(e); + }} + metaOverride={originMeta} required /> @@ -112,11 +166,132 @@ const AddOrdersForm = ({ onSubmit, ordersTypeOptions, initialValues, onBack, isS label="HOR, PLEAD or HOS" displayAddress={false} placeholder="Enter a city or ZIP" + metaOverride={newDutyMeta} + onDutyLocationChange={(e) => { + setNewDutyLocation(e); + }} /> ) : ( - + { + setNewDutyLocation(e); + }} + /> + )} + + + +
    + { + handleHasDependentsChange(e); + }} + /> + { + handleHasDependentsChange(e); + }} + /> +
    +
    + + {showAccompaniedTourField && ( + + +
    +
    + + +
    +
    + + +
    +
    +
    )} + + {showDependentAgeFields && ( + + + + + + )} +
    diff --git a/src/components/Office/AddOrdersForm/AddOrdersForm.module.scss b/src/components/Office/AddOrdersForm/AddOrdersForm.module.scss new file mode 100644 index 00000000000..ec15099903d --- /dev/null +++ b/src/components/Office/AddOrdersForm/AddOrdersForm.module.scss @@ -0,0 +1,8 @@ +@import 'shared/styles/_basics'; +@import 'shared/styles/colors.scss'; + +.radioWithToolTip { + display: flex; + align-items: center; + gap: 8px; + } \ No newline at end of file diff --git a/src/components/Office/AddOrdersForm/AddOrdersForm.test.jsx b/src/components/Office/AddOrdersForm/AddOrdersForm.test.jsx index 72a804963f0..91c83200e0c 100644 --- a/src/components/Office/AddOrdersForm/AddOrdersForm.test.jsx +++ b/src/components/Office/AddOrdersForm/AddOrdersForm.test.jsx @@ -6,27 +6,105 @@ import { Provider } from 'react-redux'; import AddOrdersForm from './AddOrdersForm'; import { dropdownInputOptions } from 'utils/formatters'; -import { ORDERS_PAY_GRADE_OPTIONS } from 'constants/orders'; +import { ORDERS_TYPE_OPTIONS } from 'constants/orders'; import { configureStore } from 'shared/store'; +import { isBooleanFlagEnabled } from 'utils/featureFlags'; -describe('CreateMoveCustomerInfo Component', () => { - const mockStore = configureStore({}); - const initialValues = { - ordersType: '', - issueDate: '', - reportByDate: '', - hasDependents: '', - newDutyLocation: '', - grade: '', - originDutyLocation: '', - }; - const testProps = { - initialValues, - ordersTypeOptions: dropdownInputOptions(ORDERS_PAY_GRADE_OPTIONS), - onSubmit: jest.fn(), - onBack: jest.fn(), - }; +jest.setTimeout(60000); + +jest.mock('components/LocationSearchBox/api', () => ({ + ShowAddress: jest.fn().mockImplementation(() => + Promise.resolve({ + city: 'Luke AFB', + country: 'United States', + id: 'fa51dab0-4553-4732-b843-1f33407f77bc', + postalCode: '85309', + state: 'AZ', + streetAddress1: 'n/a', + isOconus: true, + }), + ), + SearchDutyLocations: jest.fn().mockImplementation(() => + Promise.resolve([ + { + address: { + city: '', + id: '00000000-0000-0000-0000-000000000000', + postalCode: '', + state: '', + streetAddress1: '', + }, + address_id: '46c4640b-c35e-4293-a2f1-36c7b629f903', + affiliation: 'AIR_FORCE', + created_at: '2021-02-11T16:48:04.117Z', + id: '93f0755f-6f35-478b-9a75-35a69211da1c', + name: 'Altus AFB', + updated_at: '2021-02-11T16:48:04.117Z', + }, + { + address: { + city: 'Elmendorf AFB', + country: 'US', + id: 'fa51dab0-4553-4732-b843-1f33407f11bc', + postalCode: '78112', + state: 'AK', + streetAddress1: 'n/a', + isOconus: true, + }, + address_id: 'fa51dab0-4553-4732-b843-1f33407f11bc', + affiliation: 'AIR_FORCE', + created_at: '2021-02-11T16:48:04.117Z', + id: 'a8d6b33c-8370-4e92-8df2-356b8c9d0c1a', + name: 'Elmendorf AFB', + updated_at: '2021-02-11T16:48:04.117Z', + }, + { + address: { + city: 'Glendale Luke AFB', + country: 'United States', + id: 'fa51dab0-4553-4732-b843-1f33407f77bc', + postalCode: '85309', + state: 'AZ', + streetAddress1: 'n/a', + isOconus: true, + }, + address_id: '25be4d12-fe93-47f1-bbec-1db386dfa67f', + affiliation: 'AIR_FORCE', + created_at: '2021-02-11T16:48:04.117Z', + id: 'a8d6b33c-8370-4e92-8df2-356b8c9d0c1a', + name: 'Luke AFB', + updated_at: '2021-02-11T16:48:04.117Z', + }, + ]), + ), +})); + +jest.mock('utils/featureFlags', () => ({ + ...jest.requireActual('utils/featureFlags'), + isBooleanFlagEnabled: jest.fn().mockImplementation(() => Promise.resolve(false)), +})); +const mockStore = configureStore({}); +const initialValues = { + ordersType: '', + issueDate: '', + reportByDate: '', + hasDependents: '', + newDutyLocation: '', + grade: '', + originDutyLocation: '', + accompaniedTour: '', + dependentsUnderTwelve: '', + dependentsTwelveAndOver: '', +}; +const testProps = { + initialValues, + ordersTypeOptions: dropdownInputOptions(ORDERS_TYPE_OPTIONS), + onSubmit: jest.fn(), + onBack: jest.fn(), +}; + +describe('CreateMoveCustomerInfo Component', () => { it('renders the form inputs', async () => { render( @@ -65,7 +143,7 @@ describe('CreateMoveCustomerInfo Component', () => { await userEvent.click(submitBtn); const alerts = await findAllByRole('alert'); - expect(alerts.length).toBe(4); + expect(alerts.length).toBe(5); alerts.forEach((alert) => { expect(alert).toHaveTextContent('Required'); @@ -74,3 +152,39 @@ describe('CreateMoveCustomerInfo Component', () => { expect(testProps.onSubmit).not.toHaveBeenCalled(); }); }); + +describe('AddOrdersForm - OCONUS and Accompanied Tour Test', () => { + it('submits the form with OCONUS values and accompanied tour selection', async () => { + isBooleanFlagEnabled.mockResolvedValue(true); + + render( + + + , + ); + + await userEvent.selectOptions(await screen.findByLabelText(/Orders type/), 'PERMANENT_CHANGE_OF_STATION'); + await userEvent.type(screen.getByLabelText(/Orders date/), '08 Nov 2020'); + await userEvent.type(screen.getByLabelText(/Report by date/), '26 Nov 2020'); + await userEvent.click(screen.getByLabelText('No')); + await userEvent.selectOptions(screen.getByLabelText(/Pay grade/), ['E_5']); + + await userEvent.type(screen.getByLabelText(/Current duty location/), 'AFB'); + await userEvent.click(await screen.findByText(/Elmendorf/)); + + await userEvent.type(screen.getByLabelText(/New duty location/), 'AFB'); + await userEvent.click(await screen.findByText(/Luke/)); + + await userEvent.click(screen.getByTestId('hasDependentsYes')); + await userEvent.click(screen.getByTestId('isAnAccompaniedTourYes')); + await userEvent.type(screen.getByTestId('dependentsUnderTwelve'), '2'); + await userEvent.type(screen.getByTestId('dependentsTwelveAndOver'), '1'); + + const nextBtn = screen.getByRole('button', { name: 'Next' }); + await userEvent.click(nextBtn); + + await waitFor(() => { + expect(testProps.onSubmit).toHaveBeenCalled(); + }); + }); +}); diff --git a/src/components/Office/AllowancesDetailForm/AllowancesDetailForm.jsx b/src/components/Office/AllowancesDetailForm/AllowancesDetailForm.jsx index 8909d8ae06e..94d8bcc2dc3 100644 --- a/src/components/Office/AllowancesDetailForm/AllowancesDetailForm.jsx +++ b/src/components/Office/AllowancesDetailForm/AllowancesDetailForm.jsx @@ -1,6 +1,9 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; +import { isBooleanFlagEnabled } from '../../../utils/featureFlags'; +import { FEATURE_FLAG_KEYS } from '../../../shared/constants'; + import styles from './AllowancesDetailForm.module.scss'; import MaskedTextField from 'components/form/fields/MaskedTextField/MaskedTextField'; @@ -11,9 +14,68 @@ import { formatWeight } from 'utils/formatters'; import Hint from 'components/Hint'; const AllowancesDetailForm = ({ header, entitlements, branchOptions, formIsDisabled }) => { + const [enableUB, setEnableUB] = useState(false); + const renderOconusFields = !!( + entitlements?.accompaniedTour || + entitlements?.dependentsTwelveAndOver || + entitlements?.dependentsUnderTwelve + ); + useEffect(() => { + // Functional component version of "componentDidMount" + // By leaving the dependency array empty this will only run once + const checkUBFeatureFlag = async () => { + const enabled = await isBooleanFlagEnabled(FEATURE_FLAG_KEYS.UNACCOMPANIED_BAGGAGE); + if (enabled) { + setEnableUB(true); + } + }; + checkUBFeatureFlag(); + }, []); + return (
    {header &&

    {header}

    } + {enableUB && renderOconusFields && ( + <> + + + +
    + +
    + + )} + ({ ...jest.requireActual('formik'), useField: (field) => { @@ -74,6 +83,17 @@ const entitlements = { totalDependents: 2, }; +const entitlementOconusAdditions = { + accompaniedTour: true, + dependentsTwelveAndOver: 2, + dependentsUnderTwelve: 4, +}; + +jest.mock('../../../utils/featureFlags', () => ({ + ...jest.requireActual('../../../utils/featureFlags'), + isBooleanFlagEnabled: jest.fn().mockImplementation(() => Promise.resolve(false)), +})); + describe('AllowancesDetailForm', () => { it('renders the form', async () => { render( @@ -107,4 +127,35 @@ describe('AllowancesDetailForm', () => { expect(await screen.findByRole('heading', { level: 3 })).toHaveTextContent('Test Header'); }); + + it('does not render conditional oconus fields on load', async () => { + render( + + + , + ); + + expect(screen.queryByText('Accompanied tour')).not.toBeInTheDocument(); + expect(screen.queryByLabelText(/Number of dependents under the age of 12/)).not.toBeInTheDocument(); + expect(screen.queryByLabelText(/Number of dependents of the age 12 or over/)).not.toBeInTheDocument(); + }); + + it('does render conditional oconus fields when present in entitlement', async () => { + isBooleanFlagEnabled.mockImplementation(() => Promise.resolve(true)); + + await act(async () => { + render( + + + , + ); + }); + // Wait for state + await waitFor(() => expect(screen.queryByLabelText(/Accompanied tour/)).toBeInTheDocument()); + expect(screen.queryByLabelText(/Number of dependents under the age of 12/)).toBeInTheDocument(); + expect(screen.queryByLabelText(/Number of dependents of the age 12 or over/)).toBeInTheDocument(); + }); }); diff --git a/src/components/Office/DefinitionLists/AllowancesList.jsx b/src/components/Office/DefinitionLists/AllowancesList.jsx index 892204ab28a..3d5c1e850cc 100644 --- a/src/components/Office/DefinitionLists/AllowancesList.jsx +++ b/src/components/Office/DefinitionLists/AllowancesList.jsx @@ -1,7 +1,10 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import * as PropTypes from 'prop-types'; import classNames from 'classnames'; +import { isBooleanFlagEnabled } from '../../../utils/featureFlags'; +import { FEATURE_FLAG_KEYS, DEFAULT_EMPTY_VALUE } from '../../../shared/constants'; + import styles from './OfficeDefinitionLists.module.scss'; import descriptionListStyles from 'styles/descriptionList.module.scss'; @@ -9,10 +12,21 @@ import { formatWeight } from 'utils/formatters'; import { ORDERS_BRANCH_OPTIONS } from 'constants/orders'; const AllowancesList = ({ info, showVisualCues }) => { + const [enableUB, setEnableUB] = useState(false); const visualCuesStyle = classNames(descriptionListStyles.row, { [`${descriptionListStyles.rowWithVisualCue}`]: showVisualCues, }); + useEffect(() => { + const checkUBFeatureFlag = async () => { + const enabled = await isBooleanFlagEnabled(FEATURE_FLAG_KEYS.UNACCOMPANIED_BAGGAGE); + if (enabled) { + setEnableUB(true); + } + }; + checkUBFeatureFlag(); + }, []); + return (
    @@ -32,6 +46,42 @@ const AllowancesList = ({ info, showVisualCues }) => {
    Dependents
    {info.dependents ? 'Authorized' : 'Unauthorized'}
    + {/* Begin OCONUS fields */} + {/* As these fields are grouped together and only apply to OCONUS orders + They will all be NULL for CONUS orders. If one of these fields are present, + it will be safe to assume it is an OCONUS order. With this, if one field is present + we show all four. Otherwise, we show none */} + {/* Wrap in FF */} + {enableUB && + (info?.accompaniedTour || info?.dependentsTwelveAndOver > 0 || info?.dependentsUnderTwelve > 0) && ( + <> +
    +
    Accompanied tour
    +
    {info.accompaniedTour ? 'Yes' : 'No'}
    +
    +
    +
    Dependents under age 12
    +
    + {info.dependentsUnderTwelve ? info.dependentsUnderTwelve : DEFAULT_EMPTY_VALUE} +
    +
    +
    +
    Dependents over age 12
    +
    + {info.dependentsTwelveAndOver ? info.dependentsTwelveAndOver : DEFAULT_EMPTY_VALUE} +
    +
    + + )} + {enableUB && info?.ubAllowance >= 0 && ( +
    +
    Unaccompanied baggage allowance
    +
    + {info.ubAllowance ? formatWeight(info.ubAllowance) : DEFAULT_EMPTY_VALUE} +
    +
    + )} + {/* End OCONUS fields */}
    Pro-gear
    {formatWeight(info.progear)}
    @@ -70,6 +120,7 @@ AllowancesList.propTypes = { dependents: PropTypes.bool, requiredMedicalEquipmentWeight: PropTypes.number, organizationalClothingAndIndividualEquipment: PropTypes.bool, + ubAllowance: PropTypes.number, }).isRequired, showVisualCues: PropTypes.bool, }; diff --git a/src/components/Office/DefinitionLists/AllowancesList.stories.jsx b/src/components/Office/DefinitionLists/AllowancesList.stories.jsx index 6ee3694d760..44e3eda03e8 100644 --- a/src/components/Office/DefinitionLists/AllowancesList.stories.jsx +++ b/src/components/Office/DefinitionLists/AllowancesList.stories.jsx @@ -24,6 +24,7 @@ const info = { dependents: true, requiredMedicalEquipmentWeight: 1000, organizationalClothingAndIndividualEquipment: true, + ubAllowance: 400, }; export const Basic = () => ; diff --git a/src/components/Office/DefinitionLists/AllowancesList.test.jsx b/src/components/Office/DefinitionLists/AllowancesList.test.jsx index 3ce9df787a8..45c2e97f246 100644 --- a/src/components/Office/DefinitionLists/AllowancesList.test.jsx +++ b/src/components/Office/DefinitionLists/AllowancesList.test.jsx @@ -1,8 +1,11 @@ import React from 'react'; -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; +import { act } from 'react-dom/test-utils'; import AllowancesList from './AllowancesList'; +import { isBooleanFlagEnabled } from 'utils/featureFlags'; + const info = { branch: 'NAVY', grade: 'E_6', @@ -15,8 +18,78 @@ const info = { dependents: true, requiredMedicalEquipmentWeight: 1000, organizationalClothingAndIndividualEquipment: true, + ubAllowance: 400, +}; + +const initialValuesOconusAdditions = { + accompaniedTour: true, + dependentsTwelveAndOver: '2', + dependentsUnderTwelve: '4', + ubAllowance: 400, +}; + +const oconusInfo = { + accompaniedTour: true, + dependentsTwelveAndOver: 2, + dependentsUnderTwelve: 4, + ubAllowance: 400, }; +jest.mock('formik', () => ({ + ...jest.requireActual('formik'), + useField: (field) => { + const initialValues = { + accompaniedTour: true, + dependentsTwelveAndOver: '2', + dependentsUnderTwelve: '4', + ubAllowance: '400', + }; + + switch (field.type) { + case 'checkbox': { + return [ + { + name: field.name, + value: !!initialValues[field.name], + checked: !!initialValues[field.name], + onChange: jest.fn(), + onBlur: jest.fn(), + }, + { + touched: false, + }, + { + setValue: jest.fn(), + setTouched: jest.fn(), + }, + ]; + } + + default: { + return [ + { + value: initialValues[field.name], + }, + { + touched: false, + }, + { + setValue: jest.fn(), + setTouched: jest.fn(), + }, + ]; + } + } + }, +})); + +const { Formik } = jest.requireActual('formik'); + +jest.mock('../../../utils/featureFlags', () => ({ + ...jest.requireActual('../../../utils/featureFlags'), + isBooleanFlagEnabled: jest.fn().mockImplementation(() => Promise.resolve(false)), +})); + describe('AllowancesList', () => { it('renders formatted branch', () => { render(); @@ -77,4 +150,29 @@ describe('AllowancesList', () => { expect(screen.getByText('Required medical equipment').parentElement.className).toContain('rowWithVisualCue'); expect(screen.getByText('OCIE').parentElement.className).toContain('rowWithVisualCue'); }); + + it('does not render oconus fields by default', async () => { + render(); + expect(screen.queryByText('Accompanied tour')).not.toBeInTheDocument(); + expect(screen.queryByLabelText(/Number of dependents under the age of 12/)).not.toBeInTheDocument(); + expect(screen.queryByLabelText(/Number of dependents of the age 12 or over/)).not.toBeInTheDocument(); + expect(screen.queryByText('Unaccompanied baggage allowance')).not.toBeInTheDocument(); + }); + + it('does render oconus fields when present', async () => { + isBooleanFlagEnabled.mockImplementation(() => Promise.resolve(true)); + await act(async () => { + render( + + + , + ); + }); + // Wait for state + await waitFor(() => expect(screen.getByTestId('ordersAccompaniedTour')).toBeInTheDocument()); + expect(screen.getByTestId('ordersDependentsUnderTwelve')).toBeInTheDocument(); + expect(screen.getByTestId('ordersDependentsTwelveAndOver')).toBeInTheDocument(); + expect(screen.getByTestId('unaccompaniedBaggageAllowance')).toBeInTheDocument(); + expect(screen.getByTestId('unaccompaniedBaggageAllowance').textContent).toEqual('400 lbs'); + }); }); diff --git a/src/components/Office/DefinitionLists/NTSRShipmentInfoList.jsx b/src/components/Office/DefinitionLists/NTSRShipmentInfoList.jsx index 648ba0a56e7..ffecea1983c 100644 --- a/src/components/Office/DefinitionLists/NTSRShipmentInfoList.jsx +++ b/src/components/Office/DefinitionLists/NTSRShipmentInfoList.jsx @@ -211,7 +211,7 @@ const NTSRShipmentInfoList = ({ const destinationAddressElementFlags = getDisplayFlags('destinationAddress'); const destinationAddressElement = (
    -
    Delivery address
    +
    Delivery Address
    {deliveryAddressUpdate?.status === ADDRESS_UPDATE_STATUS.REQUESTED ? 'Review required' @@ -231,7 +231,7 @@ const NTSRShipmentInfoList = ({ const secondaryDeliveryAddressElementFlags = getDisplayFlags('secondaryDeliveryAddress'); const secondaryDeliveryAddressElement = (
    -
    Second delivery address
    +
    Second Delivery Address
    {secondaryDeliveryAddress ? formatAddress(secondaryDeliveryAddress) : '—'}
    @@ -241,7 +241,7 @@ const NTSRShipmentInfoList = ({ const tertiaryDeliveryAddressElementFlags = getDisplayFlags('tertiaryDeliveryAddress'); const tertiaryDeliveryAddressElement = (
    -
    Third delivery address
    +
    Third Delivery Address
    {tertiaryDeliveryAddress ? formatAddress(tertiaryDeliveryAddress) : '—'}
    diff --git a/src/components/Office/GblocSwitcher/GblocSwitcher.jsx b/src/components/Office/GblocSwitcher/GblocSwitcher.jsx index 2d99ba5d32d..bad2441a5d9 100644 --- a/src/components/Office/GblocSwitcher/GblocSwitcher.jsx +++ b/src/components/Office/GblocSwitcher/GblocSwitcher.jsx @@ -1,19 +1,29 @@ import React, { useContext, useEffect, useState } from 'react'; +import { connect } from 'react-redux'; import PropTypes from 'prop-types'; import styles from './GblocSwitcher.module.scss'; import ButtonDropdown from 'components/ButtonDropdown/ButtonDropdown'; import { useListGBLOCsQueries } from 'hooks/queries'; +import { selectLoggedInUser } from 'store/entities/selectors'; import SelectedGblocContext, { SELECTED_GBLOC_SESSION_STORAGE_KEY, } from 'components/Office/GblocSwitcher/SelectedGblocContext'; +import { roleTypes } from 'constants/userRoles'; -const GBLOCSwitcher = ({ officeUsersDefaultGbloc, ariaLabel }) => { +const GBLOCSwitcher = ({ officeUser, activeRole, ariaLabel }) => { const [isInitialPageLoad, setIsInitialPageLoad] = useState(true); const { selectedGbloc, handleGblocChange } = useContext(SelectedGblocContext); - const { result: gblocs } = useListGBLOCsQueries(); + let { result: gblocs } = useListGBLOCsQueries(); + if (activeRole !== roleTypes.HQ) { + gblocs = officeUser?.transportation_office_assignments.map((toa) => { + return toa?.transportationOffice?.gbloc; + }); + } + + const officeUsersDefaultGbloc = officeUser.transportation_office.gbloc; if (gblocs.indexOf(officeUsersDefaultGbloc) === -1) { gblocs.push(officeUsersDefaultGbloc); } @@ -48,12 +58,22 @@ const GBLOCSwitcher = ({ officeUsersDefaultGbloc, ariaLabel }) => { }; GBLOCSwitcher.defaultProps = { - ariaLabel: '', + ariaLabel: 'Switch to a different GBLOC', }; GBLOCSwitcher.propTypes = { - officeUsersDefaultGbloc: PropTypes.string.isRequired, + officeUser: PropTypes.object.isRequired, + activeRole: PropTypes.string.isRequired, ariaLabel: PropTypes.string, }; -export default GBLOCSwitcher; +const mapStateToProps = (state) => { + const user = selectLoggedInUser(state); + + return { + officeUser: user?.office_user || {}, + activeRole: state.auth.activeRole, + }; +}; + +export default connect(mapStateToProps)(GBLOCSwitcher); diff --git a/src/components/Office/PPM/EditNetWeights/EditPPMNetWeight.jsx b/src/components/Office/PPM/EditNetWeights/EditPPMNetWeight.jsx index f930c8e2acc..8cd593f73cb 100644 --- a/src/components/Office/PPM/EditNetWeights/EditPPMNetWeight.jsx +++ b/src/components/Office/PPM/EditNetWeights/EditPPMNetWeight.jsx @@ -1,23 +1,14 @@ -import React, { useState } from 'react'; -import { Formik } from 'formik'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import * as Yup from 'yup'; +import React from 'react'; import { PropTypes, number } from 'prop-types'; -import { Button, Fieldset, Label, Textarea } from '@trussworks/react-uswds'; import styles from './EditPPMNetWeight.module.scss'; -import MaskedTextField from 'components/form/fields/MaskedTextField/MaskedTextField'; -import { ErrorMessage } from 'components/form/ErrorMessage'; import { formatWeight } from 'utils/formatters'; import { calculateWeightTicketWeightDifference, getWeightTicketNetWeight } from 'utils/shipmentWeights'; import { calculateWeightRequested } from 'hooks/custom'; -import { patchWeightTicket } from 'services/ghcApi'; import { ShipmentShape, WeightTicketShape } from 'types/shipment'; -import { DOCUMENTS } from 'constants/queryKeys'; // Labels & constants - const CALCULATION_TYPE = { NET_WEIGHT: 'NET_WEIGHT', EXCESS_WEIGHT: 'EXCESS_WEIGHT', @@ -48,15 +39,6 @@ const FlexContainer = ({ children, className }) => { ); }; -// Form Error Indicator -const ErrorIndicator = ({ children, hasErrors }) => { - return ( -
    - {children} -
    - ); -}; - const WeightCalculationHint = ({ type, firstValue, secondValue, thirdValue }) => { const { firstLabel, secondLabel, thirdLabel } = weightLabels[type]; return ( @@ -91,111 +73,7 @@ const WeightCalculationHint = ({ type, firstValue, secondValue, thirdValue }) => ); }; -const EditPPMNetWeightForm = ({ onCancel, initialValues, weightTicket }) => { - const validationSchema = Yup.object({ - adjustedNetWeight: Yup.number() - .min(0, 'Net weight must be 0 lbs or greater') - .lessThan(weightTicket.fullWeight, 'Net weight must be less than or equal to the full weight') - .required('Required'), - netWeightRemarks: Yup.string().nullable().required('Required'), - }); - const queryClient = useQueryClient(); - - const { mutate: patchWeightTicketMutation } = useMutation({ - mutationFn: patchWeightTicket, - onSuccess: () => { - queryClient.invalidateQueries({ - queryKey: [DOCUMENTS], - }); - }, - }); - - /** - * @const onSubmit - * @description This function is used to submit the mini form represented by the EditPPMNetWeightForm. - * @param {Object} formValues - The values that are returned from the EditPPMNetWeightForm component on click. - * @param {string} formValues.adjustedNetWeight - The adjusted net weight as a string. This value needs to be parsed into an integer before mutation. - * @param {string} formValues.netWeightRemarks - The net weight remarks. - * */ - const onSubmit = (formValues /* , actions */) => { - const payload = { - adjustedNetWeight: parseInt(formValues.adjustedNetWeight, 10), - netWeightRemarks: formValues.netWeightRemarks, - }; - patchWeightTicketMutation( - { - ppmShipmentId: weightTicket.ppmShipmentId, - weightTicketId: weightTicket.id, - payload, - eTag: weightTicket.eTag, - }, - { - onSuccess: () => { - onCancel(); - }, - }, - ); - }; - - return ( - - {({ handleChange, handleSubmit, values, isValid, errors, touched, setTouched }) => ( -
    -
    - - - - {errors.netWeightRemarks} - - -