diff --git a/.envrc b/.envrc index c5e72d09ad2..32fd448864a 100644 --- a/.envrc +++ b/.envrc @@ -415,7 +415,7 @@ if [ ! -r .nix-disable ] && has nix-env; then # add the NIX_PROFILE bin path so that everything we just installed # is available on the path - PATH_add ${NIX_PROFILE}/bin + PATH_add "${NIX_PROFILE}"/bin # Add the node binaries to our path PATH_add ./node_modules/.bin # nix is immutable, so we need to specify a path for local changes, e.g. diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index dcf1897da35..f83d084326b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -8,6 +8,7 @@ variables: #Docker config DOCKER_AUTH_CONFIG: "{\"auths\":{\"https://index.docker.io/v1/\":{\"auth\":\"$CI_REGISTRY_USER:$CI_REGISTRY_PASSWORD\"}}}" + #hard code sha as newer version of debian is needed for pre-test DOCKER_APP_IMAGE: milmove01/transcom-docker:milmove-app DOCKER_BASE_IMAGE: milmove01/transcom-docker:base DOCKERHUB_USERNAME: DOCKERHUB_USERNAME @@ -24,7 +25,7 @@ variables: #CIRCLE_TOKEN: "$GITLAB_API_TOKEN" # GitLab API token for querying pipelines CIRCLE_BUILD_NUM: "$CI_PIPELINE_ID" - GOPATH: "$CI_PROJECT_DIR/go" + GOPATH: "/home/transcom/go" #Go path on the app image GOLANGCI_LINT_CONCURRENCY: "4" GOLANGCI_LINT_VERBOSE: "-v" @@ -63,7 +64,7 @@ stages: #set safe directory and path .setup_milmove_env: &setup_milmove_env - git config --global --add safe.directory /builds/milmove/mymove - - export PATH=${PATH}:${GOPATH}/bin:~/transcom/mymove/builds/milmove/mymove:/builds/milmove/mymove/scripts + - export PATH=${PATH}:${GOPATH}/bin:~/transcom/mymove/builds/milmove/mymove:/builds/milmove/mymove/scripts:/builds/milmove/mymove/bin - export REACT_APP_ERROR_LOGGING=otel .announce_failure: &announce_failure @@ -151,7 +152,7 @@ stages: #build off prd variables - export ECR_REPOSITORY_URI=${PRD_ACCOUNT_ID}.dkr.ecr.${PRD_REGION}.amazonaws.com - export APP_DOCKER_FILE=Dockerfile - - export TASK_DOCKER_FILE=Dockerfile + - export TASK_DOCKER_FILE=Dockerfile.tasks #TODO: update exp to prod - export APP_ENVIRONMENT=prd @@ -254,6 +255,7 @@ stages: export OKTA_API_KEY=notrealapikey8675309 export OKTA_OFFICE_GROUP_ID=notrealgroupId export OKTA_CUSTOMER_GROUP_ID=notrealcustomergroupId + export IWS_RBS_HOST=pkict.dmdc.osd.mil .setup_env_intergration_mtls: &setup_env_intergration_mtls - | @@ -389,6 +391,89 @@ pre_deps_golang: # - $GOPATH/pkg/mod # - /builds/milmove/mymove/bin # Ensure this path is correct and writable. # Optionally, you can define an after_script for cleanup or notifications. +golang_lint: + stage: pre_checks + interruptible: true + tags: + - $DOCKER_RUNNER_TAG + image: golangci/golangci-lint:latest # Refer to https://hub.docker.com/r/golangci/golangci-lint + script: + - golangci-lint run --print-issued-lines=false --timeout=25m --out-format code-climate:gl-code-quality-report.json,line-number + artifacts: + reports: + codequality: gl-code-quality-report.json + paths: + - gl-code-quality-report.json + when: always + allow_failure: true + +# WIP but failing and will need to get back to see if this is a viable option for go test coverage +# golang_coverage: +# stage: pre_checks +# interruptible: true +# tags: +# - $DOCKER_RUNNER_TAG +# image: $DOCKER_APP_IMAGE +# services: +# - name: docker:dind +# alias: docker +# - name: $postgres +# - name: $redis +# before_script: +# - *setup_milmove_env +# variables: +# KUBERNETES_CPU_REQUEST: "4" +# KUBERNETES_MEMORY_REQUEST: "8Gi" +# KUBERNETES_MEMORY_LIMIT: "8Gi" +# DOCKER_HOST: "tcp://docker-backend.gitlab-runner.svc.cluster.local:2375" +# DOCKER_TLS_CERTDIR: "" +# APPLICATION: app +# # 8 since this runs on xlarge with 8 CPUs +# GOTEST_PARALLEL: 8 +# DB_PASSWORD: mysecretpassword +# DB_USER_LOW_PRIV: crud +# DB_PASSWORD_LOW_PRIV: mysecretpassword +# DB_USER: postgres +# DB_HOST: localhost +# DB_PORT_TEST: 5432 +# DB_PORT: 5432 +# DB_NAME: test_db +# DB_NAME_TEST: test_db +# DTOD_USE_MOCK: 'true' +# MIGRATION_MANIFEST: '/builds/milmove/mymove/migrations/app/migrations_manifest.txt' +# MIGRATION_PATH: 'file:///builds/milmove/mymove/migrations/app/schema;file:///builds/milmove/mymove/migrations/app/secure' +# EIA_KEY: db2522a43820268a41a802a16ae9fd26 # dummy key generated with openssl rand -hex 16 +# ENV: test +# ENVIRONMENT: test +# SERVER_REPORT: 1 +# COVERAGE: 1 +# SERVE_API_INTERNAL: 'true' +# OKTA_CUSTOMER_CLIENT_ID: 1q2w3e4r5t6y7u8i9o +# OKTA_ADMIN_CLIENT_ID: AQ1SW2DE3FR4G5 +# OKTA_OFFICE_CLIENT_ID: 9f9f9s8s90gig9 +# OKTA_API_KEY: notrealapikey8675309 +# OKTA_OFFICE_GROUP_ID: notrealgroupId +# OKTA_CUSTOMER_GROUP_ID: notrealcustomergroupId +# POSTGRES_DB: test_db #for postgres container +# POSTGRES_USER: postgres +# POSTGRES_PASSWORD: mysecretpassword +# POSTGRES_HOST_AUTH_METHOD: trust +# DPS_AUTH_SECRET_KEY: placeholder +# CSRF_AUTH_KEY: d096fd8529eefaa46497849d11d2ff2e979ddfaed1aff058524ada9bceadd67c +# IWS_RBS_ENABLED: 0 +# IWS_RBS_HOST: "pkict.dmdc.osd.mil" +# script: +# - go test ./... -coverprofile=coverage.txt -covermode count +# - go get github.com/boumenot/gocover-cobertura +# - go run github.com/boumenot/gocover-cobertura < coverage.txt > coverage.xml +# allow_failure: true +# after_script: +# - *announce_failure +# artifacts: +# reports: +# coverage_report: +# coverage_format: cobertura +# path: /builds/milmove/mymove/coverage.xml pre_deps_yarn: stage: pre_checks @@ -639,36 +724,36 @@ pre_test: [ -d ~/transcom/mymove/spectral ] && cp -r ~/transcom/mymove/spectral /tmp/spectral_baseline || echo "Skipping saving baseline" - rm -rf ~/transcom/mymove/spectral - *install_yarn + # this is so we can avoid go mod downloading and resulting in an error on a false positive + - ./scripts/pre-commit-go-mod || exit 0 - echo "Run pre-commit tests without golangci-lint, eslint, or prettier" - - SKIP=golangci-lint,eslint,prettier,ato-go-linter,gomod,appcontext-linter pre-commit run --all-files - - | - echo "Run pre-commit tests with ato-go-linter only" - pre-commit run -v --all-files ato-go-linter - - | - echo "Run pre-commit tests with gomod only" - pre-commit run -v --all-files gomod,appcontext-linter - - | - echo "Run pre-commit tests with appcontext-linter only" - pre-commit run -v --all-files appcontext-linter + - SKIP=golangci-lint,eslint,prettier pre-commit run --all-files - echo "Run pre-commit tests with golangci-lint only" - | - echo 'export GOLANGCI_LINT_CONCURRENCY=4' >> $BASH_ENV - echo 'export GOLANGCI_LINT_VERBOSE=-v' >> $BASH_ENV - source $BASH_ENV + export GOLANGCI_LINT_CONCURRENCY=4 + export GOLANGCI_LINT_VERBOSE=-v mkdir -p tmp/test-results/pretest - pre-commit run -v --all-files golangci-lint | tee tmp/test-results/pretest/golangci-lint.out - - echo "Run prettier, eslint, danger checks" + # can this be removed in favor of golang_lint? + - pre-commit run -v --all-files golangci-lint | tee tmp/test-results/pretest/golangci-lint.out + - echo "Run prettier, eslint, danger checks" - yarn prettier-ci - yarn lint - yarn danger ci --failOnErrors - - echo "Run spectral linter on all files" + - echo "Run spectral linter on all files" - ./scripts/ensure-spectral-lint /tmp/spectral_baseline spectral - - ./scripts/pre-commit-go-mod || exit 0 allow_failure: true after_script: - *announce_failure - rules: - - *check_server_ignore_branch + artifacts: + reports: + codequality: tmp/test-results/pretest/golangci-lint.out + paths: + - tmp/test-results/pretest/golangci-lint.out #remove if golang_lint works + - tmp/spectral_baseline/*.json #what do we need to store for review? + - spectral/*.json #what do we need to store for review? + when: always + # rules: + # - *check_server_ignore_branch server_test: stage: test @@ -698,7 +783,7 @@ server_test: DB_PASSWORD_LOW_PRIV: mysecretpassword DB_USER: postgres DB_HOST: localhost - DB_PORT_TEST: 5433 + DB_PORT_TEST: 5432 DB_PORT: 5432 DB_NAME: test_db DB_NAME_TEST: test_db @@ -717,18 +802,22 @@ server_test: OKTA_API_KEY: notrealapikey8675309 OKTA_OFFICE_GROUP_ID: notrealgroupId OKTA_CUSTOMER_GROUP_ID: notrealcustomergroupId + POSTGRES_DB: test_db #for postgres container + POSTGRES_USER: postgres + POSTGRES_PASSWORD: mysecretpassword + POSTGRES_HOST_AUTH_METHOD: trust + DPS_AUTH_SECRET_KEY: placeholder + CSRF_AUTH_KEY: d096fd8529eefaa46497849d11d2ff2e979ddfaed1aff058524ada9bceadd67c + IWS_RBS_ENABLED: 0 + IWS_RBS_HOST: "pkict.dmdc.osd.mil" script: - - psql --version - for i in $(seq 1 5); do go mod download && break || s=$? && sleep 5; done; (exit $s) - scripts/check-generated-code go.sum - make bin/swagger - - echo "server test -- TODO Add steps need to potentially pass job id to file and persist" + - echo "server test -- build gotestsum and run scripts for report" - make -j 2 bin/milmove bin/gotestsum - make server_test for app - # - go install gotest.tools/gotestsum@latest - # - go mod tidy - #- bin/gotestsum --junitfile server_test_report.xml --format server_test - allow_failure: true + allow_failure: true #leaving true until 5 tests failing tests are working artifacts: paths: - /builds/milmove/mymove/bin/gotestsum @@ -738,8 +827,9 @@ server_test: junit: /builds/milmove/mymove/tmp/test-results/gotest/app/go-test-report.xml after_script: - *announce_failure - rules: - - *check_server_ignore_branch + # we want to make this run on every branch bc webhooks don't exist currently + # rules: + # - *check_server_ignore_branch server_test_coverage: stage: test @@ -806,8 +896,9 @@ client_test: - /builds/milmove/mymove/jest-junit-reports after_script: - *announce_failure - rules: - - *check_client_ignore_branch + # we want to make this run on every branch bc webhooks don't exist currently + # rules: + # - *check_client_ignore_branch client_test_coverage: stage: test diff --git a/Dockerfile b/Dockerfile index 18f55ac4c63..293ea88db39 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM debian:stable AS build-env +FROM harbor.csde.caci.com/docker.io/debian:stable AS build-env COPY config/tls/dod-wcf-root-ca-1.pem /usr/local/share/ca-certificates/dod-wcf-root-ca-1.pem.crt COPY config/tls/dod-wcf-intermediate-ca-1.pem /usr/local/share/ca-certificates/dod-wcf-intermediate-ca-1.pem.crt @@ -8,7 +8,7 @@ RUN apt-get install -y ca-certificates --no-install-recommends RUN update-ca-certificates # hadolint ignore=DL3007 -FROM gcr.io/distroless/base-debian11@sha256:ac69aa622ea5dcbca0803ca877d47d069f51bd4282d5c96977e0390d7d256455 +FROM gcr.io/distroless/base-debian12@sha256:74ddbf52d93fafbdd21b399271b0b4aac1babf8fa98cab59e5692e01169a1348 COPY --from=build-env /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt COPY bin/rds-ca-rsa4096-g1.pem /bin/rds-ca-rsa4096-g1.pem diff --git a/Dockerfile.dp3 b/Dockerfile.dp3 index bd32b33446e..c2cafd6bd07 100644 --- a/Dockerfile.dp3 +++ b/Dockerfile.dp3 @@ -1,7 +1,7 @@ -FROM debian:stable AS build-env +FROM harbor.csde.caci.com/docker.io/debian:stable AS build-env # hadolint ignore=DL3007 -FROM gcr.io/distroless/base-debian11@sha256:ac69aa622ea5dcbca0803ca877d47d069f51bd4282d5c96977e0390d7d256455 +FROM gcr.io/distroless/base-debian12@sha256:74ddbf52d93fafbdd21b399271b0b4aac1babf8fa98cab59e5692e01169a1348 #AWS GovCloud RDS cert COPY bin/rds-ca-rsa4096-g1.pem /bin/rds-ca-rsa4096-g1.pem @@ -32,4 +32,4 @@ ENTRYPOINT ["/bin/milmove"] CMD ["serve", "--logging-level=debug"] -EXPOSE 8080 +EXPOSE 8080 \ No newline at end of file diff --git a/Dockerfile.local b/Dockerfile.local index bcbcd46c6bc..225bb958ab1 100644 --- a/Dockerfile.local +++ b/Dockerfile.local @@ -20,7 +20,7 @@ RUN rm -f bin/milmove && make bin/milmove ######### # hadolint ignore=DL3007 -FROM gcr.io/distroless/base-debian11@sha256:ac69aa622ea5dcbca0803ca877d47d069f51bd4282d5c96977e0390d7d256455 +FROM gcr.io/distroless/base-debian12@sha256:74ddbf52d93fafbdd21b399271b0b4aac1babf8fa98cab59e5692e01169a1348 COPY --from=builder --chown=root:root /home/circleci/project/bin/rds-ca-rsa4096-g1.pem /bin/rds-ca-rsa4096-g1.pem COPY --from=builder --chown=root:root /home/circleci/project/bin/rds-ca-2019-root.pem /bin/rds-ca-2019-root.pem diff --git a/Dockerfile.migrations b/Dockerfile.migrations index 6f8284da332..5d4956df394 100644 --- a/Dockerfile.migrations +++ b/Dockerfile.migrations @@ -1,4 +1,4 @@ -FROM debian:stable AS build-env +FROM harbor.csde.caci.com/docker.io/debian:stable AS build-env COPY config/tls/dod-wcf-root-ca-1.pem /usr/local/share/ca-certificates/dod-wcf-root-ca-1.pem.crt COPY config/tls/dod-wcf-intermediate-ca-1.pem /usr/local/share/ca-certificates/dod-wcf-intermediate-ca-1.pem.crt @@ -9,7 +9,7 @@ RUN update-ca-certificates # hadolint ignore=DL3007 -FROM gcr.io/distroless/base-debian11@sha256:ac69aa622ea5dcbca0803ca877d47d069f51bd4282d5c96977e0390d7d256455 +FROM gcr.io/distroless/base-debian12@sha256:74ddbf52d93fafbdd21b399271b0b4aac1babf8fa98cab59e5692e01169a1348 COPY config/tls/dod-wcf-root-ca-1.pem /usr/local/share/ca-certificates/dod-wcf-root-ca-1.pem.crt COPY config/tls/dod-wcf-intermediate-ca-1.pem /usr/local/share/ca-certificates/dod-wcf-intermediate-ca-1.pem.crt diff --git a/Dockerfile.reviewapp b/Dockerfile.reviewapp index 2949f635093..beeb0112195 100644 --- a/Dockerfile.reviewapp +++ b/Dockerfile.reviewapp @@ -108,7 +108,7 @@ RUN set -x \ ######### # hadolint ignore=DL3007 -FROM gcr.io/distroless/base-debian11@sha256:ac69aa622ea5dcbca0803ca877d47d069f51bd4282d5c96977e0390d7d256455 as milmove +FROM gcr.io/distroless/base-debian12@sha256:74ddbf52d93fafbdd21b399271b0b4aac1babf8fa98cab59e5692e01169a1348 as milmove COPY --from=server_builder /build/bin/rds-ca-2019-root.pem /bin/rds-ca-2019-root.pem COPY --from=server_builder /build/bin/milmove /bin/milmove diff --git a/Dockerfile.tasks b/Dockerfile.tasks index 1e8034642a8..d41ee54e808 100644 --- a/Dockerfile.tasks +++ b/Dockerfile.tasks @@ -1,4 +1,4 @@ -FROM debian:stable AS build-env +FROM harbor.csde.caci.com/docker.io/debian:stable AS build-env COPY config/tls/dod-wcf-root-ca-1.pem /usr/local/share/ca-certificates/dod-wcf-root-ca-1.pem.crt COPY config/tls/dod-wcf-intermediate-ca-1.pem /usr/local/share/ca-certificates/dod-wcf-intermediate-ca-1.pem.crt @@ -8,7 +8,7 @@ RUN apt-get install -y ca-certificates --no-install-recommends RUN update-ca-certificates # hadolint ignore=DL3007 -FROM gcr.io/distroless/base-debian11@sha256:ac69aa622ea5dcbca0803ca877d47d069f51bd4282d5c96977e0390d7d256455 +FROM gcr.io/distroless/base-debian12@sha256:74ddbf52d93fafbdd21b399271b0b4aac1babf8fa98cab59e5692e01169a1348 COPY --from=build-env /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt COPY config/tls/milmove-cert-bundle.p7b /config/tls/milmove-cert-bundle.p7b diff --git a/Dockerfile.tasks_dp3 b/Dockerfile.tasks_dp3 index b305b972913..72f71bdb971 100644 --- a/Dockerfile.tasks_dp3 +++ b/Dockerfile.tasks_dp3 @@ -1,5 +1,5 @@ # hadolint ignore=DL3007 -FROM gcr.io/distroless/base-debian11@sha256:ac69aa622ea5dcbca0803ca877d47d069f51bd4282d5c96977e0390d7d256455 +FROM gcr.io/distroless/base-debian12@sha256:74ddbf52d93fafbdd21b399271b0b4aac1babf8fa98cab59e5692e01169a1348 # Demo Environment Certs COPY config/tls/api.demo.dp3.us.chain.der.p7b /config/tls/api.demo.dp3.us.chain.der.p7b diff --git a/Dockerfile.tasks_local b/Dockerfile.tasks_local index 5e5243cd779..bb67bf94872 100644 --- a/Dockerfile.tasks_local +++ b/Dockerfile.tasks_local @@ -19,7 +19,7 @@ RUN rm -f bin/milmove-tasks && make bin/milmove-tasks ######### # hadolint ignore=DL3007 -FROM gcr.io/distroless/base-debian11@sha256:ac69aa622ea5dcbca0803ca877d47d069f51bd4282d5c96977e0390d7d256455 +FROM gcr.io/distroless/base-debian12@sha256:74ddbf52d93fafbdd21b399271b0b4aac1babf8fa98cab59e5692e01169a1348 COPY --from=builder --chown=root:root /home/circleci/project/config/tls/milmove-cert-bundle.p7b /config/tls/milmove-cert-bundle.p7b COPY --from=builder --chown=root:root /home/circleci/project/bin/rds-ca-2019-root.pem /bin/rds-ca-2019-root.pem diff --git a/Makefile b/Makefile index 6017c9b15f5..e2f29bde50e 100644 --- a/Makefile +++ b/Makefile @@ -32,15 +32,6 @@ DB_PORT_DEPLOYED_MIGRATIONS=5434 DB_PORT_DOCKER=5432 REDIS_PORT=6379 REDIS_PORT_DOCKER=6379 -ifdef CIRCLECI - DB_PORT_DEV=5432 - DB_PORT_TEST=5432 - UNAME_S := $(shell uname -s) - ifeq ($(UNAME_S),Linux) - LDFLAGS=-linkmode external -extldflags -static - endif -endif - ifdef GITLAB DB_PORT_DEV=5432 DB_PORT_TEST=5432 @@ -96,10 +87,10 @@ prereqs: ## Check that pre-requirements are installed, includes dependency scrip .PHONY: check_hosts check_hosts: .check_hosts.stamp ## Check that hosts are in the /etc/hosts file .check_hosts.stamp: scripts/check-hosts-file -ifndef CIRCLECI +ifndef GITLAB scripts/check-hosts-file else - @echo "Not checking hosts on CircleCI." + @echo "Not checking hosts on GitLab." endif touch .check_hosts.stamp @@ -112,10 +103,10 @@ check_go_version: .check_go_version.stamp ## Check that the correct Golang versi .PHONY: check_gopath check_gopath: .check_gopath.stamp ## Check that $GOPATH exists in $PATH .check_gopath.stamp: scripts/check-gopath go.sum # Make sure any go binaries rebuild if version possibly changes -ifndef CIRCLECI +ifndef GITLAB scripts/check-gopath else - @echo "No need to check go path on CircleCI." + @echo "No need to check go path on GitLab." endif touch .check_gopath.stamp @@ -344,7 +335,7 @@ swagger_generate: .swagger_build.stamp ## Check that the build files haven't bee # if API docs have changed, swagger regeneration will capture those # changes. On Circle CI, or if the user has set # SWAGGER_AUTOREBUILD, rebuild automatically without asking -ifdef CIRCLECI +ifdef GITLAB SWAGGER_AUTOREBUILD=1 endif SWAGGER_FILES = $(shell find swagger swagger-def -type f) @@ -476,16 +467,16 @@ server_test_coverage: db_test_reset db_test_migrate redis_reset server_test_cove .PHONY: redis_pull redis_pull: ## Pull redis image -ifdef CIRCLECI - @echo "Relying on CircleCI to setup redis." +ifdef GITLAB + @echo "Relying on GitLab to setup redis." else docker pull $(REDIS_DOCKER_CONTAINER_IMAGE) endif .PHONY: redis_destroy redis_destroy: ## Destroy Redis -ifdef CIRCLECI - @echo "Relying on CircleCI to setup redis." +ifdef GITLAB + @echo "Relying on GitLab to setup redis." else @echo "Destroying the ${REDIS_DOCKER_CONTAINER} docker redis container..." docker rm -f $(REDIS_DOCKER_CONTAINER) || echo "No Redis container" @@ -493,8 +484,8 @@ endif .PHONY: redis_run redis_run: redis_pull ## Run Redis -ifdef CIRCLECI - @echo "Relying on CircleCI to setup redis." +ifdef GITLAB + @echo "Relying on GitLab to setup redis." else @echo "Stopping the Redis brew service in case it's running..." brew services stop redis 2> /dev/null || true @@ -522,17 +513,17 @@ db_pull: ## Pull db image .PHONY: db_dev_destroy db_dev_destroy: ## Destroy Dev DB -ifndef CIRCLECI +ifndef GITLAB @echo "Destroying the ${DB_DOCKER_CONTAINER_DEV} docker database container..." docker rm -f $(DB_DOCKER_CONTAINER_DEV) || echo "No database container" rm -fr mnt/db_dev # delete mount directory if exists else - @echo "Relying on CircleCI's database setup to destroy the DB." + @echo "Relying on GitLab's database setup to destroy the DB." endif .PHONY: db_dev_start db_dev_start: ## Start Dev DB -ifndef CIRCLECI +ifndef GITLAB brew services stop postgresql 2> /dev/null || true @echo "Starting the ${DB_DOCKER_CONTAINER_DEV} docker database container..." # If running do nothing, if not running try to start, if can't start then run @@ -542,16 +533,16 @@ ifndef CIRCLECI -p $(DB_PORT_DEV):$(DB_PORT_DOCKER)\ $(DB_DOCKER_CONTAINER_IMAGE) else - @echo "Relying on CircleCI's database setup to start the DB." + @echo "Relying on GitLab's database setup to start the DB." endif .PHONY: db_dev_create db_dev_create: ## Create Dev DB -ifndef CIRCLECI +ifndef GITLAB @echo "Create the ${DB_NAME_DEV} database..." DB_NAME=postgres scripts/wait-for-db && DB_NAME=postgres psql-wrapper "CREATE DATABASE $(DB_NAME_DEV);" || true else - @echo "Relying on CircleCI's database setup to create the DB." + @echo "Relying on GitLab's database setup to create the DB." psql postgres://postgres:$(PGPASSWORD)@localhost:$(DB_PORT)?sslmode=disable -c 'CREATE DATABASE $(DB_NAME_DEV);' endif @@ -616,17 +607,17 @@ db_dev_bandwidth_up: check_app bin/generate-test-data db_dev_truncate ## Truncat .PHONY: db_deployed_migrations_destroy db_deployed_migrations_destroy: ## Destroy Deployed Migrations DB -ifndef CIRCLECI +ifndef GITLAB @echo "Destroying the ${DB_DOCKER_CONTAINER_DEPLOYED_MIGRATIONS} docker database container..." docker rm -f $(DB_DOCKER_CONTAINER_DEPLOYED_MIGRATIONS) || echo "No database container" rm -fr mnt/db_deployed_migrations # delete mount directory if exists else - @echo "Relying on CircleCI's database setup to destroy the DB." + @echo "Relying on GitLab's database setup to destroy the DB." endif .PHONY: db_deployed_migrations_start db_deployed_migrations_start: ## Start Deployed Migrations DB -ifndef CIRCLECI +ifndef GITLAB brew services stop postgresql 2> /dev/null || true endif @echo "Starting the ${DB_DOCKER_CONTAINER_DEPLOYED_MIGRATIONS} docker database container..." @@ -671,18 +662,18 @@ db_deployed_psql: ## Open PostgreSQL shell for Deployed Migrations DB .PHONY: db_test_destroy db_test_destroy: ## Destroy Test DB -ifndef CIRCLECI +ifndef GITLAB @echo "Destroying the ${DB_DOCKER_CONTAINER_TEST} docker database container..." docker rm -f $(DB_DOCKER_CONTAINER_TEST) || \ echo "No database container" else - @echo "Relying on CircleCI's database setup to destroy the DB." + @echo "Relying on GitLab's database setup to destroy the DB." psql postgres://postgres:$(PGPASSWORD)@localhost:$(DB_PORT_TEST)?sslmode=disable -c 'DROP DATABASE IF EXISTS $(DB_NAME_TEST);' endif .PHONY: db_test_start db_test_start: ## Start Test DB -ifndef CIRCLECI +ifndef GITLAB brew services stop postgresql 2> /dev/null || true @echo "Starting the ${DB_DOCKER_CONTAINER_TEST} docker database container..." docker start $(DB_DOCKER_CONTAINER_TEST) || \ @@ -694,17 +685,17 @@ ifndef CIRCLECI --mount type=tmpfs,destination=/var/lib/postgresql/data \ $(DB_DOCKER_CONTAINER_IMAGE) else - @echo "Relying on CircleCI's database setup to start the DB." + @echo "Relying on GitLab's database setup to start the DB." endif .PHONY: db_test_create db_test_create: ## Create Test DB -ifndef CIRCLECI +ifndef GITLAB @echo "Create the ${DB_NAME_TEST} database..." DB_NAME=postgres DB_PORT=$(DB_PORT_TEST) scripts/wait-for-db && \ createdb -p $(DB_PORT_TEST) -h $(DB_HOST) -U postgres $(DB_NAME_TEST) || true else - @echo "Relying on CircleCI's database setup to create the DB." + @echo "Relying on GitLab's database setup to create the DB." psql postgres://postgres:$(PGPASSWORD)@localhost:$(DB_PORT_TEST)?sslmode=disable -c 'CREATE DATABASE $(DB_NAME_TEST);' endif @@ -721,7 +712,7 @@ db_test_truncate: .PHONY: db_test_migrate_standalone db_test_migrate_standalone: bin/milmove ## Migrate Test DB directly -ifndef CIRCLECI +ifndef GITLAB @echo "Migrating the ${DB_NAME_TEST} database..." DB_DEBUG=0 DB_NAME=$(DB_NAME_TEST) DB_PORT=$(DB_PORT_TEST) bin/milmove migrate -p "file://migrations/${APPLICATION}/secure;file://migrations/${APPLICATION}/schema" -m "migrations/${APPLICATION}/migrations_manifest.txt" else @@ -1046,7 +1037,7 @@ run_prime_docker: ## Runs the docker that spins up the Prime API and data to tes # .PHONY: make_test -make_test: ## Test make targets not checked by CircleCI +make_test: ## Test make targets not checked by GitLab scripts/make-test # @@ -1074,7 +1065,7 @@ pretty: gofmt ## Run code through JS and Golang formatters npx prettier --write --loglevel warn "src/**/*.{js,jsx}" .PHONY: docker_circleci -docker_circleci: ## Run CircleCI container locally with project mounted +docker_circleci: ## Run GitLab container locally with project mounted docker run -it --pull=always --rm=true -v $(PWD):$(PWD) -w $(PWD) -e CIRCLECI=1 milmove/circleci-docker:milmove-app-3d9acdaa37c81a87b5fc1c6193a8e528dd56e4ed bash .PHONY: docker_local_ssh_server_with_password @@ -1288,4 +1279,4 @@ multi_branch: check_local_env clone_repo check_cloned_env success_message ## Set # ----- END SETUP MULTI BRANCH ----- # -default: help +default: help \ No newline at end of file diff --git a/cmd/prime-api-client/prime/create_mto_service_item.go b/cmd/prime-api-client/prime/create_mto_service_item.go index 4147b173fce..9c96f43e82c 100644 --- a/cmd/prime-api-client/prime/create_mto_service_item.go +++ b/cmd/prime-api-client/prime/create_mto_service_item.go @@ -39,6 +39,10 @@ type shuttleParams struct { Body primemessages.MTOServiceItemShuttle `json:"body"` } +type domesticShuttleParams struct { + Body primemessages.MTOServiceItemDomesticShuttle `json:"body"` +} + // InitCreateMTOServiceItemFlags initializes flags. func InitCreateMTOServiceItemFlags(flag *pflag.FlagSet) { flag.String(utils.FilenameFlag, "", "Name of the file being passed in") @@ -146,12 +150,17 @@ func CreateMTOServiceItem(cmd *cobra.Command, args []string) error { var params shuttleParams err = utils.DecodeJSONFileToPayload(filename, utils.ContainsDash(args), ¶ms) serviceItemParams.SetBody(¶ms.Body) + case primemessages.MTOServiceItemModelTypeMTOServiceItemDomesticShuttle: + var params domesticShuttleParams + err = utils.DecodeJSONFileToPayload(filename, utils.ContainsDash(args), ¶ms) + serviceItemParams.SetBody(¶ms.Body) default: err = fmt.Errorf("allowed modelType(): %v", []primemessages.MTOServiceItemModelType{ primemessages.MTOServiceItemModelTypeMTOServiceItemDestSIT, primemessages.MTOServiceItemModelTypeMTOServiceItemOriginSIT, primemessages.MTOServiceItemModelTypeMTOServiceItemDomesticCrating, primemessages.MTOServiceItemModelTypeMTOServiceItemShuttle, + primemessages.MTOServiceItemModelTypeMTOServiceItemDomesticShuttle, }) } // return any decoding errors diff --git a/migrations/app/migrations_manifest.txt b/migrations/app/migrations_manifest.txt index 8613ca2bcf5..8f36174c2e6 100644 --- a/migrations/app/migrations_manifest.txt +++ b/migrations/app/migrations_manifest.txt @@ -1075,13 +1075,19 @@ 20250103130619_revert_data_change_for_gbloc_for_ak.up.sql 20250103142533_update_postal_codes_and_gblocs_for_ak.up.sql 20250103180420_update_pricing_proc_to_use_local_price_variable.up.sql +20250106202424_update_duty_locs.up.sql +20250109194140_create_audit_history_table_for_payment_service_items.up.sql 20250110001339_update_nts_release_enum_name.up.sql 20250110153428_add_shipment_address_updates_to_move_history.up.sql 20250110214012_homesafeconnect_cert.up.sql 20250113152050_rename_ubp.up.sql 20250113160816_updating_create_accessorial_service_item_proc.up.sql 20250113201232_update_estimated_pricing_procs_add_is_peak_func.up.sql +20250114164752_add_ppm_estimated_incentive_proc.up.sql 20250116200912_disable_homesafe_stg_cert.up.sql 20250120144247_update_pricing_proc_to_use_110_percent_weight.up.sql +20250120214107_add_international_ntsr_service_items.up.sql 20250121153007_update_pricing_proc_to_handle_international_shuttle.up.sql +20250121184450_upd_duty_loc_B-22242.up.sql 20250123173216_add_destination_queue_db_func_and_gbloc_view.up.sql +20250206173204_add_hawaii_data.up.sql diff --git a/migrations/app/schema/20250106202424_update_duty_locs.up.sql b/migrations/app/schema/20250106202424_update_duty_locs.up.sql new file mode 100644 index 00000000000..d3bc09560e5 --- /dev/null +++ b/migrations/app/schema/20250106202424_update_duty_locs.up.sql @@ -0,0 +1,51 @@ +--update duty location for NAS Meridian, MS to use zip 39309 +update duty_locations set name = 'NAS Meridian, MS 39309', address_id = '691551c2-71fe-4a15-871f-0c46dff98230' where id = '334fecaf-abeb-49ce-99b5-81d69c8beae5'; + +--remove 39302 duty location +delete from duty_locations where id = 'e55be32c-bf89-4927-8893-4454a26bfd55'; + +--update duty location for Minneapolis, MN 55460 to use 55467 +update orders set new_duty_location_id = 'fc4d669f-594a-4784-9831-bf2eb9f8948b' where new_duty_location_id = '4c960096-1fbc-4b9d-b7d9-5979a3ba7344'; + +--remove 55460 duty location +delete from duty_locations where id = '4c960096-1fbc-4b9d-b7d9-5979a3ba7344'; + +--add 92135 duty location +DO $$ +BEGIN + + INSERT INTO addresses + (id, street_address_1, street_address_2, city, state, postal_code, created_at, updated_at, street_address_3, county, is_oconus, country_id, us_post_region_cities_id) + SELECT '3d617fab-bf6f-4f07-8ab5-f7652b8e7f3e'::uuid, 'n/a', NULL, 'NAS N ISLAND', 'CA', '39125', now(), now(), NULL, 'SAN DIEGO', false, '791899e6-cd77-46f2-981b-176ecb8d7098'::uuid, 'ce42858c-85af-4566-bbef-6b9aaf75c18a'::uuid + WHERE NOT EXISTS (select * from addresses where id = '3d617fab-bf6f-4f07-8ab5-f7652b8e7f3e'); + + INSERT INTO addresses + (id, street_address_1, street_address_2, city, state, postal_code, created_at, updated_at, street_address_3, county, is_oconus, country_id, us_post_region_cities_id) + SELECT '8d613f71-b80e-4ad4-95e7-00781b084c7c'::uuid, 'n/a', NULL, 'NAS NORTH ISLAND', 'CA', '39125', now(), now(), NULL, 'SAN DIEGO', false, '791899e6-cd77-46f2-981b-176ecb8d7098'::uuid, '191165db-d30a-414d-862b-54afdfc7aeb9'::uuid + WHERE NOT EXISTS (select * from addresses where id = '8d613f71-b80e-4ad4-95e7-00781b084c7c'); + + INSERT INTO duty_locations (id,"name",affiliation,address_id,created_at,updated_at,transportation_office_id,provides_services_counseling) + SELECT '56255626-bbbe-4834-8324-1c08f011f2f6'::uuid,'NAS N Island, CA 92135',NULL,'3d617fab-bf6f-4f07-8ab5-f7652b8e7f3e'::uuid,now(),now(),null,true + WHERE NOT EXISTS (select * from duty_locations where id = '56255626-bbbe-4834-8324-1c08f011f2f6'); + + INSERT INTO duty_locations (id,"name",affiliation,address_id,created_at,updated_at,transportation_office_id,provides_services_counseling) + SELECT '7156098f-13cf-4455-bcd5-eb829d57c714'::uuid,'NAS North Island, CA 92135',NULL,'8d613f71-b80e-4ad4-95e7-00781b084c7c'::uuid,now(),now(),null,true + WHERE NOT EXISTS (select * from duty_locations where id = '7156098f-13cf-4455-bcd5-eb829d57c714'); +END $$; + +--add Cannon AFB 88101 duty location +DO $$ +BEGIN + + INSERT INTO addresses + (id, street_address_1, street_address_2, city, state, postal_code, created_at, updated_at, street_address_3, county, is_oconus, country_id, us_post_region_cities_id) + SELECT 'fb90a7df-6494-4974-a0ce-4bdbcaff80c0'::uuid, 'n/a', NULL, 'CANNON AFB', 'NM', '88101', now(), now(), NULL, 'CURRY', false, '791899e6-cd77-46f2-981b-176ecb8d7098'::uuid, '68393e10-1aed-4a51-85a0-559a0a5b0e3f'::uuid + WHERE NOT EXISTS (select * from addresses where id = 'fb90a7df-6494-4974-a0ce-4bdbcaff80c0'); + + INSERT INTO duty_locations (id,"name",affiliation,address_id,created_at,updated_at,transportation_office_id,provides_services_counseling) + SELECT '98beab3c-f8ce-4e3c-b78e-8db614721621'::uuid, 'Cannon AFB, NM 88101',null, 'fb90a7df-6494-4974-a0ce-4bdbcaff80c0'::uuid,now(),now(),'80796bc4-e494-4b19-bb16-cdcdba187829',true + WHERE NOT EXISTS (select * from duty_locations where id = '98beab3c-f8ce-4e3c-b78e-8db614721621'); +END $$; + +--associate New London, CT duty location to New London transportation office +update duty_locations set transportation_office_id = '5eb485ae-fb9c-4c90-80e4-6231158797df' where id = '3a2a84cd-0991-4f40-9a19-f977608d08f0'; \ No newline at end of file diff --git a/migrations/app/schema/20250109194140_create_audit_history_table_for_payment_service_items.up.sql b/migrations/app/schema/20250109194140_create_audit_history_table_for_payment_service_items.up.sql new file mode 100644 index 00000000000..8318441ef8d --- /dev/null +++ b/migrations/app/schema/20250109194140_create_audit_history_table_for_payment_service_items.up.sql @@ -0,0 +1 @@ +SELECT add_audit_history_table(target_table := 'payment_service_items', audit_rows := BOOLEAN 't', audit_query_text := BOOLEAN 't', ignored_cols := ARRAY['created_at', 'updated_at', 'denied_at', 'requested_at', 'sent_to_gex_at', 'approved_at']); \ No newline at end of file diff --git a/migrations/app/schema/20250114164752_add_ppm_estimated_incentive_proc.up.sql b/migrations/app/schema/20250114164752_add_ppm_estimated_incentive_proc.up.sql new file mode 100644 index 00000000000..c0d7b3c9407 --- /dev/null +++ b/migrations/app/schema/20250114164752_add_ppm_estimated_incentive_proc.up.sql @@ -0,0 +1,249 @@ +-- IDFSIT PerUnitCents +INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES + ('fb7925e7-ebfe-49d9-9cf4-7219e68ec686'::uuid,'bd6064ca-e780-4ab4-a37b-0ae98eebb244','597bb77e-0ce7-4ba2-9624-24300962625f','2024-01-17 15:55:50.041957','2024-01-17 15:55:50.041957',false); -- PerUnitCents + +-- IDASIT PerUnitCents +INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES + ('51393ee1-f505-4f7b-96c4-135f771af814'::uuid,'806c6d59-57ff-4a3f-9518-ebf29ba9cb10','597bb77e-0ce7-4ba2-9624-24300962625f','2024-01-17 15:55:50.041957','2024-01-17 15:55:50.041957',false); -- PerUnitCents + +-- IOFSIT PerUnitCents +INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES + ('7518ec84-0c40-4c17-86dd-3ce04e2fe701'::uuid,'b488bf85-ea5e-49c8-ba5c-e2fa278ac806','597bb77e-0ce7-4ba2-9624-24300962625f','2024-01-17 15:55:50.041957','2024-01-17 15:55:50.041957',false); -- PerUnitCents + +-- IOASIT PerUnitCents +INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES + ('cff34123-e2a5-40ed-9cf3-451701850a26'::uuid,'bd424e45-397b-4766-9712-de4ae3a2da36','597bb77e-0ce7-4ba2-9624-24300962625f','2024-01-17 15:55:50.041957','2024-01-17 15:55:50.041957',false); -- PerUnitCents + +-- inserting PortZip param for FSC +-- we need this for international PPMs since they only get reimbursed for the CONUS -> Port portion +INSERT INTO service_params (id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) VALUES + ('bb53e034-80c2-420e-8492-f54d2018fff1'::uuid,'4780b30c-e846-437a-b39a-c499a6b09872','d9ad3878-4b94-4722-bbaf-d4b8080f339d','2024-01-17 15:55:50.041957','2024-01-17 15:55:50.041957',true); -- PortZip + +-- remove PriceAreaIntlOrigin, we don't need it +DELETE FROM service_params +WHERE service_item_param_key_id = '6d44624c-b91b-4226-8fcd-98046e2f433d'; + +DELETE FROM service_item_param_keys +WHERE key = 'PriceAreaIntlOrigin'; + +-- remove PriceAreaIntlDest, we don't need it +DELETE FROM service_params +WHERE service_item_param_key_id = '4736f489-dfda-4df1-a303-8c434a120d5d'; + +DELETE FROM service_item_param_keys +WHERE key = 'PriceAreaIntlDest'; + +-- adding port info that PPMs will consume +INSERT INTO public.ports +(id, port_code, port_type, port_name, created_at, updated_at) +VALUES('d8776c6b-bc5e-45d8-ac50-ab60c34c022d'::uuid, '4E1', 'S','TACOMA, PUGET SOUND', now(), now()); + +INSERT INTO public.port_locations +(id, port_id, cities_id, us_post_region_cities_id, country_id, is_active, created_at, updated_at) +VALUES('ee3a97dc-112e-4805-8518-f56f2d9c6cc6'::uuid, 'd8776c6b-bc5e-45d8-ac50-ab60c34c022d'::uuid, 'baaf6ab1-6142-4fb7-b753-d0a142c75baf'::uuid, '86fef297-d61f-44ea-afec-4f679ce686b7'::uuid, '791899e6-cd77-46f2-981b-176ecb8d7098'::uuid, true, now(), now()); + +-- func to fetch a service id from re_services by providing the service code +CREATE OR REPLACE FUNCTION get_service_id(service_code TEXT) RETURNS UUID AS $$ +DECLARE + service_id UUID; +BEGIN + SELECT rs.id INTO service_id FROM re_services rs WHERE rs.code = service_code; + IF service_id IS NULL THEN + RAISE EXCEPTION 'Service code % not found in re_services', service_code; + END IF; + RETURN service_id; +END; +$$ LANGUAGE plpgsql; + + +-- db func that will calculate a PPM's incentives +-- this is used for estimated/final/max incentives +CREATE OR REPLACE FUNCTION calculate_ppm_incentive( + ppm_id UUID, + pickup_address_id UUID, + destination_address_id UUID, + move_date DATE, + mileage INT, + weight INT, + is_estimated BOOLEAN, + is_actual BOOLEAN, + is_max BOOLEAN +) RETURNS TABLE ( + total_incentive NUMERIC, + price_islh NUMERIC, + price_ihpk NUMERIC, + price_ihupk NUMERIC, + price_fsc NUMERIC +) AS +$$ +DECLARE + ppm RECORD; + contract_id UUID; + o_rate_area_id UUID; + d_rate_area_id UUID; + service_id UUID; + estimated_fsc_multiplier NUMERIC; + fuel_price NUMERIC; + price_difference NUMERIC; + cents_above_baseline NUMERIC; +BEGIN + + IF NOT is_estimated AND NOT is_actual AND NOT is_max THEN + RAISE EXCEPTION 'is_estimated, is_actual, and is_max cannot all be FALSE. No update will be performed.'; + END IF; + + -- validating it's a real PPM + SELECT ppms.id INTO ppm FROM ppm_shipments ppms WHERE ppms.id = ppm_id; + IF ppm IS NULL THEN + RAISE EXCEPTION 'PPM with ID % not found', ppm_id; + END IF; + + contract_id := get_contract_id(move_date); + IF contract_id IS NULL THEN + RAISE EXCEPTION 'Contract not found for date: %', move_date; + END IF; + + o_rate_area_id := get_rate_area_id(pickup_address_id, NULL, contract_id); + IF o_rate_area_id IS NULL THEN + RAISE EXCEPTION 'Origin rate area is NULL for address ID %', pickup_address_id; + END IF; + + d_rate_area_id := get_rate_area_id(destination_address_id, NULL, contract_id); + IF d_rate_area_id IS NULL THEN + RAISE EXCEPTION 'Destination rate area is NULL for address ID %', destination_address_id; + END IF; + + -- ISLH calculation + service_id := get_service_id('ISLH'); + price_islh := ROUND( + calculate_escalated_price( + o_rate_area_id, + d_rate_area_id, + service_id, + contract_id, + 'ISLH', + move_date + ) * (weight / 100)::NUMERIC * 100, 0 + ); + + -- IHPK calculation + service_id := get_service_id('IHPK'); + price_ihpk := ROUND( + calculate_escalated_price( + o_rate_area_id, + NULL, + service_id, + contract_id, + 'IHPK', + move_date + ) * (weight / 100)::NUMERIC * 100, 0 + ); + + -- IHUPK calculation + service_id := get_service_id('IHUPK'); + price_ihupk := ROUND( + calculate_escalated_price( + NULL, + d_rate_area_id, + service_id, + contract_id, + 'IHUPK', + move_date + ) * (weight / 100)::NUMERIC * 100, 0 + ); + + -- FSC calculation + estimated_fsc_multiplier := get_fsc_multiplier(weight); + fuel_price := get_fuel_price(move_date); + price_difference := calculate_price_difference(fuel_price); + cents_above_baseline := mileage * estimated_fsc_multiplier; + price_fsc := ROUND((cents_above_baseline * price_difference) * 100); + + total_incentive := price_islh + price_ihpk + price_ihupk + price_fsc; + + UPDATE ppm_shipments + SET estimated_incentive = CASE WHEN is_estimated THEN total_incentive ELSE estimated_incentive END, + final_incentive = CASE WHEN is_actual THEN total_incentive ELSE final_incentive END, + max_incentive = CASE WHEN is_max THEN total_incentive ELSE max_incentive END + WHERE id = ppm_id; + + -- returning a table so we can use this data in the breakdown for the service member + RETURN QUERY SELECT total_incentive, price_islh, price_ihpk, price_ihupk, price_fsc; +END; +$$ LANGUAGE plpgsql; + + +-- db func that will calculate a PPM's SIT cost +-- returns a table with total cost and the cost of each first day/add'l day SIT service item +CREATE OR REPLACE FUNCTION calculate_ppm_sit_cost( + ppm_id UUID, + address_id UUID, + is_origin BOOLEAN, + move_date DATE, + weight INT, + sit_days INT +) RETURNS TABLE ( + total_cost INT, + price_first_day INT, + price_addl_day INT +) AS +$$ +DECLARE + ppm RECORD; + contract_id UUID; + sit_rate_area_id UUID; + service_id UUID; +BEGIN + -- make sure we validate parameters + IF sit_days IS NULL OR sit_days < 0 THEN + RAISE EXCEPTION 'SIT days must be a positive integer. Provided value: %', sit_days; + END IF; + + SELECT ppms.id INTO ppm FROM ppm_shipments ppms WHERE ppms.id = ppm_id; + IF ppm IS NULL THEN + RAISE EXCEPTION 'PPM with ID % not found', ppm_id; + END IF; + + contract_id := get_contract_id(move_date); + IF contract_id IS NULL THEN + RAISE EXCEPTION 'Contract not found for date: %', move_date; + END IF; + + sit_rate_area_id := get_rate_area_id(address_id, NULL, contract_id); + IF sit_rate_area_id IS NULL THEN + RAISE EXCEPTION 'Rate area is NULL for address ID % and contract ID %', address_id, contract_id; + END IF; + + -- calculate first day SIT cost + service_id := get_service_id(CASE WHEN is_origin THEN 'IOFSIT' ELSE 'IDFSIT' END); + price_first_day := ( + calculate_escalated_price( + CASE WHEN is_origin THEN sit_rate_area_id ELSE NULL END, + CASE WHEN NOT is_origin THEN sit_rate_area_id ELSE NULL END, + service_id, + contract_id, + CASE WHEN is_origin THEN 'IOFSIT' ELSE 'IDFSIT' END, + move_date + ) * (weight / 100)::NUMERIC * 100 + )::INT; + + -- calculate additional day SIT cost + service_id := get_service_id(CASE WHEN is_origin THEN 'IOASIT' ELSE 'IDASIT' END); + price_addl_day := ( + calculate_escalated_price( + CASE WHEN is_origin THEN sit_rate_area_id ELSE NULL END, + CASE WHEN NOT is_origin THEN sit_rate_area_id ELSE NULL END, + service_id, + contract_id, + CASE WHEN is_origin THEN 'IOASIT' ELSE 'IDASIT' END, + move_date + ) * (weight / 100)::NUMERIC * 100 * sit_days + )::INT; + + -- add em up + total_cost := price_first_day + price_addl_day; + + RETURN QUERY SELECT total_cost, price_first_day, price_addl_day; +END; +$$ LANGUAGE plpgsql; + diff --git a/migrations/app/schema/20250120214107_add_international_ntsr_service_items.up.sql b/migrations/app/schema/20250120214107_add_international_ntsr_service_items.up.sql new file mode 100644 index 00000000000..c80906403d9 --- /dev/null +++ b/migrations/app/schema/20250120214107_add_international_ntsr_service_items.up.sql @@ -0,0 +1,40 @@ +-- +-- Add service items for international NTS-R shipments. +-- +INSERT INTO re_service_items +(id, service_id, shipment_type, market_code, is_auto_approved, created_at, updated_at, sort) +VALUES + --ISLH International Shipping & Linehaul + ('bf76fb0f-408a-4391-8aa7-92908f3c027a', '9f3d551a-0725-430e-897e-80ee9add3ae9' ,'HHG_OUTOF_NTS', 'i', true, now(), now(), 1), + --PODFSC International POD Fuel Surcharge + ('db2106c8-887c-4304-aad2-c7413de13cc4', '388115e8-abe9-441d-96cf-a39f24baa0a3' ,'HHG_OUTOF_NTS', 'i', true, now(), now(), 2), + --POEFSC International POD Fuel Surcharge + ('509a491d-cddd-476c-9ba0-65077cf93e58', 'f75758d8-2fcd-40ba-9432-3ff3032a71d1' ,'HHG_OUTOF_NTS', 'i', true, now(), now(), 2), + --IHUPK International HHG unpack + ('89c6c283-4666-46c6-91ee-8041db7f88a7', '56e91c2d-015d-4243-9657-3ed34867abaa' ,'HHG_OUTOF_NTS', 'i', true, now(), now(), 3), + --INPK International NTS packing + ('4d348ec0-a278-4038-b061-6a4e17ea6721', '874cb86a-bc39-4f57-a614-53ee3fcacf14' ,'HHG_OUTOF_NTS', 'i', false, now(), now(), NULL), + --ICRT International crating + ('b8f4e434-0912-44c5-b824-c60e5c5dffee', '86203d72-7f7c-49ff-82f0-5b95e4958f60' ,'HHG_OUTOF_NTS', 'i', false, now(), now(), NULL), + --IDASIT International destination add'l day SIT + ('7135540f-602c-4d02-ba66-403b7252738e', '806c6d59-57ff-4a3f-9518-ebf29ba9cb10' ,'HHG_OUTOF_NTS', 'i', false, now(), now(), NULL), + --IDDSIT International destination SIT delivery + ('5d3261a5-a7af-4133-b2e0-2a06f694c551', '28389ee1-56cf-400c-aa52-1501ecdd7c69' ,'HHG_OUTOF_NTS', 'i', false, now(), now(), NULL), + --IDFSIT International destination 1st day SIT + ('90e022da-9944-4563-98a3-6eb7cabb017e', 'bd6064ca-e780-4ab4-a37b-0ae98eebb244' ,'HHG_OUTOF_NTS', 'i', false, now(), now(), NULL), + --IDSHUT International destination shuttle service + ('ba6c218b-dd99-4ef4-87ed-421581218bbf', '22fc07ed-be15-4f50-b941-cbd38153b378' ,'HHG_OUTOF_NTS', 'i', false, now(), now(), NULL), + --IOASIT International origin add'l day SIT + ('ded116f5-ca9d-465e-acd8-3eee899e9713', 'bd424e45-397b-4766-9712-de4ae3a2da36' ,'HHG_OUTOF_NTS', 'i', false, now(), now(), NULL), + --IOFSIT International origin 1st day SIT + ('03114a6f-22ef-4664-9269-b76636466285', 'b488bf85-ea5e-49c8-ba5c-e2fa278ac806' ,'HHG_OUTOF_NTS', 'i', false, now(), now(), NULL), + --IOPSIT International origin SIT pickup + ('03e7b2fd-431d-4ce6-a640-78de846095c9', '6f4f6e31-0675-4051-b659-89832259f390' ,'HHG_OUTOF_NTS', 'i', false, now(), now(), NULL), + --IOSHUT International origin shuttle service + ('94b1786f-86ec-4736-8dbc-6a0e29b64272', '624a97c5-dfbf-4da9-a6e9-526b4f95af8d' ,'HHG_OUTOF_NTS', 'i', false, now(), now(), NULL), + --IUCRT International uncrating + ('cefe3094-5670-41c3-b6cd-72730bbb8fc7', '4132416b-b1aa-42e7-98f2-0ac0a03e8a31' ,'HHG_OUTOF_NTS', 'i', false, now(), now(), NULL), + --IOFSC International Origin SIT Fuel Surcharge + ('6506987f-925d-473d-9872-b94e0279c1af', '81e29d0c-02a6-4a7a-be02-554deb3ee49e' ,'HHG_OUTOF_NTS', 'i', false, now(), now(), NULL), + --IDSFSC International Destination SIT Fuel Surcharge + ('ca34445f-3e42-4e7e-b631-b4ae81c813c9', '690a5fc1-0ea5-4554-8294-a367b5daefa9' ,'HHG_OUTOF_NTS', 'i', false, now(), now(), NULL); diff --git a/migrations/app/schema/20250121184450_upd_duty_loc_B-22242.up.sql b/migrations/app/schema/20250121184450_upd_duty_loc_B-22242.up.sql new file mode 100644 index 00000000000..c21c04f6a81 --- /dev/null +++ b/migrations/app/schema/20250121184450_upd_duty_loc_B-22242.up.sql @@ -0,0 +1,111 @@ +DO $$ +BEGIN + + --remove duty loc Johnston City, TN 37602 + IF EXISTS (SELECT 1 FROM duty_locations WHERE id = 'd3a1be10-dcd7-4720-bcbe-7ba76d243687') THEN + + + update orders set origin_duty_location_id = 'cd0c7325-15bb-45c7-a690-26c56c903ed7' where origin_duty_location_id = 'd3a1be10-dcd7-4720-bcbe-7ba76d243687'; + update orders set new_duty_location_id = 'cd0c7325-15bb-45c7-a690-26c56c903ed7' where new_duty_location_id = 'd3a1be10-dcd7-4720-bcbe-7ba76d243687'; + + delete from duty_locations where id = 'd3a1be10-dcd7-4720-bcbe-7ba76d243687'; + + END IF; + +END $$; + +DO $$ +BEGIN + + --remove duty loc Oceanside, CA 92052 + IF EXISTS (SELECT 1 FROM duty_locations WHERE id = '54ca99b7-3c2a-42b0-aa1a-ad071ac580de') THEN + + update orders set origin_duty_location_id = 'a6993e7b-4600-44b9-b288-04ca011143f0' where origin_duty_location_id = '54ca99b7-3c2a-42b0-aa1a-ad071ac580de'; + update orders set new_duty_location_id = 'a6993e7b-4600-44b9-b288-04ca011143f0' where new_duty_location_id = '54ca99b7-3c2a-42b0-aa1a-ad071ac580de'; + + delete from duty_locations where id = '54ca99b7-3c2a-42b0-aa1a-ad071ac580de'; + + END IF; + +END $$; + +DO $$ +BEGIN + + --remove duty loc Albuquerque, NM 87103 + IF EXISTS (SELECT 1 FROM duty_locations WHERE id = '2cc57072-19fa-438b-a44b-e349dff11763') THEN + + update orders set new_duty_location_id = '54acfb0e-222b-49eb-b94b-ccb00c6f529c' where new_duty_location_id = '2cc57072-19fa-438b-a44b-e349dff11763'; + + delete from duty_locations where id = '2cc57072-19fa-438b-a44b-e349dff11763'; + + END IF; + +END $$; + +DO $$ +BEGIN + + --remove duty loc August, GA 30917 + IF EXISTS (SELECT 1 FROM duty_locations WHERE id = '109ac405-47fb-4e1e-9efb-58290453ac09') THEN + + update orders set origin_duty_location_id = '595363c2-14ee-48e0-b318-e76ab0016453' where origin_duty_location_id = '109ac405-47fb-4e1e-9efb-58290453ac09'; + update orders set new_duty_location_id = '595363c2-14ee-48e0-b318-e76ab0016453' where new_duty_location_id = '109ac405-47fb-4e1e-9efb-58290453ac09'; + + delete from duty_locations where id = '109ac405-47fb-4e1e-9efb-58290453ac09'; + + END IF; + +END $$; + +DO $$ +BEGIN + + --remove duty loc Frankfort, KY 40602 + IF EXISTS (SELECT 1 FROM duty_locations WHERE id = 'c7fadaa2-902f-4302-a7cd-108c525b96d4') THEN + + update orders set origin_duty_location_id = '1a973257-cd15-42a9-86be-a14796c014bc' where origin_duty_location_id = 'c7fadaa2-902f-4302-a7cd-108c525b96d4'; + update orders set new_duty_location_id = '1a973257-cd15-42a9-86be-a14796c014bc' where new_duty_location_id = 'c7fadaa2-902f-4302-a7cd-108c525b96d4'; + + delete from duty_locations where id = 'c7fadaa2-902f-4302-a7cd-108c525b96d4'; + + END IF; + +END $$; + +DO $$ +BEGIN + + --remove duty loc Seattle, WA 98111 + IF EXISTS (SELECT 1 FROM duty_locations WHERE id = '2fb3e898-d6de-4be7-8576-7c7b10c2a706') THEN + + update orders set origin_duty_location_id = 'e7fdae4f-6be7-4264-99f8-03ee8541499c' where origin_duty_location_id = '2fb3e898-d6de-4be7-8576-7c7b10c2a706'; + update orders set new_duty_location_id = 'e7fdae4f-6be7-4264-99f8-03ee8541499c' where new_duty_location_id = '2fb3e898-d6de-4be7-8576-7c7b10c2a706'; + + delete from duty_locations where id = '2fb3e898-d6de-4be7-8576-7c7b10c2a706'; + + END IF; + +END $$; + +--add Joint Base Lewis McChord, WA 98438 duty location +INSERT INTO public.addresses + (id, street_address_1, city, state, postal_code, created_at, updated_at, county, is_oconus, country_id, us_post_region_cities_id) +SELECT '23d3140b-1ba2-400f-9d57-317034673c06'::uuid, 'n/a', 'JOINT BASE LEWIS MCCHORD', 'WA', '98438', now(),now(), 'PIERCE', false, '791899e6-cd77-46f2-981b-176ecb8d7098'::uuid, '81182dd4-1693-4b8d-9b6f-042bc4254019'::uuid +WHERE NOT EXISTS (SELECT * FROM addresses WHERE id = '23d3140b-1ba2-400f-9d57-317034673c06'); + +INSERT INTO public.duty_locations +(id, "name", affiliation, address_id, created_at, updated_at, transportation_office_id, provides_services_counseling) +SELECT '38fc6718-b80f-4761-a077-cfa62e414e27', 'Joint Base Lewis McChord, WA 98438', 'AIR_FORCE', '23d3140b-1ba2-400f-9d57-317034673c06'::uuid, now(), now(), '95abaeaa-452f-4fe0-9264-960cd2a15ccd', true +WHERE NOT EXISTS (SELECT * FROM duty_locations WHERE id = '38fc6718-b80f-4761-a077-cfa62e414e27'); + +INSERT INTO public.duty_locations +(id, "name", affiliation, address_id, created_at, updated_at, transportation_office_id, provides_services_counseling) +SELECT '693781f4-d011-4925-a492-aa1185f3f1fe'::uuid, 'McChord AFB, WA 98438', 'AIR_FORCE', 'cc7894e3-148e-4e21-98df-37e45f0b2c9f'::uuid, now(), now(), '95abaeaa-452f-4fe0-9264-960cd2a15ccd', true +WHERE NOT EXISTS (SELECT * FROM duty_locations WHERE id = '693781f4-d011-4925-a492-aa1185f3f1fe'); + +--associate duty loc Yuma, AZ 85365 to transportation office PPPO DMO MCAS Yuma - USMC +update duty_locations set transportation_office_id = '6ac7e595-1e0c-44cb-a9a4-cd7205868ed4' where id = '9e94208a-881d-47bc-82c0-4f375471751e'; + +--update name for Alameda +update transportation_offices set name = 'PPSO Base Alameda - USCG' where id = '3fc4b408-1197-430a-a96a-24a5a1685b45'; \ No newline at end of file diff --git a/migrations/app/schema/20250206173204_add_hawaii_data.up.sql b/migrations/app/schema/20250206173204_add_hawaii_data.up.sql new file mode 100644 index 00000000000..718a3de7a6d --- /dev/null +++ b/migrations/app/schema/20250206173204_add_hawaii_data.up.sql @@ -0,0 +1,364 @@ +-- insert Hawaii rate areas +INSERT INTO re_oconus_rate_areas (id,rate_area_id,country_id,us_post_region_cities_id, created_at,updated_at,active) VALUES + ('f041907c-080c-4e27-886f-4af7a2b8c559','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'6e3f3e59-adf9-404d-9a27-5c556593f02d'::uuid,now(),now(),true), + ('49bff3e2-d795-4fb2-8061-6f7f7dc871f3','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'64b913ec-97f7-403d-b3de-b52da877947b'::uuid,now(),now(),true), + ('e3ff1ee1-2d39-4fac-9bb4-b5355030ba99','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'3c7bc566-7627-474a-a7e2-8b61c00383c1'::uuid,now(),now(),true), + ('a25de140-fac9-4303-9d83-769ed4205668','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'93d86d63-1f55-4ed8-8db0-6f19e5f6599b'::uuid,now(),now(),true), + ('b68a62a1-69e0-4a15-bcbf-8d31c4464fbd','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'4ddb8033-4877-4e88-9e84-606e23b7131e'::uuid,now(),now(),true), + ('35387756-cea0-402b-a95c-be54e0094496','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'30d784ec-eded-4883-855c-2000d630774e'::uuid,now(),now(),true), + ('ff0167d9-ef93-43c8-9495-ba5bed8a0f69','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'563b5084-b7bc-4b37-8406-8c2dbf09cec4'::uuid,now(),now(),true), + ('956270a4-f850-4d94-b361-54d7104c381e','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'e82091dd-f4d5-40dc-ac78-73ce3dbba151'::uuid,now(),now(),true), + ('02c23633-7ee1-443a-9ecf-a1e422a28e6c','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'0556a422-ebdf-4041-906f-eebb3679b5af'::uuid,now(),now(),true), + ('3e45d573-1df1-4d43-a5f0-45844bff3185','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'99cbe9bd-4b8e-4ef9-ada1-a9b3b41d38f8'::uuid,now(),now(),true); +INSERT INTO re_oconus_rate_areas (id,rate_area_id,country_id,us_post_region_cities_id, created_at,updated_at,active) VALUES + ('6c26fd7c-44f5-40cf-ac88-e3202c752884','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'84e4594d-b21e-4b40-9ed0-37d6d332f55b'::uuid,now(),now(),true), + ('a52e2e8c-6251-4a94-9f3d-ecf8d18d097f','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'d87f4b53-70dd-432b-996d-6b4de56cd579'::uuid,now(),now(),true), + ('49f630f7-de16-40be-b15a-aa8eee7a0a09','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'6d4680f2-bd63-4af9-9abf-b5d4f5682423'::uuid,now(),now(),true), + ('8e854111-4805-46c6-92a2-aa7673a94b41','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'e7c8daed-ad79-4c28-aa7f-7f348df929c7'::uuid,now(),now(),true), + ('9a41d67f-10b2-48d8-8e3c-2f20d6744dc2','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'1719c3b4-e99f-4ac4-a5d0-2126aab7cd8c'::uuid,now(),now(),true), + ('76a161e0-40ee-4e6c-8138-8c78cefc0b73','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'e40bea96-389c-44f9-8ab4-407208ca7828'::uuid,now(),now(),true), + ('4e0ef4d5-124a-4f20-b2a4-f6d0d31bf8eb','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'9a297e12-fb3e-4454-b689-74fa82638c08'::uuid,now(),now(),true), + ('ed1aa4d4-99fd-4a2a-b757-c9d06e04484b','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'4c7443d1-25db-42db-99a7-cc08873e288b'::uuid,now(),now(),true), + ('7ff933f8-cede-4bf0-a918-2403d6b902a5','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'4e312da4-f0db-4dec-ab96-6bc6531f3ce6'::uuid,now(),now(),true), + ('bdf97313-d164-4d9b-bec3-9aff052fa094','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'5ca476cf-16f7-4ffa-b339-cfb0eff9f21d'::uuid,now(),now(),true); +INSERT INTO re_oconus_rate_areas (id,rate_area_id,country_id,us_post_region_cities_id, created_at,updated_at,active) VALUES + ('d6d7872d-1751-44d0-86e1-217e7e129afe','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'de3d79a4-bb38-400d-b478-0fb5389fa6b5'::uuid,now(),now(),true), + ('1100147f-9baf-4a12-879e-dd40aadf073b','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'91a847de-4602-42ff-8602-e6ba64fbe06f'::uuid,now(),now(),true), + ('c0abc8fa-f8c6-48e5-a49f-cc5714f6f509','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'fc58a7c3-81ed-4362-b18b-2a3a798db953'::uuid,now(),now(),true), + ('9ba33b41-15de-4743-b8f8-75367d0126e7','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'4f7347b7-441a-493d-bc70-ea421589c374'::uuid,now(),now(),true), + ('3188e86d-944a-42c1-b3b5-3bf7e360fb71','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'1bd23977-93cc-4441-bb3d-5e46deb33be4'::uuid,now(),now(),true), + ('fc281a01-a7cd-41a0-86d9-716ab4864c08','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'4b9ea4cb-5feb-4226-851e-086418dfe224'::uuid,now(),now(),true), + ('fb6d3db2-4079-48fe-9a67-170e200c9a8d','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'640de451-a487-4d24-9c8b-6405f1431a38'::uuid,now(),now(),true), + ('3bb35faa-229f-4721-82a2-becc9c98ae04','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'97508ed1-fde5-422c-b5ca-f7350bac5b72'::uuid,now(),now(),true), + ('313905e6-f237-4e1d-ab01-be6dfdddfa92','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'0d09ac18-0ee4-4905-879f-8b2d6b5112f7'::uuid,now(),now(),true), + ('0ed0a302-5016-4ccd-a6ba-5b28258af8dc','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'24baeda8-d21b-437f-bd43-8da4d6241011'::uuid,now(),now(),true); +INSERT INTO re_oconus_rate_areas (id,rate_area_id,country_id,us_post_region_cities_id, created_at,updated_at,active) VALUES + ('846616d3-34e1-4304-b778-3bf8b3052c32','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'affb1936-8b01-41e5-bb7d-558476520092'::uuid,now(),now(),true), + ('f3b6f9da-4bfd-4ac5-a9e2-b4973adf372c','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'87a96b8b-cc4b-416c-a0ab-f31868e90796'::uuid,now(),now(),true), + ('b42c8d98-71b3-464f-b868-6ab743ad7d3a','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'6779b457-5e01-40bd-a584-76602c234bf0'::uuid,now(),now(),true), + ('41e52c48-4b52-4498-b898-22af6fcd5452','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'74792dae-6e22-4dc2-8185-6fc56fad958c'::uuid,now(),now(),true), + ('036d9855-ae02-4836-9405-f332d3654838','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'9b8e278b-6a25-4bdf-831a-cea032d0f647'::uuid,now(),now(),true), + ('5bad5f3a-861a-448d-8747-66bfc77b9859','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'f9dfe9b6-fe0e-405a-b88f-4e085a940843'::uuid,now(),now(),true), + ('c7132b2f-8e28-4ee3-af04-d6137f14944a','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'274a633f-5cd7-4429-accb-56e77c0b9ac2'::uuid,now(),now(),true), + ('05775275-180d-4562-8cd5-9b20403e0824','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'cb071d07-649b-422e-99d4-a52ba1dbdccf'::uuid,now(),now(),true), + ('5c8029b9-85c3-452b-95cf-00d3d66bda7a','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'f0a8fb3b-7e36-43bc-9c23-488ea507c42b'::uuid,now(),now(),true), + ('b08f70e8-702b-454a-bc58-b0b10e1b603e','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'8e667d68-60dc-4b13-87b5-0e7d4ac6b71b'::uuid,now(),now(),true); +INSERT INTO re_oconus_rate_areas (id,rate_area_id,country_id,us_post_region_cities_id, created_at,updated_at,active) VALUES + ('20b866a6-d146-439b-b87b-d22412cda747','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'1e3fd71e-0ac7-4fe3-9cf3-3d040473a10b'::uuid,now(),now(),true), + ('5dde8586-790a-40d6-9a46-e6cd725d1d5e','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'d64af573-bfcb-4c39-bc32-c89632c26913'::uuid,now(),now(),true), + ('fedaa7ae-99a0-4981-9a07-835c0d094e1c','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'712527f7-731f-4421-9b1c-9c58a64654b0'::uuid,now(),now(),true), + ('73bf3243-c29b-4b4e-9341-5aaf99616e56','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'8f87c288-1ed2-4bd5-a397-607585e22768'::uuid,now(),now(),true), + ('3c6ab532-1a18-4f78-9b95-deb1905d2d34','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'70934bce-e2fe-47d9-bb99-55f1e3c4fc00'::uuid,now(),now(),true), + ('af3a2ad4-585b-4947-bb40-3ae575013ed5','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'fa790c20-9da9-45f5-af4c-11438f4fb735'::uuid,now(),now(),true), + ('01e44612-45b9-4964-9de9-843e7a3c0044','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'e169dc8c-bb76-4290-9282-3aa4189c8970'::uuid,now(),now(),true), + ('6a0ce7ea-2daa-4f06-95bf-3c6222c5a8b3','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'af62f613-8e38-4178-9e79-020136b1fe76'::uuid,now(),now(),true), + ('e164aac9-e9aa-4831-8ef1-98660215705b','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'481ec192-416e-49f0-ba7b-f565d5986eb3'::uuid,now(),now(),true), + ('670a550c-7243-4318-b8d4-e4efcfa32539','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'5a821f96-fb5d-48dc-8eeb-642d3ed6f3ef'::uuid,now(),now(),true); +INSERT INTO re_oconus_rate_areas (id,rate_area_id,country_id,us_post_region_cities_id, created_at,updated_at,active) VALUES + ('e25c2146-7ade-4552-a66d-d3bc948f39c0','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'87048c04-81cb-4ef4-a536-2fcff23eee0a'::uuid,now(),now(),true), + ('d32022a5-f00b-461b-8a3b-0c69df06a94f','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'7133a5d8-0c5d-429b-b43f-36308a712b72'::uuid,now(),now(),true), + ('66861383-84f0-46c0-9597-c8765152601d','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'64fbd65a-c742-48f0-9342-b5e1340cffb6'::uuid,now(),now(),true), + ('9b52d7a7-8ebe-4cef-a49d-2e03eb87878c','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'07a388d8-2a3d-43fe-baa6-91282a179617'::uuid,now(),now(),true), + ('05c41080-8f3d-41bc-8350-25c9c50dc8bf','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'9f70610f-4a16-4636-ac8d-1a068075ef30'::uuid,now(),now(),true), + ('97b37859-0f6c-48ff-bec0-403cca95712f','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'7b121c37-3133-4daa-af24-26ad005b15d8'::uuid,now(),now(),true), + ('f0ae37b7-bcc1-4107-bdc7-43d21605d0c6','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'d12a6235-1399-453e-afd3-00fd9cf7faff'::uuid,now(),now(),true), + ('de8e5560-246b-45bb-a1df-0e9a0f0ab7ad','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'d6448f66-ae16-4ab3-af2a-7988e65c2c45'::uuid,now(),now(),true), + ('de85bbee-084e-42d2-ab68-677a927edb2a','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'8cab537e-1a01-49dc-9b44-c1dbe2226047'::uuid,now(),now(),true), + ('733e38ca-86e1-454f-87a4-884f8b17df21','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'2929ef2d-4373-4880-8017-85e8863afa04'::uuid,now(),now(),true); +INSERT INTO re_oconus_rate_areas (id,rate_area_id,country_id,us_post_region_cities_id, created_at,updated_at,active) VALUES + ('8b2f2560-f26c-43d9-98ca-322a3e73b0bb','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'a62608f7-f9e0-4272-a051-4bd7014f9484'::uuid,now(),now(),true), + ('f5b7df58-dd68-4d24-8f7a-907895dbb151','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'ca858eba-ed95-4478-8ea2-ed5c99b75259'::uuid,now(),now(),true), + ('d7843a4e-0c70-466b-b01d-5909b1f62c11','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'462d1ef8-0d79-4e0b-b09c-ff349796c6b8'::uuid,now(),now(),true), + ('8d211696-7337-4c65-be73-9a88049476e4','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'cdb689fd-8000-4445-adb4-70c0941fe601'::uuid,now(),now(),true), + ('3821b597-e99a-432d-afa0-1aa74b802b94','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'10d1be45-6fdf-43a3-bfd3-930e493ff8fd'::uuid,now(),now(),true), + ('5918a6de-11ae-43f1-ba68-23cd71da6e7b','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'0b5abf65-b844-4b1e-88cf-17c7469bb2a0'::uuid,now(),now(),true), + ('76e8e46c-ab0d-4c67-95b6-d00185dd2832','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'9689a42b-27ad-4c44-88ee-af317acaee70'::uuid,now(),now(),true), + ('7b33ae55-5463-4b57-97e6-a7443a1e0904','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'fa0344ac-7814-4c97-b22e-68721c0686a9'::uuid,now(),now(),true), + ('766fcf2c-9145-4f79-b502-d0ffaf96ef17','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'b28b69f2-3139-434a-8647-5ecb68b52d62'::uuid,now(),now(),true), + ('e1e1bf33-bd47-4ac7-9128-1f5c02d0db7e','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'fbb7608d-d1a2-4928-a57d-8fca190b1d05'::uuid,now(),now(),true); +INSERT INTO re_oconus_rate_areas (id,rate_area_id,country_id,us_post_region_cities_id, created_at,updated_at,active) VALUES + ('8df33232-52a9-480f-afb8-df27cd099bd2','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'28e21fa2-10e5-421d-a744-f9a7941f5fe3'::uuid,now(),now(),true), + ('8415fff3-ccac-4ae8-82ac-e54f04ca4082','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'1eb608e7-fd23-4d17-9731-6a48b69fd886'::uuid,now(),now(),true), + ('9d2ef3a3-9e55-4797-9b58-35429c3849a8','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'5279030c-7331-4466-8d81-8ff94420a0cd'::uuid,now(),now(),true), + ('3e99cabc-75cd-4152-b519-cddfbd4294aa','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'61e3f4b5-66e1-449d-aab2-6a91a715cd01'::uuid,now(),now(),true), + ('616c1ea1-ac8f-4ccf-a28d-80078015ba0e','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'5ae811db-96b7-48c8-954e-d0252872668e'::uuid,now(),now(),true), + ('e33e15dd-d3aa-480d-a70c-8de943b1dccc','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'5d3cb00b-2b90-47d1-b841-01ef70d334d6'::uuid,now(),now(),true), + ('1bdbc429-b5d6-4483-821e-a31839e63ad2','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'3829f26e-b286-4205-9f94-106d461425c9'::uuid,now(),now(),true), + ('0d327bc0-5315-44d1-848c-1f02667a81db','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'83f0986d-0f31-4870-8d43-d00d58f45aff'::uuid,now(),now(),true), + ('7088fd82-8a7f-47c3-a80b-b5255a795f6b','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'848b1555-6920-4aa8-b44d-8fc4f08ab7a0'::uuid,now(),now(),true), + ('54045203-93a8-4011-a9b2-84673cc5176d','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'7cea3c34-3f16-44ed-905d-af85ef732efb'::uuid,now(),now(),true); +INSERT INTO re_oconus_rate_areas (id,rate_area_id,country_id,us_post_region_cities_id, created_at,updated_at,active) VALUES + ('f6b671cf-c344-46ba-bf68-08fcba2ebd84','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'1ffe2a07-0271-4873-a98e-372e4d7ed4c6'::uuid,now(),now(),true), + ('26e71f17-7713-46d9-a059-5e07978b9b50','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'6083c976-172c-4024-96b6-d1bd034352e9'::uuid,now(),now(),true), + ('f7c97962-b0f9-4671-b9d8-06c6195bcde0','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'cf358ecf-fbc4-41b8-8872-1d8fc7b732d7'::uuid,now(),now(),true), + ('20e947f3-a7d1-423c-ae79-9886857d5dcc','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'253cefff-4e1a-4b20-9d21-05654e30132c'::uuid,now(),now(),true), + ('49e0f65d-1545-424d-b1c9-e5e3d0067fe9','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'fa9bae80-8d1a-443a-902a-2e527a0d00ce'::uuid,now(),now(),true), + ('9f0d1cb8-4a5d-42f4-8766-c3ad7f029084','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'fd3c4223-7dcf-4efc-a80e-5aba50156140'::uuid,now(),now(),true), + ('e4392356-88c7-4841-a415-920aa9cdc7ca','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'24d4d08c-7676-4d05-aaf2-16e03a5d764f'::uuid,now(),now(),true), + ('f549cddf-ec33-4772-b259-5e12aa2f66b9','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'7f3e06ad-b892-42c5-880e-a70fb9ba7c84'::uuid,now(),now(),true), + ('788cb14b-30e1-447d-95cc-358e29f01d8d','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'09e92a39-2407-4a64-891b-ed81f7d61048'::uuid,now(),now(),true), + ('46d6bc67-b8e5-4e11-834e-eaaa62cad46e','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'6895d267-67a1-4cde-8d46-e96d2142faf5'::uuid,now(),now(),true); +INSERT INTO re_oconus_rate_areas (id,rate_area_id,country_id,us_post_region_cities_id, created_at,updated_at,active) VALUES + ('67d1f1ab-782a-452f-a607-ba2e6a9a87aa','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'e5c12a89-96fa-4fd5-8448-ffb546c0ad82'::uuid,now(),now(),true), + ('51a17659-2fd7-49c3-9971-8fd1e341fcba','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'6b63cd86-8dc5-498e-a541-782789aa6edd'::uuid,now(),now(),true), + ('646aaf79-c655-443d-a02f-81764d2965c0','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'48c74f64-68a1-43fc-90e0-d9843d645b65'::uuid,now(),now(),true), + ('ecb1f2a7-525e-490a-8e88-b53a6681aa96','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'900cfeee-f5bb-41b3-976f-6a223d230b23'::uuid,now(),now(),true), + ('bf597a56-1728-4325-9734-1138799d6332','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'e8283abd-d3bb-4d11-b66c-ea57d001fa7e'::uuid,now(),now(),true), + ('8608f8da-6f62-41ea-8c3d-024c1bcc7daa','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'8498acbd-27f2-4095-8a27-b01743bfa6e1'::uuid,now(),now(),true), + ('df227441-2e7f-456f-82c5-b4772c7cc5f6','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'d1dd9288-32ce-436f-800a-2f08bd1a9702'::uuid,now(),now(),true), + ('10177e21-4c50-44a6-956b-3eedeac77094','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'9c6a995f-5cfc-4bcd-b1fb-bad1b4512fdf'::uuid,now(),now(),true), + ('373f38ab-0a7b-40c9-8cc5-0df01d1d87de','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'63b50a6f-a019-492a-84f4-3768f20bd5f2'::uuid,now(),now(),true), + ('14b7aa87-7a4e-4e47-b2e4-fa1ed8cdd2a8','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'2f870d5b-0d5a-4ccf-8f11-cab82d5cf1e6'::uuid,now(),now(),true); +INSERT INTO re_oconus_rate_areas (id,rate_area_id,country_id,us_post_region_cities_id, created_at,updated_at,active) VALUES + ('05b15308-4fbd-4e03-ab59-437b9f37e69a','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'f24af83c-8734-48c0-9796-c944a36e137d'::uuid,now(),now(),true), + ('b36bffda-b020-4e49-bb6e-971d17116bde','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'384cf248-0a91-47e2-bab3-cdd6c3816843'::uuid,now(),now(),true), + ('89c8ec3e-af9d-4376-9b47-2a68589abcb4','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'e7bbfe8c-ed10-4c50-a91b-9bf308f0a92a'::uuid,now(),now(),true), + ('ba61f387-aa0f-434c-aa61-5c15982d4ce4','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'592f383e-b566-4b0b-a8e7-cad06a61302f'::uuid,now(),now(),true), + ('09795f49-0694-4cfb-a711-8aa4edc64ef4','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'3098d832-1e05-485a-bdc2-75b249940c75'::uuid,now(),now(),true), + ('e54eb89b-88e8-469d-964e-faad60eafdef','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'b8204250-497a-4550-b2ac-cfa205acb252'::uuid,now(),now(),true), + ('19e2ad86-e55a-4ba5-bae1-f86e922933e3','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'698046a6-9284-4405-9abd-8a987dca3fc5'::uuid,now(),now(),true), + ('d1689b93-68e7-44d5-abd5-c3063bc4e670','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'27323f14-a2d8-4953-8bdc-ed68fcb9f5de'::uuid,now(),now(),true), + ('64bd4440-9ee4-4a80-bcbd-5701ab160e73','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'b668cee2-78c2-4065-9af0-400793b58262'::uuid,now(),now(),true), + ('00f9ac3e-b9c2-4cf5-af47-6b4799f7333f','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'5cee3d06-1fb5-49b4-816f-63fa7b79719e'::uuid,now(),now(),true); +INSERT INTO re_oconus_rate_areas (id,rate_area_id,country_id,us_post_region_cities_id, created_at,updated_at,active) VALUES + ('9771ac15-d640-4826-b055-c296696842e8','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'aef986d9-8f48-4ce7-b82a-d6fa1b5fb052'::uuid,now(),now(),true), + ('91b89a8b-81f9-4380-b94c-6f7a33f8b4d9','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'077558ed-d9ef-479f-8163-6fa629437697'::uuid,now(),now(),true), + ('5996d817-ab35-4477-8b77-832fe18a1abd','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'1944531b-e876-4217-8c0f-ffc79e1ed814'::uuid,now(),now(),true), + ('f1795fad-656d-4600-ad09-05629eba7d43','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'526ce089-31f1-4648-aacb-757c1aa0a1ed'::uuid,now(),now(),true), + ('f899cc7f-96e6-4b26-af04-0d6edb9e01b2','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'7abfb152-f97d-4004-b3a3-d7dcba9dabee'::uuid,now(),now(),true), + ('97425b50-7fab-4178-94c9-bf913585ac80','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'35c7c653-1c14-46c0-a485-7c487862acd4'::uuid,now(),now(),true), + ('70f5543b-82cb-4171-9c55-58e479ebaaa1','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'3ace2df3-045a-4a39-9a8a-364f96686af9'::uuid,now(),now(),true), + ('bae425c4-2ab4-4fdb-8b96-b0654894ebbc','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'ca928d85-d169-4e56-8412-840675dd08a8'::uuid,now(),now(),true), + ('736ab44f-3497-42f7-891d-845c868e7a29','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'e17a9675-4d71-470e-8ee5-6f88605c0c2a'::uuid,now(),now(),true), + ('f118e38a-f46d-47d2-879d-c08b84781de8','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'069ecbb5-d5a3-4c19-af40-9a1bce621bb8'::uuid,now(),now(),true); +INSERT INTO re_oconus_rate_areas (id,rate_area_id,country_id,us_post_region_cities_id, created_at,updated_at,active) VALUES + ('c74ddc03-0e4b-4645-99d2-7c711b3ab5c4','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'3689739b-c24e-4355-a612-6e3c3be7d16c'::uuid,now(),now(),true), + ('7b49bb85-c6bf-4cf3-8726-7256658e2ffb','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'b6713466-1a6c-4d57-901d-bef1323cd973'::uuid,now(),now(),true), + ('bf1b7d0b-6477-4784-9a62-aebb13921de9','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'018308a0-e7d0-43be-9c90-d13293102af5'::uuid,now(),now(),true), + ('2323d966-5d75-46df-b6df-eb43c616ba89','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'73be3c75-eb0f-4525-87c2-97d63494ca92'::uuid,now(),now(),true), + ('7607c8ed-de3f-414e-ae11-e85fca9bd847','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'9fcecf37-9044-4c24-b2e1-9193a8bfec2d'::uuid,now(),now(),true), + ('c7e39bbe-048a-442a-a8c9-ec58de94caec','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'20528efa-9329-477f-bd8a-d4546abdce54'::uuid,now(),now(),true), + ('17a3c0ed-5cfb-4b60-8436-f0843e2d8624','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'71449390-b1fd-43ed-a72d-da805ff4fdbb'::uuid,now(),now(),true), + ('a3e5aad3-27d5-4e50-853d-504a3f15c08e','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'928f17db-ff7d-46b8-a3b0-31587db2d334'::uuid,now(),now(),true), + ('c43a5bf3-0d45-4001-9565-b78a0c59c9d3','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'ad6ed769-a4d8-41a2-b9be-aa500546f8fa'::uuid,now(),now(),true), + ('919ed6b0-7939-4476-a7a8-6b49b820849c','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'6dc3eafe-ee75-4ed9-9019-933dd155b1fc'::uuid,now(),now(),true); +INSERT INTO re_oconus_rate_areas (id,rate_area_id,country_id,us_post_region_cities_id, created_at,updated_at,active) VALUES + ('43197d2c-728b-4204-8786-851da9fe9be8','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'17e33fff-b934-46d0-84a6-9f0a89ecd6f7'::uuid,now(),now(),true), + ('ec36f031-4c35-4e24-ba08-83e03846be85','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'244446bd-416c-4a6c-8d27-469db94417aa'::uuid,now(),now(),true), + ('64f29707-3503-40d0-96b7-6299381f1c60','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'c37d1d9e-cc49-4a98-9524-ac453e275c74'::uuid,now(),now(),true), + ('e7d88312-1da3-42b0-a487-2e2bf623842e','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'4be1d1fd-19b8-43c6-9f8f-3779f46fe518'::uuid,now(),now(),true), + ('ccb55592-003a-4135-802f-8d443422bc8b','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'e9b38982-6164-47f9-b940-3c9e25951236'::uuid,now(),now(),true), + ('435c4a00-bc26-4cc3-8dd4-f2eb4e31c895','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'e474d48b-c435-4977-b290-63b40802d2d0'::uuid,now(),now(),true), + ('69cc680a-75a9-4b10-9847-51fbaf1caee1','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'910d037a-c995-418c-84ec-d460ac422ae3'::uuid,now(),now(),true), + ('391bd5cd-ab81-4eb0-b123-21a410c71cd5','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'7497eff0-0583-4c95-9a29-5937527e1d68'::uuid,now(),now(),true), + ('d6ab4627-4d4f-41db-9baf-fec5f2487651','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'b0f6a629-8120-43b1-8a04-c9c9d741a39a'::uuid,now(),now(),true), + ('714c5707-aba2-455d-ad9a-dcf3b0b8b66c','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'632268db-5eb3-48fc-83c1-d6ab47b6a8a5'::uuid,now(),now(),true); +INSERT INTO re_oconus_rate_areas (id,rate_area_id,country_id,us_post_region_cities_id, created_at,updated_at,active) VALUES + ('05554ee7-30d4-4bb3-bd8c-e0c478f4a037','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'9a1b9b7b-4319-4aee-b8aa-f0b98d94a528'::uuid,now(),now(),true), + ('f91a9353-16a2-4fbf-83db-54b06bc68381','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'0799c023-f508-4c68-a5b4-0654f002ab83'::uuid,now(),now(),true), + ('bb4c4e1e-828f-4a25-9dc2-81887e5553be','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'1841c945-89e8-414e-b570-864783247c28'::uuid,now(),now(),true), + ('fa941444-f49d-4f51-b025-d3c6b63242a7','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'80fcfb21-83f7-4738-91e9-69a435972474'::uuid,now(),now(),true), + ('eca65b4d-3981-46d9-9e10-a8d20c85c303','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'208b9fae-a5e7-4cc1-ba9e-15dceedee24b'::uuid,now(),now(),true), + ('cad3dad4-4025-478b-b3db-44b2f2886115','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'7a0135b7-06bd-4c1e-849b-1d0201a886e2'::uuid,now(),now(),true), + ('f15f99d4-4a96-4603-8dbf-d3af72be8db0','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'56ad34b3-961d-487e-a265-9f2cc4c58fec'::uuid,now(),now(),true), + ('5897d064-bbb0-4ac4-a283-5fa7b91cdfd6','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'a0846cf9-2ae8-44d1-ad1f-e4e80770e41c'::uuid,now(),now(),true), + ('bd4ca5db-cabb-44e7-8fdc-9a5d060161c7','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'749a1b35-b966-48ee-9be9-59ea581be394'::uuid,now(),now(),true), + ('4c286583-5039-4455-bc9f-36ed842f9aa5','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'470d34d6-da01-4fdf-949d-6b47ed2969d0'::uuid,now(),now(),true); +INSERT INTO re_oconus_rate_areas (id,rate_area_id,country_id,us_post_region_cities_id, created_at,updated_at,active) VALUES + ('428a26d5-3dc7-44f6-8043-183b0bb60ee0','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'a2df67b1-5b0b-4ab0-b9a0-11ff7ed8e0bf'::uuid,now(),now(),true), + ('86ff314b-0702-478a-aa19-c8cdb2f0fc2f','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'80b7c3a1-f3ee-4e41-aeaa-ac1ab429d7ef'::uuid,now(),now(),true), + ('1e564ac9-c298-4a6e-b503-02db1459a621','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'345360d0-c3e9-4d87-b60b-59ab4b50c9ff'::uuid,now(),now(),true), + ('458174be-38b9-4acb-bd8b-7fb9012cd271','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'02bd132f-3620-4d55-ae47-d4a776b96235'::uuid,now(),now(),true), + ('0f86385c-8c84-4597-9c4e-3e8272c1df45','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'8e786192-31cf-4fcd-98c1-05de9a64d81b'::uuid,now(),now(),true), + ('83658135-4bdb-4d86-9359-6733fd7ef3ad','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'cd40843e-74d0-485f-ab78-c6c95d24b61a'::uuid,now(),now(),true), + ('c7e65080-7667-48b9-8794-013cdc1c72ec','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'f5a873a9-d1a0-4e6a-93fe-64cfdd0a957e'::uuid,now(),now(),true), + ('80f818a0-1047-466a-a3c1-ce2fc905f205','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'53d28592-dfbf-47d0-8543-1d45f206f8c0'::uuid,now(),now(),true), + ('13342500-620c-41db-af42-dcecf468ee43','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'550ed19a-fc56-42e7-a344-a5dc5c987037'::uuid,now(),now(),true), + ('c2c17c57-3d88-444d-b6ba-69a3d0d734f0','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'70f511da-6726-48a1-8991-4d33686419b7'::uuid,now(),now(),true); +INSERT INTO re_oconus_rate_areas (id,rate_area_id,country_id,us_post_region_cities_id, created_at,updated_at,active) VALUES + ('950daf0c-0174-4e2f-b03b-05c088520cff','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'7895e2e6-5877-465c-80b0-03da0c4bf3c2'::uuid,now(),now(),true), + ('9133dbe1-d08b-478e-8dbe-109fc05ca427','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'20b97576-a136-4ce3-a430-88ba601ee993'::uuid,now(),now(),true), + ('c1a175cd-c7d1-45ff-a49a-2a6cc4613e9f','71755cc7-0844-4523-a0ac-da9a1e743ad1'::uuid,'791899e6-cd77-46f2-981b-176ecb8d7098'::uuid,'f4d874b1-2445-439f-8c70-b10b8732fe11'::uuid,now(),now(),true); + + +-- insert Hawaii GBLOC associations +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('0b7f145f-0add-4aa3-bc51-a28a2f79b111','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'f041907c-080c-4e27-886f-4af7a2b8c559'::uuid,NULL,NULL,true,now(),now()), + ('df2f72e1-29f6-4219-9255-16c9c6098fcf','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'49bff3e2-d795-4fb2-8061-6f7f7dc871f3'::uuid,NULL,NULL,true,now(),now()), + ('979afabc-346c-4481-b91c-eb1ed5061874','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'e3ff1ee1-2d39-4fac-9bb4-b5355030ba99'::uuid,NULL,NULL,true,now(),now()), + ('9e294d37-1e1f-418a-9153-7a77ce5e6aaf','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'a25de140-fac9-4303-9d83-769ed4205668'::uuid,NULL,NULL,true,now(),now()), + ('a5f78d7f-f0a4-4190-829f-50bc0888a3f5','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'b68a62a1-69e0-4a15-bcbf-8d31c4464fbd'::uuid,NULL,NULL,true,now(),now()), + ('28ca264b-f2e0-42fe-a4c8-d0a91045fea0','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'35387756-cea0-402b-a95c-be54e0094496'::uuid,NULL,NULL,true,now(),now()), + ('1ba7ca65-ffbe-4049-897f-ef34b619f54d','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'ff0167d9-ef93-43c8-9495-ba5bed8a0f69'::uuid,NULL,NULL,true,now(),now()), + ('3bc8655a-3c05-4c96-b317-a2d21f52c5f1','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'956270a4-f850-4d94-b361-54d7104c381e'::uuid,NULL,NULL,true,now(),now()), + ('39c53018-0418-4c19-8b66-334e7f689b45','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'02c23633-7ee1-443a-9ecf-a1e422a28e6c'::uuid,NULL,NULL,true,now(),now()), + ('feb1c2fa-b793-4029-8426-e9de4384d969','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'3e45d573-1df1-4d43-a5f0-45844bff3185'::uuid,NULL,NULL,true,now(),now()); +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('d2c3e83d-a568-4b8e-9424-2f068c844066','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'6c26fd7c-44f5-40cf-ac88-e3202c752884'::uuid,NULL,NULL,true,now(),now()), + ('d1a64f83-a974-4741-a7a4-17c4747460d0','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'a52e2e8c-6251-4a94-9f3d-ecf8d18d097f'::uuid,NULL,NULL,true,now(),now()), + ('58a79e8b-8dec-4de1-836b-c12bc987071a','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'49f630f7-de16-40be-b15a-aa8eee7a0a09'::uuid,NULL,NULL,true,now(),now()), + ('078eae9d-9cdb-40fa-aa9b-788dd5391536','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'8e854111-4805-46c6-92a2-aa7673a94b41'::uuid,NULL,NULL,true,now(),now()), + ('b4db50d9-8480-4914-9983-d1f7b06ae4e5','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'9a41d67f-10b2-48d8-8e3c-2f20d6744dc2'::uuid,NULL,NULL,true,now(),now()), + ('6851d85a-6bed-42ef-9287-66b6b7e02616','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'76a161e0-40ee-4e6c-8138-8c78cefc0b73'::uuid,NULL,NULL,true,now(),now()), + ('6adc1f68-be6c-438a-959b-396448a69917','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'4e0ef4d5-124a-4f20-b2a4-f6d0d31bf8eb'::uuid,NULL,NULL,true,now(),now()), + ('0d3e608e-8ab9-4be3-a0ee-b91f83ad916d','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'ed1aa4d4-99fd-4a2a-b757-c9d06e04484b'::uuid,NULL,NULL,true,now(),now()), + ('f30c24bf-9111-4ffe-acbe-e9bf53fe8ef3','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'7ff933f8-cede-4bf0-a918-2403d6b902a5'::uuid,NULL,NULL,true,now(),now()), + ('301e01f6-1b04-499c-9d5b-224271487551','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'bdf97313-d164-4d9b-bec3-9aff052fa094'::uuid,NULL,NULL,true,now(),now()); +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('361645a1-3345-40ac-b03d-9a60828018a8','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'d6d7872d-1751-44d0-86e1-217e7e129afe'::uuid,NULL,NULL,true,now(),now()), + ('1a121ea0-1026-4904-a97d-efdc92bc2fab','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'1100147f-9baf-4a12-879e-dd40aadf073b'::uuid,NULL,NULL,true,now(),now()), + ('352d8604-a837-4731-a8d1-92082d8f5576','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'c0abc8fa-f8c6-48e5-a49f-cc5714f6f509'::uuid,NULL,NULL,true,now(),now()), + ('b5dd300a-faa8-4ab7-8276-c9e7e5bf4506','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'9ba33b41-15de-4743-b8f8-75367d0126e7'::uuid,NULL,NULL,true,now(),now()), + ('9450a528-7898-4b73-91a0-573257cf8288','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'3188e86d-944a-42c1-b3b5-3bf7e360fb71'::uuid,NULL,NULL,true,now(),now()), + ('87f6667b-06f3-4beb-9570-2c6b320efb16','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'fc281a01-a7cd-41a0-86d9-716ab4864c08'::uuid,NULL,NULL,true,now(),now()), + ('1f7b2bf7-6bb5-4a80-bacf-4e726150358d','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'fb6d3db2-4079-48fe-9a67-170e200c9a8d'::uuid,NULL,NULL,true,now(),now()), + ('f9a42eaa-a002-4159-a40d-2d45187c95dd','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'3bb35faa-229f-4721-82a2-becc9c98ae04'::uuid,NULL,NULL,true,now(),now()), + ('28298fc7-f185-43fb-9f47-5e64717f7131','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'313905e6-f237-4e1d-ab01-be6dfdddfa92'::uuid,NULL,NULL,true,now(),now()), + ('7ef4d9e8-7647-488a-b803-2dd68a78a487','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'0ed0a302-5016-4ccd-a6ba-5b28258af8dc'::uuid,NULL,NULL,true,now(),now()); +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('e175dc40-d8d4-4f06-a362-ac50c4fe1890','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'846616d3-34e1-4304-b778-3bf8b3052c32'::uuid,NULL,NULL,true,now(),now()), + ('09125d7c-4192-45c4-b60e-1d34d2236dc1','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'f3b6f9da-4bfd-4ac5-a9e2-b4973adf372c'::uuid,NULL,NULL,true,now(),now()), + ('0d08e371-109d-4465-8185-3f3a9d992d14','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'b42c8d98-71b3-464f-b868-6ab743ad7d3a'::uuid,NULL,NULL,true,now(),now()), + ('cc097b12-3720-4b38-9819-32e1c66aae83','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'41e52c48-4b52-4498-b898-22af6fcd5452'::uuid,NULL,NULL,true,now(),now()), + ('5d7c1eff-2565-41e9-ae94-3792726606d7','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'036d9855-ae02-4836-9405-f332d3654838'::uuid,NULL,NULL,true,now(),now()), + ('59738b80-7b6d-4de5-a02f-a03f30e28a21','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'5bad5f3a-861a-448d-8747-66bfc77b9859'::uuid,NULL,NULL,true,now(),now()), + ('5506f869-0bb8-4333-bd92-fb067257287e','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'c7132b2f-8e28-4ee3-af04-d6137f14944a'::uuid,NULL,NULL,true,now(),now()), + ('d085b83d-e5dd-4736-b2de-65024d9a770f','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'05775275-180d-4562-8cd5-9b20403e0824'::uuid,NULL,NULL,true,now(),now()), + ('3d6e0a5e-3f5d-46a5-859c-5d2681277de5','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'5c8029b9-85c3-452b-95cf-00d3d66bda7a'::uuid,NULL,NULL,true,now(),now()), + ('bf0fa527-ac28-4c28-937e-0c9ecd6e6b0a','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'b08f70e8-702b-454a-bc58-b0b10e1b603e'::uuid,NULL,NULL,true,now(),now()); +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('bedd5f6b-0b3b-4cd8-8724-4c20757af6d0','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'20b866a6-d146-439b-b87b-d22412cda747'::uuid,NULL,NULL,true,now(),now()), + ('0d4479de-6229-40ca-a8bf-a844ff6b7653','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'5dde8586-790a-40d6-9a46-e6cd725d1d5e'::uuid,NULL,NULL,true,now(),now()), + ('104c0668-9649-4b05-9224-96677c4313d1','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'fedaa7ae-99a0-4981-9a07-835c0d094e1c'::uuid,NULL,NULL,true,now(),now()), + ('2622a62b-bcf6-49df-a985-da16972b2565','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'73bf3243-c29b-4b4e-9341-5aaf99616e56'::uuid,NULL,NULL,true,now(),now()), + ('03d65ff1-3441-45c9-bb15-353af13566aa','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'3c6ab532-1a18-4f78-9b95-deb1905d2d34'::uuid,NULL,NULL,true,now(),now()), + ('6e2a2921-cd88-4e0f-ba51-ba80a0d11f0b','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'af3a2ad4-585b-4947-bb40-3ae575013ed5'::uuid,NULL,NULL,true,now(),now()), + ('01ff6dc1-080a-44f6-b5dd-a2adbc817c72','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'01e44612-45b9-4964-9de9-843e7a3c0044'::uuid,NULL,NULL,true,now(),now()), + ('f772ac89-acf5-49a5-8539-a35ad5bb406c','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'6a0ce7ea-2daa-4f06-95bf-3c6222c5a8b3'::uuid,NULL,NULL,true,now(),now()), + ('21407cef-1b02-46e1-add0-714241bb0965','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'e164aac9-e9aa-4831-8ef1-98660215705b'::uuid,NULL,NULL,true,now(),now()), + ('82f0ed7b-9c68-41a4-ae6e-932c0aa037e5','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'670a550c-7243-4318-b8d4-e4efcfa32539'::uuid,NULL,NULL,true,now(),now()); +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('b27b3565-7a34-4f76-bb9d-bd4764a3c046','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'e25c2146-7ade-4552-a66d-d3bc948f39c0'::uuid,NULL,NULL,true,now(),now()), + ('f78103a7-62d4-4825-bd47-f2bf8a5f386a','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'d32022a5-f00b-461b-8a3b-0c69df06a94f'::uuid,NULL,NULL,true,now(),now()), + ('4275485b-c5cd-443e-8eab-b25db7aca167','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'66861383-84f0-46c0-9597-c8765152601d'::uuid,NULL,NULL,true,now(),now()), + ('7ea4019d-19dc-4e0e-92e7-149772385368','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'9b52d7a7-8ebe-4cef-a49d-2e03eb87878c'::uuid,NULL,NULL,true,now(),now()), + ('127ed1f9-05bc-401b-ace7-854662eda3e4','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'05c41080-8f3d-41bc-8350-25c9c50dc8bf'::uuid,NULL,NULL,true,now(),now()), + ('aa0a3c08-abb7-4d70-9b80-00eb525b7329','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'97b37859-0f6c-48ff-bec0-403cca95712f'::uuid,NULL,NULL,true,now(),now()), + ('a4d57e99-c781-4bfb-af80-902294551ca0','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'f0ae37b7-bcc1-4107-bdc7-43d21605d0c6'::uuid,NULL,NULL,true,now(),now()), + ('7cb0e3ef-d141-44ad-a6ff-32264d563cdb','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'de8e5560-246b-45bb-a1df-0e9a0f0ab7ad'::uuid,NULL,NULL,true,now(),now()), + ('01729d8d-c001-4a23-9e60-11c8c5d64cbe','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'de85bbee-084e-42d2-ab68-677a927edb2a'::uuid,NULL,NULL,true,now(),now()), + ('ff651ae1-ff18-4e0c-9080-145c444d873e','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'733e38ca-86e1-454f-87a4-884f8b17df21'::uuid,NULL,NULL,true,now(),now()); +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('f130def4-2be3-4f26-81e8-a75381e52ad1','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'8b2f2560-f26c-43d9-98ca-322a3e73b0bb'::uuid,NULL,NULL,true,now(),now()), + ('111ff46a-e1cc-4eaf-9cd4-e33fc021d3d3','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'f5b7df58-dd68-4d24-8f7a-907895dbb151'::uuid,NULL,NULL,true,now(),now()), + ('380e1bb4-0138-4a5b-badf-353a08cd58c5','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'d7843a4e-0c70-466b-b01d-5909b1f62c11'::uuid,NULL,NULL,true,now(),now()), + ('71ef938f-769d-404d-9c2a-a48cf954d1e4','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'8d211696-7337-4c65-be73-9a88049476e4'::uuid,NULL,NULL,true,now(),now()), + ('14774917-4376-4191-a925-23a5322154f2','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'3821b597-e99a-432d-afa0-1aa74b802b94'::uuid,NULL,NULL,true,now(),now()), + ('9f42b8d1-0d5d-43a8-b7b1-bac5b91e6145','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'5918a6de-11ae-43f1-ba68-23cd71da6e7b'::uuid,NULL,NULL,true,now(),now()), + ('ec6cea21-f2b3-4176-8abd-6179ec3f190e','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'76e8e46c-ab0d-4c67-95b6-d00185dd2832'::uuid,NULL,NULL,true,now(),now()), + ('b3377895-ca73-482f-91c3-c986c22b545c','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'7b33ae55-5463-4b57-97e6-a7443a1e0904'::uuid,NULL,NULL,true,now(),now()), + ('c97bb442-5839-4521-b95e-cfecbe4d0125','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'766fcf2c-9145-4f79-b502-d0ffaf96ef17'::uuid,NULL,NULL,true,now(),now()), + ('3305fccd-efac-4717-a1f6-1f31daf0a0a5','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'e1e1bf33-bd47-4ac7-9128-1f5c02d0db7e'::uuid,NULL,NULL,true,now(),now()); +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('e6508bd6-32eb-4f53-8405-57cdef069e78','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'8df33232-52a9-480f-afb8-df27cd099bd2'::uuid,NULL,NULL,true,now(),now()), + ('671d7baa-8b6c-4b71-a3fc-166d138cfaaa','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'8415fff3-ccac-4ae8-82ac-e54f04ca4082'::uuid,NULL,NULL,true,now(),now()), + ('14f91fd9-bc46-439d-b788-6b6553085f80','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'9d2ef3a3-9e55-4797-9b58-35429c3849a8'::uuid,NULL,NULL,true,now(),now()), + ('614c349e-4450-45d6-b14c-86b9aa118c63','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'3e99cabc-75cd-4152-b519-cddfbd4294aa'::uuid,NULL,NULL,true,now(),now()), + ('481b28f2-994e-403e-9391-6352d3f0f0bd','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'616c1ea1-ac8f-4ccf-a28d-80078015ba0e'::uuid,NULL,NULL,true,now(),now()), + ('57eb8a31-2606-4d23-8a23-f7576069a23e','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'e33e15dd-d3aa-480d-a70c-8de943b1dccc'::uuid,NULL,NULL,true,now(),now()), + ('d7e38c2b-b1c2-4d22-9650-397266536d20','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'1bdbc429-b5d6-4483-821e-a31839e63ad2'::uuid,NULL,NULL,true,now(),now()), + ('2e270943-5d8b-445c-8545-8eeb1d8cb8c2','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'0d327bc0-5315-44d1-848c-1f02667a81db'::uuid,NULL,NULL,true,now(),now()), + ('7466d6e1-e3e8-46cc-a6c0-b6dcabea653a','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'7088fd82-8a7f-47c3-a80b-b5255a795f6b'::uuid,NULL,NULL,true,now(),now()), + ('e3f744cf-8a28-4ffb-b177-71aecc96575d','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'54045203-93a8-4011-a9b2-84673cc5176d'::uuid,NULL,NULL,true,now(),now()); +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('209d6646-d917-4e2f-a69a-9ae914cdd129','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'f6b671cf-c344-46ba-bf68-08fcba2ebd84'::uuid,NULL,NULL,true,now(),now()), + ('8dfa8159-1ba0-44d1-a340-c3152dd4679d','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'26e71f17-7713-46d9-a059-5e07978b9b50'::uuid,NULL,NULL,true,now(),now()), + ('fdaa6dde-ebc1-431c-93d9-65963dac2960','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'f7c97962-b0f9-4671-b9d8-06c6195bcde0'::uuid,NULL,NULL,true,now(),now()), + ('5faa2cb2-8dff-4fca-83f9-b5ab22a7296a','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'20e947f3-a7d1-423c-ae79-9886857d5dcc'::uuid,NULL,NULL,true,now(),now()), + ('3946aae4-063f-423d-9932-8b04ca15b931','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'49e0f65d-1545-424d-b1c9-e5e3d0067fe9'::uuid,NULL,NULL,true,now(),now()), + ('b5113518-3412-4062-a91a-775adbe277b6','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'9f0d1cb8-4a5d-42f4-8766-c3ad7f029084'::uuid,NULL,NULL,true,now(),now()), + ('40063586-641d-447a-8932-6862103e0596','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'e4392356-88c7-4841-a415-920aa9cdc7ca'::uuid,NULL,NULL,true,now(),now()), + ('8e33e711-ff66-4a2e-8201-7898ae73ea26','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'f549cddf-ec33-4772-b259-5e12aa2f66b9'::uuid,NULL,NULL,true,now(),now()), + ('90440c26-70b7-4988-b1f7-f69d3abd5eb5','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'788cb14b-30e1-447d-95cc-358e29f01d8d'::uuid,NULL,NULL,true,now(),now()), + ('353b2723-2f0e-4623-8622-2e6071b0b403','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'46d6bc67-b8e5-4e11-834e-eaaa62cad46e'::uuid,NULL,NULL,true,now(),now()); +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('221a4c6c-8084-45c8-8ac3-2474e13eadff','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'67d1f1ab-782a-452f-a607-ba2e6a9a87aa'::uuid,NULL,NULL,true,now(),now()), + ('79d5bfe2-b124-4e81-9a07-b0169eff1b9d','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'51a17659-2fd7-49c3-9971-8fd1e341fcba'::uuid,NULL,NULL,true,now(),now()), + ('2e102552-37d0-4c22-ae7d-b9e931b34dc1','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'646aaf79-c655-443d-a02f-81764d2965c0'::uuid,NULL,NULL,true,now(),now()), + ('0198b34d-5528-4934-bf47-22024ed34e3d','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'ecb1f2a7-525e-490a-8e88-b53a6681aa96'::uuid,NULL,NULL,true,now(),now()), + ('016e0c1e-f890-4372-8968-a4da81c7be7c','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'bf597a56-1728-4325-9734-1138799d6332'::uuid,NULL,NULL,true,now(),now()), + ('47d78518-39ad-455f-8a44-fe974cce5c0e','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'8608f8da-6f62-41ea-8c3d-024c1bcc7daa'::uuid,NULL,NULL,true,now(),now()), + ('03979a62-cf11-4ec4-840b-f3863701b2d0','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'df227441-2e7f-456f-82c5-b4772c7cc5f6'::uuid,NULL,NULL,true,now(),now()), + ('880e545d-a6f1-46ce-a3d9-703a95c55b9c','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'10177e21-4c50-44a6-956b-3eedeac77094'::uuid,NULL,NULL,true,now(),now()), + ('b9a799fb-fffe-4739-bc73-d2232fc08bb3','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'373f38ab-0a7b-40c9-8cc5-0df01d1d87de'::uuid,NULL,NULL,true,now(),now()), + ('f6d8da90-1a28-4dbc-9aca-ff3c1cf838db','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'14b7aa87-7a4e-4e47-b2e4-fa1ed8cdd2a8'::uuid,NULL,NULL,true,now(),now()); +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('910ddece-59c3-482c-ae9b-d23f3478a06b','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'05b15308-4fbd-4e03-ab59-437b9f37e69a'::uuid,NULL,NULL,true,now(),now()), + ('40db8097-6261-4507-9c87-371d8f4ac794','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'b36bffda-b020-4e49-bb6e-971d17116bde'::uuid,NULL,NULL,true,now(),now()), + ('b4bccff5-4836-43f7-9ab9-f8e05b65a528','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'89c8ec3e-af9d-4376-9b47-2a68589abcb4'::uuid,NULL,NULL,true,now(),now()), + ('953326d7-6244-47f0-ac10-a336445eadfe','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'ba61f387-aa0f-434c-aa61-5c15982d4ce4'::uuid,NULL,NULL,true,now(),now()), + ('c02b62dc-e3be-447c-ae20-ef7d419a5757','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'09795f49-0694-4cfb-a711-8aa4edc64ef4'::uuid,NULL,NULL,true,now(),now()), + ('41ed482c-5381-48bb-acbb-f296e238f067','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'e54eb89b-88e8-469d-964e-faad60eafdef'::uuid,NULL,NULL,true,now(),now()), + ('e1f11e3e-e8f4-4fbb-83e2-2a76bee64c24','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'19e2ad86-e55a-4ba5-bae1-f86e922933e3'::uuid,NULL,NULL,true,now(),now()), + ('25750162-53a8-45b1-9008-4b01cdf1afca','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'d1689b93-68e7-44d5-abd5-c3063bc4e670'::uuid,NULL,NULL,true,now(),now()), + ('38e423ae-f98f-409a-b662-1f1b90715b8c','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'64bd4440-9ee4-4a80-bcbd-5701ab160e73'::uuid,NULL,NULL,true,now(),now()), + ('12425939-589e-4ebe-b67d-48dd2a086115','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'00f9ac3e-b9c2-4cf5-af47-6b4799f7333f'::uuid,NULL,NULL,true,now(),now()); +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('816cab83-2e6a-4643-81b8-e6698d68eab0','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'9771ac15-d640-4826-b055-c296696842e8'::uuid,NULL,NULL,true,now(),now()), + ('4653a73a-17e8-4e90-8f0d-19f40025c82e','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'91b89a8b-81f9-4380-b94c-6f7a33f8b4d9'::uuid,NULL,NULL,true,now(),now()), + ('3780c556-4aa0-4ada-a5ef-3ab6c2f9c969','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'5996d817-ab35-4477-8b77-832fe18a1abd'::uuid,NULL,NULL,true,now(),now()), + ('14abdfc1-7c4f-4b48-a60b-da7daf82e00d','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'f1795fad-656d-4600-ad09-05629eba7d43'::uuid,NULL,NULL,true,now(),now()), + ('51cc866b-4d6d-4c16-8b9f-6ebac7d7c45f','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'f899cc7f-96e6-4b26-af04-0d6edb9e01b2'::uuid,NULL,NULL,true,now(),now()), + ('ffe1e0c3-648f-4cb6-a696-a165f31a6c1e','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'97425b50-7fab-4178-94c9-bf913585ac80'::uuid,NULL,NULL,true,now(),now()), + ('74b41148-97d2-4d61-8794-f93e119c9730','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'70f5543b-82cb-4171-9c55-58e479ebaaa1'::uuid,NULL,NULL,true,now(),now()), + ('8e733754-e830-476d-acde-c85a129c2704','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'bae425c4-2ab4-4fdb-8b96-b0654894ebbc'::uuid,NULL,NULL,true,now(),now()), + ('96824049-1a8a-47a7-b9e3-1afd090741bf','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'736ab44f-3497-42f7-891d-845c868e7a29'::uuid,NULL,NULL,true,now(),now()), + ('09c670b5-220a-4ea6-8ddf-5baeea1ce9f2','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'f118e38a-f46d-47d2-879d-c08b84781de8'::uuid,NULL,NULL,true,now(),now()); +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('ef4b04d4-b472-4105-b6c8-00c1f314ab17','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'c74ddc03-0e4b-4645-99d2-7c711b3ab5c4'::uuid,NULL,NULL,true,now(),now()), + ('c9fc02c1-d1a7-48c5-b4eb-3e0598534820','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'7b49bb85-c6bf-4cf3-8726-7256658e2ffb'::uuid,NULL,NULL,true,now(),now()), + ('354777b7-8b85-4e4d-89bf-3152c75d7571','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'bf1b7d0b-6477-4784-9a62-aebb13921de9'::uuid,NULL,NULL,true,now(),now()), + ('68562095-8f5c-4edf-ba21-29d99215ab11','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'2323d966-5d75-46df-b6df-eb43c616ba89'::uuid,NULL,NULL,true,now(),now()), + ('2d810fbe-c240-46f6-ba1e-792c4f25bb47','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'7607c8ed-de3f-414e-ae11-e85fca9bd847'::uuid,NULL,NULL,true,now(),now()), + ('5bece096-75d3-4f26-bf4e-561701fe7ce2','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'c7e39bbe-048a-442a-a8c9-ec58de94caec'::uuid,NULL,NULL,true,now(),now()), + ('bf728fd7-dee8-428c-a753-c91d96dbf26b','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'17a3c0ed-5cfb-4b60-8436-f0843e2d8624'::uuid,NULL,NULL,true,now(),now()), + ('61e2274a-2bd7-422f-94bc-f2beeb1fb84b','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'a3e5aad3-27d5-4e50-853d-504a3f15c08e'::uuid,NULL,NULL,true,now(),now()), + ('a3a5fb63-aa87-464a-a439-7851fcbca270','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'c43a5bf3-0d45-4001-9565-b78a0c59c9d3'::uuid,NULL,NULL,true,now(),now()), + ('24de42d0-4689-4d7d-b94c-8596ae12c7a6','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'919ed6b0-7939-4476-a7a8-6b49b820849c'::uuid,NULL,NULL,true,now(),now()); +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('e611b59a-bd7d-4a84-a12b-d4a96ac2ec47','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'43197d2c-728b-4204-8786-851da9fe9be8'::uuid,NULL,NULL,true,now(),now()), + ('c013b9b9-6d4e-40d9-88d2-332fcf421595','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'ec36f031-4c35-4e24-ba08-83e03846be85'::uuid,NULL,NULL,true,now(),now()), + ('d649b940-429b-443f-b81c-385fb789ac64','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'64f29707-3503-40d0-96b7-6299381f1c60'::uuid,NULL,NULL,true,now(),now()), + ('5f70407d-8c97-43b8-aebf-8b876f55f2b2','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'e7d88312-1da3-42b0-a487-2e2bf623842e'::uuid,NULL,NULL,true,now(),now()), + ('b2f26584-e32d-4eb0-b721-f79e4b986666','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'ccb55592-003a-4135-802f-8d443422bc8b'::uuid,NULL,NULL,true,now(),now()), + ('0a291f21-8ec8-490e-aea3-21ddcaf155fa','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'435c4a00-bc26-4cc3-8dd4-f2eb4e31c895'::uuid,NULL,NULL,true,now(),now()), + ('edc42ed1-66dd-4d85-8db0-3ed3c8b9c694','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'69cc680a-75a9-4b10-9847-51fbaf1caee1'::uuid,NULL,NULL,true,now(),now()), + ('dbdcb0a7-2827-42b3-9ecf-beaaeeccc219','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'391bd5cd-ab81-4eb0-b123-21a410c71cd5'::uuid,NULL,NULL,true,now(),now()), + ('6a8697c6-cb7b-4fb7-95cc-acf6ad909193','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'d6ab4627-4d4f-41db-9baf-fec5f2487651'::uuid,NULL,NULL,true,now(),now()), + ('9d21922c-e067-4aa6-b8a1-0e0d0b30d1ec','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'714c5707-aba2-455d-ad9a-dcf3b0b8b66c'::uuid,NULL,NULL,true,now(),now()); +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('edcdda55-d1cc-4137-bcee-c35403795032','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'05554ee7-30d4-4bb3-bd8c-e0c478f4a037'::uuid,NULL,NULL,true,now(),now()), + ('34cd246e-f618-4204-b761-c6202da4e999','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'f91a9353-16a2-4fbf-83db-54b06bc68381'::uuid,NULL,NULL,true,now(),now()), + ('4cb5c010-6c43-468a-88a7-3e1e4aad1e93','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'bb4c4e1e-828f-4a25-9dc2-81887e5553be'::uuid,NULL,NULL,true,now(),now()), + ('56bed37c-d397-4405-a507-36e48b7be94c','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'fa941444-f49d-4f51-b025-d3c6b63242a7'::uuid,NULL,NULL,true,now(),now()), + ('0198017d-0822-4b77-adac-eb83690d3262','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'eca65b4d-3981-46d9-9e10-a8d20c85c303'::uuid,NULL,NULL,true,now(),now()), + ('97ce133d-103a-47d8-bdd6-f25b24e258fd','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'cad3dad4-4025-478b-b3db-44b2f2886115'::uuid,NULL,NULL,true,now(),now()), + ('bf618f8e-9047-4cb2-afc4-84c756084be7','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'f15f99d4-4a96-4603-8dbf-d3af72be8db0'::uuid,NULL,NULL,true,now(),now()), + ('7fe6d143-070b-48c5-b138-404386a53e79','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'5897d064-bbb0-4ac4-a283-5fa7b91cdfd6'::uuid,NULL,NULL,true,now(),now()), + ('e2321d3b-2648-4cc1-9651-f7b00ff5da11','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'bd4ca5db-cabb-44e7-8fdc-9a5d060161c7'::uuid,NULL,NULL,true,now(),now()), + ('a03bffce-29fe-48e3-baed-e7a42641d663','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'4c286583-5039-4455-bc9f-36ed842f9aa5'::uuid,NULL,NULL,true,now(),now()); +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('0069ba48-0a2a-47f5-b669-b055f95382c3','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'428a26d5-3dc7-44f6-8043-183b0bb60ee0'::uuid,NULL,NULL,true,now(),now()), + ('b4b6187e-e576-43dc-82bb-d2978b6b8e8d','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'86ff314b-0702-478a-aa19-c8cdb2f0fc2f'::uuid,NULL,NULL,true,now(),now()), + ('c51bb0b0-1d56-4dff-bb7c-a3bab342926b','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'1e564ac9-c298-4a6e-b503-02db1459a621'::uuid,NULL,NULL,true,now(),now()), + ('95d14542-3ea7-4263-a5a4-2fb9827ec6ed','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'458174be-38b9-4acb-bd8b-7fb9012cd271'::uuid,NULL,NULL,true,now(),now()), + ('1c872bd1-3d94-4976-9358-701c9ab76bd6','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'0f86385c-8c84-4597-9c4e-3e8272c1df45'::uuid,NULL,NULL,true,now(),now()), + ('1f6f5329-dde1-487b-8b2a-182f0a68e2c1','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'83658135-4bdb-4d86-9359-6733fd7ef3ad'::uuid,NULL,NULL,true,now(),now()), + ('6b861ecd-979b-4f11-b413-97f1ea253215','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'c7e65080-7667-48b9-8794-013cdc1c72ec'::uuid,NULL,NULL,true,now(),now()), + ('1a9c0ae9-f5c4-48f3-bd49-1b1ff202be56','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'80f818a0-1047-466a-a3c1-ce2fc905f205'::uuid,NULL,NULL,true,now(),now()), + ('cf9c449f-e2b3-4716-ae41-48c9a1d605f0','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'13342500-620c-41db-af42-dcecf468ee43'::uuid,NULL,NULL,true,now(),now()), + ('71a21ed8-da0e-461c-9bf7-39945d06068a','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'c2c17c57-3d88-444d-b6ba-69a3d0d734f0'::uuid,NULL,NULL,true,now(),now()); +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('87a10170-ce98-4ddc-bb17-0a7996ae7385','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'950daf0c-0174-4e2f-b03b-05c088520cff'::uuid,NULL,NULL,true,now(),now()), + ('4073c7fa-822b-4f03-b4a6-213e600199ff','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'9133dbe1-d08b-478e-8dbe-109fc05ca427'::uuid,NULL,NULL,true,now(),now()), + ('6f8eacf4-dd13-491e-a3bd-4c1bc49d38bc','97611c2a-3ef6-4883-bf9a-3a7709f2acdd'::uuid,'c1a175cd-c7d1-45ff-a49a-2a6cc4613e9f'::uuid,NULL,NULL,true,now(),now()); diff --git a/pkg/assets/notifications/templates/move_counseled_template.html b/pkg/assets/notifications/templates/move_counseled_template.html index d8c22a243b0..cf60b2410de 100644 --- a/pkg/assets/notifications/templates/move_counseled_template.html +++ b/pkg/assets/notifications/templates/move_counseled_template.html @@ -2,6 +2,10 @@
This is a confirmation that your counselor has approved move details for the assigned move code{{if .Locator}} {{.Locator}}{{end}}{{if .OriginDutyLocation}} from {{.OriginDutyLocation}}{{end}}{{if .DestinationLocation}} to {{.DestinationLocation}}{{end}} in the MilMove system.
+{{if .WeightRestriction}} +Your move has been identified as going to an administratively restricted HHG weight location. Your weight restriction is {{.WeightRestriction}}lbs.
+Be advised, you may be required to pay excess cost if you choose to move more than your weight restriction.
+{{end}}What this means to you: If you are doing a Personally Procured Move (PPM), you can start moving your personal property.
diff --git a/pkg/assets/notifications/templates/move_counseled_template.txt b/pkg/assets/notifications/templates/move_counseled_template.txt index 39aead0ef8a..535c27f66e9 100644 --- a/pkg/assets/notifications/templates/move_counseled_template.txt +++ b/pkg/assets/notifications/templates/move_counseled_template.txt @@ -1,7 +1,9 @@ *** DO NOT REPLY directly to this email *** This is a confirmation that your counselor has approved move details for the assigned move code {{.Locator}}{{if .OriginDutyLocation}} from {{.OriginDutyLocation}}{{end}}{{if .DestinationLocation}} to {{.DestinationLocation}}{{end}} in the MilMove system. - +{{if .WeightRestriction}} +Your move has been identified as going to an administratively restricted HHG weight location. Your weight restriction is {{.WeightRestriction}}lbs. Be advised, you may be required to pay excess cost if you choose to move more than your weight restriction.
+{{end}} What this means to you: If you are doing a Personally Procured Move (PPM), you can start moving your personal property. diff --git a/pkg/assets/notifications/templates/move_submitted_template.html b/pkg/assets/notifications/templates/move_submitted_template.html index cce7d87e8b8..a2a408af88d 100644 --- a/pkg/assets/notifications/templates/move_submitted_template.html +++ b/pkg/assets/notifications/templates/move_submitted_template.html @@ -20,10 +20,11 @@- Your weight allowance: {{.WeightAllowance}} pounds. + Your standard weight allowance: {{.WeightAllowance}} pounds. That is how much combined weight the government will pay for all movements between authorized locations under your - orders. -
+ orders. +Be advised, if you are moving to an administratively restricted HHG weight location this amount could be less.
+If you move more than {{.WeightAllowance}} pounds or ship to/from an other than authorized location, you may owe the diff --git a/pkg/assets/notifications/templates/move_submitted_template.txt b/pkg/assets/notifications/templates/move_submitted_template.txt index 72f1c68566d..85516ead51f 100644 --- a/pkg/assets/notifications/templates/move_submitted_template.txt +++ b/pkg/assets/notifications/templates/move_submitted_template.txt @@ -6,7 +6,7 @@ We have assigned you a move code: {{.Locator}}. You can use this code when talki {{ if .OriginDutyLocationPhoneLine -}} To change any information about your move, or to add or cancel shipments, you should contact {{.OriginDutyLocationPhoneLine}} or visit your local transportation office ({{.OneSourceTransportationOfficeLink}}) . {{- end }} {{- if not .OriginDutyLocationPhoneLine }} To change any information about your move, or to add or cancel shipments, you should contact your nearest transportation office. You can find the contact information using the directory of PCS-related contacts ({{.OneSourceTransportationOfficeLink}}) . {{- end }} -Your weight allowance: {{.WeightAllowance}} pounds. That is how much combined weight the government will pay for all movements between authorized locations under your orders. +Your standard weight allowance: {{.WeightAllowance}} pounds. That is how much combined weight the government will pay for all movements between authorized locations under your orders. Be advised, if you are moving to an administratively restricted HHG weight location this amount could be less. If you move more than {{.WeightAllowance}} pounds or ship to/from an other than authorized location, you may owe the government the difference in cost between what you are authorized and what you decide to move. diff --git a/pkg/assets/sql_scripts/move_history_fetcher.sql b/pkg/assets/sql_scripts/move_history_fetcher.sql index 28f88eab583..fc293e20377 100644 --- a/pkg/assets/sql_scripts/move_history_fetcher.sql +++ b/pkg/assets/sql_scripts/move_history_fetcher.sql @@ -52,14 +52,18 @@ WITH move AS ( 'closeout_office_name', (SELECT transportation_offices.name FROM transportation_offices WHERE transportation_offices.id = uuid(c.closeout_office_id)), 'counseling_office_name', - (SELECT transportation_offices.name FROM transportation_offices WHERE transportation_offices.id = uuid(c.counseling_transportation_office_id)) + (SELECT transportation_offices.name FROM transportation_offices WHERE transportation_offices.id = uuid(c.counseling_transportation_office_id)), + 'assigned_office_user_first_name', + (SELECT office_users.first_name FROM office_users WHERE office_users.id IN (uuid(c.sc_assigned_id), uuid(c.too_assigned_id), uuid(c.tio_assigned_id))), + 'assigned_office_user_last_name', + (SELECT office_users.last_name FROM office_users WHERE office_users.id IN (uuid(c.sc_assigned_id), uuid(c.too_assigned_id), uuid(c.tio_assigned_id))) )) )::TEXT AS context, NULL AS context_id FROM audit_history JOIN move ON audit_history.object_id = move.id - JOIN jsonb_to_record(audit_history.changed_data) as c(closeout_office_id TEXT, counseling_transportation_office_id TEXT) on TRUE + JOIN jsonb_to_record(audit_history.changed_data) as c(closeout_office_id TEXT, counseling_transportation_office_id TEXT, sc_assigned_id TEXT, too_assigned_id TEXT, tio_assigned_id TEXT) ON TRUE WHERE audit_history.table_name = 'moves' -- Remove log for when shipment_seq_num updates AND NOT (audit_history.event_name = NULL AND audit_history.changed_data::TEXT LIKE '%shipment_seq_num%' AND LENGTH(audit_history.changed_data::TEXT) < 25) @@ -212,7 +216,8 @@ WITH move AS ( 'shipment_id', move_shipments.id::TEXT, 'shipment_id_abbr', move_shipments.shipment_id_abbr, 'shipment_type', move_shipments.shipment_type, - 'shipment_locator', move_shipments.shipment_locator + 'shipment_locator', move_shipments.shipment_locator, + 'rejection_reason', payment_service_items.rejection_reason ) )::TEXT AS context, payment_requests.id AS id, @@ -239,6 +244,42 @@ WITH move AS ( JOIN move_payment_requests ON move_payment_requests.id = audit_history.object_id WHERE audit_history.table_name = 'payment_requests' ), + move_payment_service_items AS ( + SELECT + jsonb_agg(jsonb_build_object( + 'name', re_services.name, + 'price', payment_service_items.price_cents::TEXT, + 'status', payment_service_items.status, + 'rejection_reason', payment_service_items.rejection_reason, + 'paid_at', payment_service_items.paid_at, + 'shipment_id', move_shipments.id::TEXT, + 'shipment_id_abbr', move_shipments.shipment_id_abbr, + 'shipment_type', move_shipments.shipment_type, + 'shipment_locator', move_shipments.shipment_locator + ) + )::TEXT AS context, + payment_service_items.id AS id + FROM + payment_requests + JOIN payment_service_items ON payment_service_items.payment_request_id = payment_requests.id + JOIN move_service_items ON move_service_items.id = payment_service_items.mto_service_item_id + LEFT JOIN move_shipments ON move_shipments.id = move_service_items.mto_shipment_id + JOIN re_services ON move_service_items.re_service_id = re_services.id + WHERE + payment_requests.move_id = (SELECT move.id FROM move) + GROUP BY + payment_service_items.id + ), + payment_service_items_logs AS ( + SELECT DISTINCT + audit_history.*, + context AS context, + NULL AS context_id + FROM + audit_history + JOIN move_payment_service_items ON move_payment_service_items.id = audit_history.object_id + WHERE audit_history.table_name = 'payment_service_items' + ), move_proof_of_service_docs AS ( SELECT proof_of_service_docs.*, @@ -717,6 +758,11 @@ WITH move AS ( FROM payment_requests_logs UNION + SELECT + * + FROM + payment_service_items_logs + UNION SELECT * FROM diff --git a/pkg/factory/entitlement_factory.go b/pkg/factory/entitlement_factory.go index 17aff299990..0df88cd9ca4 100644 --- a/pkg/factory/entitlement_factory.go +++ b/pkg/factory/entitlement_factory.go @@ -98,4 +98,5 @@ func BuildEntitlement(db *pop.Connection, customs []Customization, traits []Trai } return entitlement + } diff --git a/pkg/factory/mto_shipment_factory.go b/pkg/factory/mto_shipment_factory.go index 0dc95a166e6..86819917c97 100644 --- a/pkg/factory/mto_shipment_factory.go +++ b/pkg/factory/mto_shipment_factory.go @@ -103,6 +103,10 @@ func buildMTOShipmentWithBuildType(db *pop.Connection, customs []Customization, newMTOShipment.StorageFacilityID = &storageFacility.ID } + if newMTOShipment.ShipmentType == models.MTOShipmentTypeHHGOutOfNTS && newMTOShipment.StorageFacility != nil { + newMTOShipment.PickupAddress = &newMTOShipment.StorageFacility.Address + } + if addPrimeActualWeight { actualWeight := unit.Pound(980) newMTOShipment.PrimeActualWeight = &actualWeight diff --git a/pkg/factory/ppm_shipment_factory.go b/pkg/factory/ppm_shipment_factory.go index 0306aebe547..cc87032285e 100644 --- a/pkg/factory/ppm_shipment_factory.go +++ b/pkg/factory/ppm_shipment_factory.go @@ -87,42 +87,60 @@ func buildPPMShipmentWithBuildType(db *pop.Connection, customs []Customization, ppmShipment.W2Address = &w2AddressResult } - oldDutyLocationAddress := ppmShipment.Shipment.MoveTaskOrder.Orders.OriginDutyLocation.Address - pickupAddress := BuildAddress(db, []Customization{ - { - Model: models.Address{ - StreetAddress1: "987 New Street", - City: oldDutyLocationAddress.City, - State: oldDutyLocationAddress.State, - PostalCode: oldDutyLocationAddress.PostalCode, + pickupAddressResult := findValidCustomization(customs, Addresses.PickupAddress) + if pickupAddressResult != nil { + pickupAddressResultCustoms := convertCustomizationInList(customs, Addresses.PickupAddress, Address) + + pickupAddressResult := BuildAddress(db, pickupAddressResultCustoms, traits) + ppmShipment.PickupAddressID = &pickupAddressResult.ID + ppmShipment.PickupAddress = &pickupAddressResult + } else { + oldDutyLocationAddress := ppmShipment.Shipment.MoveTaskOrder.Orders.OriginDutyLocation.Address + pickupAddress := BuildAddress(db, []Customization{ + { + Model: models.Address{ + StreetAddress1: "987 New Street", + City: oldDutyLocationAddress.City, + State: oldDutyLocationAddress.State, + PostalCode: oldDutyLocationAddress.PostalCode, + }, }, - }, - }, nil) - ppmShipment.PickupAddressID = &pickupAddress.ID - ppmShipment.PickupAddress = &pickupAddress + }, nil) + ppmShipment.PickupAddressID = &pickupAddress.ID + ppmShipment.PickupAddress = &pickupAddress + } - newDutyLocationAddress := ppmShipment.Shipment.MoveTaskOrder.Orders.NewDutyLocation.Address - destinationAddress := BuildAddress(db, []Customization{ - { - Model: models.Address{ - StreetAddress1: "123 New Street", - City: newDutyLocationAddress.City, - State: newDutyLocationAddress.State, - PostalCode: newDutyLocationAddress.PostalCode, + deliveryAddressResult := findValidCustomization(customs, Addresses.DeliveryAddress) + if deliveryAddressResult != nil { + deliveryAddressResultCustoms := convertCustomizationInList(customs, Addresses.DeliveryAddress, Address) + + deliveryAddressResult := BuildAddress(db, deliveryAddressResultCustoms, traits) + ppmShipment.DestinationAddressID = &deliveryAddressResult.ID + ppmShipment.DestinationAddress = &deliveryAddressResult + } else { + newDutyLocationAddress := ppmShipment.Shipment.MoveTaskOrder.Orders.NewDutyLocation.Address + destinationAddress := BuildAddress(db, []Customization{ + { + Model: models.Address{ + StreetAddress1: "123 New Street", + City: newDutyLocationAddress.City, + State: newDutyLocationAddress.State, + PostalCode: newDutyLocationAddress.PostalCode, + }, }, - }, - }, nil) - ppmShipment.DestinationAddressID = &destinationAddress.ID - ppmShipment.DestinationAddress = &destinationAddress + }, nil) + ppmShipment.DestinationAddressID = &destinationAddress.ID + ppmShipment.DestinationAddress = &destinationAddress + } if buildType == ppmBuildFullAddress { secondaryPickupAddress := BuildAddress(db, []Customization{ { Model: models.Address{ StreetAddress1: "123 Main Street", - City: pickupAddress.City, - State: pickupAddress.State, - PostalCode: pickupAddress.PostalCode, + City: ppmShipment.PickupAddress.City, + State: ppmShipment.PickupAddress.State, + PostalCode: ppmShipment.PickupAddress.PostalCode, }, }, }, nil) @@ -130,9 +148,9 @@ func buildPPMShipmentWithBuildType(db *pop.Connection, customs []Customization, { Model: models.Address{ StreetAddress1: "1234 Main Street", - City: destinationAddress.City, - State: destinationAddress.State, - PostalCode: destinationAddress.PostalCode, + City: ppmShipment.DestinationAddress.City, + State: ppmShipment.DestinationAddress.State, + PostalCode: ppmShipment.DestinationAddress.PostalCode, }, }, }, nil) @@ -140,9 +158,9 @@ func buildPPMShipmentWithBuildType(db *pop.Connection, customs []Customization, { Model: models.Address{ StreetAddress1: "123 Third Street", - City: pickupAddress.City, - State: pickupAddress.State, - PostalCode: pickupAddress.PostalCode, + City: ppmShipment.PickupAddress.City, + State: ppmShipment.PickupAddress.State, + PostalCode: ppmShipment.PickupAddress.PostalCode, }, }, }, nil) @@ -150,9 +168,9 @@ func buildPPMShipmentWithBuildType(db *pop.Connection, customs []Customization, { Model: models.Address{ StreetAddress1: "1234 Third Street", - City: destinationAddress.City, - State: destinationAddress.State, - PostalCode: destinationAddress.PostalCode, + City: ppmShipment.DestinationAddress.City, + State: ppmShipment.DestinationAddress.State, + PostalCode: ppmShipment.DestinationAddress.PostalCode, }, }, }, nil) diff --git a/pkg/gen/ghcapi/configure_mymove.go b/pkg/gen/ghcapi/configure_mymove.go index 08d71eb7034..415ee1c890d 100644 --- a/pkg/gen/ghcapi/configure_mymove.go +++ b/pkg/gen/ghcapi/configure_mymove.go @@ -107,6 +107,11 @@ func configureAPI(api *ghcoperations.MymoveAPI) http.Handler { return middleware.NotImplemented("operation shipment.ApproveShipmentDiversion has not yet been implemented") }) } + if api.ShipmentApproveShipmentsHandler == nil { + api.ShipmentApproveShipmentsHandler = shipment.ApproveShipmentsHandlerFunc(func(params shipment.ApproveShipmentsParams) middleware.Responder { + return middleware.NotImplemented("operation shipment.ApproveShipments has not yet been implemented") + }) + } if api.ReportViolationsAssociateReportViolationsHandler == nil { api.ReportViolationsAssociateReportViolationsHandler = report_violations.AssociateReportViolationsHandlerFunc(func(params report_violations.AssociateReportViolationsParams) middleware.Responder { return middleware.NotImplemented("operation report_violations.AssociateReportViolations has not yet been implemented") @@ -477,6 +482,11 @@ func configureAPI(api *ghcoperations.MymoveAPI) http.Handler { return middleware.NotImplemented("operation ppm.ShowAOAPacket has not yet been implemented") }) } + if api.TransportationOfficeShowCounselingOfficesHandler == nil { + api.TransportationOfficeShowCounselingOfficesHandler = transportation_office.ShowCounselingOfficesHandlerFunc(func(params transportation_office.ShowCounselingOfficesParams) middleware.Responder { + return middleware.NotImplemented("operation transportation_office.ShowCounselingOffices has not yet been implemented") + }) + } if api.PpmShowPaymentPacketHandler == nil { api.PpmShowPaymentPacketHandler = ppm.ShowPaymentPacketHandlerFunc(func(params ppm.ShowPaymentPacketParams) middleware.Responder { return middleware.NotImplemented("operation ppm.ShowPaymentPacket has not yet been implemented") diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index 0704324506f..bb69f484f51 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -1516,7 +1516,7 @@ func init() { }, "/move-task-orders/{moveTaskOrderID}/status": { "patch": { - "description": "Changes move task order status to make it available to prime", + "description": "Changes move task order status", "consumes": [ "application/json" ], @@ -1526,7 +1526,7 @@ func init() { "tags": [ "moveTaskOrder" ], - "summary": "Change the status of a move task order to make it available to prime", + "summary": "Change the status of a move task order", "operationId": "updateMoveTaskOrderStatus", "parameters": [ { @@ -4617,6 +4617,12 @@ func init() { "description": "Used to illustrate which user is assigned to this payment request.\n", "name": "assignedTo", "in": "query" + }, + { + "type": "string", + "description": "user's actively logged in role", + "name": "activeRole", + "in": "query" } ], "responses": { @@ -4970,6 +4976,12 @@ func init() { "description": "filters using a counselingOffice name of the move", "name": "counselingOffice", "in": "query" + }, + { + "type": "string", + "description": "user's actively logged in role", + "name": "activeRole", + "in": "query" } ], "responses": { @@ -5124,6 +5136,12 @@ func init() { "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" + }, + { + "type": "string", + "description": "user's actively logged in role", + "name": "activeRole", + "in": "query" } ], "responses": { @@ -5395,6 +5413,64 @@ func init() { } ] }, + "/shipments/approve": { + "post": { + "description": "Approves multiple shipments in one request", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "shipment" + ], + "summary": "Approves multiple shipments at once", + "operationId": "approveShipments", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/ApproveShipments" + } + } + ], + "responses": { + "200": { + "description": "Successfully approved the shipments", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/MTOShipment" + } + } + }, + "403": { + "$ref": "#/responses/PermissionDenied" + }, + "404": { + "$ref": "#/responses/NotFound" + }, + "409": { + "$ref": "#/responses/Conflict" + }, + "412": { + "$ref": "#/responses/PreconditionFailed" + }, + "422": { + "$ref": "#/responses/UnprocessableEntity" + }, + "500": { + "$ref": "#/responses/ServerError" + } + }, + "x-permissions": [ + "update.shipment" + ] + } + }, "/shipments/{shipmentID}": { "get": { "description": "fetches a shipment by ID", @@ -6402,7 +6478,7 @@ func init() { "operationId": "getTransportationOfficesGBLOCs", "responses": { "200": { - "description": "Successfully retrieved transportation offices", + "description": "Successfully retrieved GBLOCs", "schema": { "$ref": "#/definitions/GBLOCs" } @@ -6425,6 +6501,57 @@ func init() { } } }, + "/transportation_offices/{dutyLocationId}/counseling_offices/{serviceMemberId}": { + "get": { + "description": "Returns the counseling locations matching the GBLOC from the selected duty location", + "produces": [ + "application/json" + ], + "tags": [ + "transportationOffice" + ], + "summary": "Returns the counseling locations in the GBLOC matching the duty location", + "operationId": "showCounselingOffices", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "UUID of the duty location", + "name": "dutyLocationId", + "in": "path", + "required": true + }, + { + "type": "string", + "format": "uuid", + "description": "UUID of the service member, some counseling offices are branch specific", + "name": "serviceMemberId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Successfully retrieved counseling offices", + "schema": { + "$ref": "#/definitions/CounselingOffices" + } + }, + "400": { + "$ref": "#/responses/InvalidRequest" + }, + "403": { + "$ref": "#/responses/PermissionDenied" + }, + "404": { + "$ref": "#/responses/NotFound" + }, + "500": { + "description": "internal server error" + } + } + } + }, "/uploads": { "post": { "description": "Uploads represent a single digital file, such as a JPEG or PDF. Currently, office application uploads are only for Services Counselors to upload files for orders, but this may be expanded in the future.", @@ -6896,6 +7023,33 @@ func init() { } } }, + "ApproveShipments": { + "type": "object", + "required": [ + "approveShipments" + ], + "properties": { + "approveShipments": { + "type": "array", + "items": { + "type": "object", + "required": [ + "shipmentID", + "eTag" + ], + "properties": { + "eTag": { + "type": "string" + }, + "shipmentID": { + "type": "string", + "format": "uuid" + } + } + } + } + } + }, "AssignOfficeUserBody": { "type": "object", "required": [ @@ -7142,6 +7296,30 @@ func init() { } } }, + "CounselingOffice": { + "type": "object", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid", + "example": "c56a4180-65aa-42ec-a945-5fd21dec0538" + }, + "name": { + "type": "string", + "example": "Fort Bragg North Station" + } + } + }, + "CounselingOffices": { + "type": "array", + "items": { + "$ref": "#/definitions/CounselingOffice" + } + }, "CounselingUpdateAllowancePayload": { "type": "object", "properties": { @@ -7213,6 +7391,12 @@ func init() { "type": "integer", "x-nullable": true, "example": 500 + }, + "weightRestriction": { + "description": "Indicates the weight restriction for a move to a particular location.", + "type": "integer", + "x-nullable": true, + "example": 1500 } } }, @@ -7748,6 +7932,12 @@ func init() { "x-nullable": true, "example": true }, + "counselingOfficeId": { + "type": "string", + "format": "uuid", + "x-nullable": true, + "example": "cf1addea-a4f9-4173-8506-2bb82a064cb7" + }, "departmentIndicator": { "$ref": "#/definitions/DeptIndicator" }, @@ -8420,6 +8610,12 @@ func init() { "type": "integer", "x-nullable": true, "example": 3 + }, + "weightRestriction": { + "type": "integer", + "x-formatting": "weight", + "x-nullable": true, + "example": 1500 } } }, @@ -10272,6 +10468,15 @@ func init() { "format": "uuid", "x-nullable": true }, + "counselingOffice": { + "$ref": "#/definitions/TransportationOffice" + }, + "counselingOfficeId": { + "description": "The transportation office that will handle services counseling for this move", + "type": "string", + "format": "uuid", + "x-nullable": true + }, "createdAt": { "type": "string", "format": "date-time" @@ -11500,6 +11705,27 @@ func init() { "readOnly": true, "example": "1f2270c7-7166-40ae-981e-b200ebdf3054" }, + "intlLinehaulPrice": { + "description": "The full price of international shipping and linehaul (ISLH)", + "type": "integer", + "format": "cents", + "x-nullable": true, + "x-omitempty": false + }, + "intlPackPrice": { + "description": "The full price of international packing (IHPK)", + "type": "integer", + "format": "cents", + "x-nullable": true, + "x-omitempty": false + }, + "intlUnpackPrice": { + "description": "The full price of international unpacking (IHUPK)", + "type": "integer", + "format": "cents", + "x-nullable": true, + "x-omitempty": false + }, "miles": { "description": "The distance between the old address and the new address in miles.", "type": "integer", @@ -14286,6 +14512,12 @@ func init() { "type": "integer", "x-nullable": true, "example": 500 + }, + "weightRestriction": { + "description": "Indicates the weight restriction for the move to a particular location.", + "type": "integer", + "x-nullable": true, + "example": 1500 } } }, @@ -17564,7 +17796,7 @@ func init() { }, "/move-task-orders/{moveTaskOrderID}/status": { "patch": { - "description": "Changes move task order status to make it available to prime", + "description": "Changes move task order status", "consumes": [ "application/json" ], @@ -17574,7 +17806,7 @@ func init() { "tags": [ "moveTaskOrder" ], - "summary": "Change the status of a move task order to make it available to prime", + "summary": "Change the status of a move task order", "operationId": "updateMoveTaskOrderStatus", "parameters": [ { @@ -21421,6 +21653,12 @@ func init() { "description": "Used to illustrate which user is assigned to this payment request.\n", "name": "assignedTo", "in": "query" + }, + { + "type": "string", + "description": "user's actively logged in role", + "name": "activeRole", + "in": "query" } ], "responses": { @@ -21792,6 +22030,12 @@ func init() { "description": "filters using a counselingOffice name of the move", "name": "counselingOffice", "in": "query" + }, + { + "type": "string", + "description": "user's actively logged in role", + "name": "activeRole", + "in": "query" } ], "responses": { @@ -21952,6 +22196,12 @@ func init() { "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" + }, + { + "type": "string", + "description": "user's actively logged in role", + "name": "activeRole", + "in": "query" } ], "responses": { @@ -22298,6 +22548,82 @@ func init() { } ] }, + "/shipments/approve": { + "post": { + "description": "Approves multiple shipments in one request", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "shipment" + ], + "summary": "Approves multiple shipments at once", + "operationId": "approveShipments", + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/ApproveShipments" + } + } + ], + "responses": { + "200": { + "description": "Successfully approved the shipments", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/MTOShipment" + } + } + }, + "403": { + "description": "The request was denied", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "404": { + "description": "The requested resource wasn't found", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "409": { + "description": "Conflict error", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "412": { + "description": "Precondition failed", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "422": { + "description": "The payload was unprocessable.", + "schema": { + "$ref": "#/definitions/ValidationError" + } + }, + "500": { + "description": "A server error occurred", + "schema": { + "$ref": "#/definitions/Error" + } + } + }, + "x-permissions": [ + "update.shipment" + ] + } + }, "/shipments/{shipmentID}": { "get": { "description": "fetches a shipment by ID", @@ -23575,7 +23901,7 @@ func init() { "operationId": "getTransportationOfficesGBLOCs", "responses": { "200": { - "description": "Successfully retrieved transportation offices", + "description": "Successfully retrieved GBLOCs", "schema": { "$ref": "#/definitions/GBLOCs" } @@ -23613,6 +23939,66 @@ func init() { } } }, + "/transportation_offices/{dutyLocationId}/counseling_offices/{serviceMemberId}": { + "get": { + "description": "Returns the counseling locations matching the GBLOC from the selected duty location", + "produces": [ + "application/json" + ], + "tags": [ + "transportationOffice" + ], + "summary": "Returns the counseling locations in the GBLOC matching the duty location", + "operationId": "showCounselingOffices", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "UUID of the duty location", + "name": "dutyLocationId", + "in": "path", + "required": true + }, + { + "type": "string", + "format": "uuid", + "description": "UUID of the service member, some counseling offices are branch specific", + "name": "serviceMemberId", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Successfully retrieved counseling offices", + "schema": { + "$ref": "#/definitions/CounselingOffices" + } + }, + "400": { + "description": "The request payload is invalid", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "403": { + "description": "The request was denied", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "404": { + "description": "The requested resource wasn't found", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "500": { + "description": "internal server error" + } + } + } + }, "/uploads": { "post": { "description": "Uploads represent a single digital file, such as a JPEG or PDF. Currently, office application uploads are only for Services Counselors to upload files for orders, but this may be expanded in the future.", @@ -24100,6 +24486,36 @@ func init() { } } }, + "ApproveShipments": { + "type": "object", + "required": [ + "approveShipments" + ], + "properties": { + "approveShipments": { + "type": "array", + "items": { + "$ref": "#/definitions/ApproveShipmentsApproveShipmentsItems0" + } + } + } + }, + "ApproveShipmentsApproveShipmentsItems0": { + "type": "object", + "required": [ + "shipmentID", + "eTag" + ], + "properties": { + "eTag": { + "type": "string" + }, + "shipmentID": { + "type": "string", + "format": "uuid" + } + } + }, "AssignOfficeUserBody": { "type": "object", "required": [ @@ -24346,6 +24762,30 @@ func init() { } } }, + "CounselingOffice": { + "type": "object", + "required": [ + "id", + "name" + ], + "properties": { + "id": { + "type": "string", + "format": "uuid", + "example": "c56a4180-65aa-42ec-a945-5fd21dec0538" + }, + "name": { + "type": "string", + "example": "Fort Bragg North Station" + } + } + }, + "CounselingOffices": { + "type": "array", + "items": { + "$ref": "#/definitions/CounselingOffice" + } + }, "CounselingUpdateAllowancePayload": { "type": "object", "properties": { @@ -24421,6 +24861,12 @@ func init() { "type": "integer", "x-nullable": true, "example": 500 + }, + "weightRestriction": { + "description": "Indicates the weight restriction for a move to a particular location.", + "type": "integer", + "x-nullable": true, + "example": 1500 } } }, @@ -24956,6 +25402,12 @@ func init() { "x-nullable": true, "example": true }, + "counselingOfficeId": { + "type": "string", + "format": "uuid", + "x-nullable": true, + "example": "cf1addea-a4f9-4173-8506-2bb82a064cb7" + }, "departmentIndicator": { "$ref": "#/definitions/DeptIndicator" }, @@ -25628,6 +26080,12 @@ func init() { "type": "integer", "x-nullable": true, "example": 3 + }, + "weightRestriction": { + "type": "integer", + "x-formatting": "weight", + "x-nullable": true, + "example": 1500 } } }, @@ -27480,6 +27938,15 @@ func init() { "format": "uuid", "x-nullable": true }, + "counselingOffice": { + "$ref": "#/definitions/TransportationOffice" + }, + "counselingOfficeId": { + "description": "The transportation office that will handle services counseling for this move", + "type": "string", + "format": "uuid", + "x-nullable": true + }, "createdAt": { "type": "string", "format": "date-time" @@ -28708,6 +29175,27 @@ func init() { "readOnly": true, "example": "1f2270c7-7166-40ae-981e-b200ebdf3054" }, + "intlLinehaulPrice": { + "description": "The full price of international shipping and linehaul (ISLH)", + "type": "integer", + "format": "cents", + "x-nullable": true, + "x-omitempty": false + }, + "intlPackPrice": { + "description": "The full price of international packing (IHPK)", + "type": "integer", + "format": "cents", + "x-nullable": true, + "x-omitempty": false + }, + "intlUnpackPrice": { + "description": "The full price of international unpacking (IHUPK)", + "type": "integer", + "format": "cents", + "x-nullable": true, + "x-omitempty": false + }, "miles": { "description": "The distance between the old address and the new address in miles.", "type": "integer", @@ -31626,6 +32114,12 @@ func init() { "type": "integer", "x-nullable": true, "example": 500 + }, + "weightRestriction": { + "description": "Indicates the weight restriction for the move to a particular location.", + "type": "integer", + "x-nullable": true, + "example": 1500 } } }, diff --git a/pkg/gen/ghcapi/ghcoperations/move_task_order/update_move_task_order_status.go b/pkg/gen/ghcapi/ghcoperations/move_task_order/update_move_task_order_status.go index ec8f17920aa..8b51f08902a 100644 --- a/pkg/gen/ghcapi/ghcoperations/move_task_order/update_move_task_order_status.go +++ b/pkg/gen/ghcapi/ghcoperations/move_task_order/update_move_task_order_status.go @@ -32,9 +32,9 @@ func NewUpdateMoveTaskOrderStatus(ctx *middleware.Context, handler UpdateMoveTas /* UpdateMoveTaskOrderStatus swagger:route PATCH /move-task-orders/{moveTaskOrderID}/status moveTaskOrder updateMoveTaskOrderStatus -# Change the status of a move task order to make it available to prime +# Change the status of a move task order -Changes move task order status to make it available to prime +Changes move task order status */ type UpdateMoveTaskOrderStatus struct { Context *middleware.Context diff --git a/pkg/gen/ghcapi/ghcoperations/mymove_api.go b/pkg/gen/ghcapi/ghcoperations/mymove_api.go index 61bda14b7d3..c7668464a5d 100644 --- a/pkg/gen/ghcapi/ghcoperations/mymove_api.go +++ b/pkg/gen/ghcapi/ghcoperations/mymove_api.go @@ -92,6 +92,9 @@ func NewMymoveAPI(spec *loads.Document) *MymoveAPI { ShipmentApproveShipmentDiversionHandler: shipment.ApproveShipmentDiversionHandlerFunc(func(params shipment.ApproveShipmentDiversionParams) middleware.Responder { return middleware.NotImplemented("operation shipment.ApproveShipmentDiversion has not yet been implemented") }), + ShipmentApproveShipmentsHandler: shipment.ApproveShipmentsHandlerFunc(func(params shipment.ApproveShipmentsParams) middleware.Responder { + return middleware.NotImplemented("operation shipment.ApproveShipments has not yet been implemented") + }), ReportViolationsAssociateReportViolationsHandler: report_violations.AssociateReportViolationsHandlerFunc(func(params report_violations.AssociateReportViolationsParams) middleware.Responder { return middleware.NotImplemented("operation report_violations.AssociateReportViolations has not yet been implemented") }), @@ -314,6 +317,9 @@ func NewMymoveAPI(spec *loads.Document) *MymoveAPI { PpmShowAOAPacketHandler: ppm.ShowAOAPacketHandlerFunc(func(params ppm.ShowAOAPacketParams) middleware.Responder { return middleware.NotImplemented("operation ppm.ShowAOAPacket has not yet been implemented") }), + TransportationOfficeShowCounselingOfficesHandler: transportation_office.ShowCounselingOfficesHandlerFunc(func(params transportation_office.ShowCounselingOfficesParams) middleware.Responder { + return middleware.NotImplemented("operation transportation_office.ShowCounselingOffices has not yet been implemented") + }), PpmShowPaymentPacketHandler: ppm.ShowPaymentPacketHandlerFunc(func(params ppm.ShowPaymentPacketParams) middleware.Responder { return middleware.NotImplemented("operation ppm.ShowPaymentPacket has not yet been implemented") }), @@ -458,6 +464,8 @@ type MymoveAPI struct { ShipmentApproveShipmentHandler shipment.ApproveShipmentHandler // ShipmentApproveShipmentDiversionHandler sets the operation handler for the approve shipment diversion operation ShipmentApproveShipmentDiversionHandler shipment.ApproveShipmentDiversionHandler + // ShipmentApproveShipmentsHandler sets the operation handler for the approve shipments operation + ShipmentApproveShipmentsHandler shipment.ApproveShipmentsHandler // ReportViolationsAssociateReportViolationsHandler sets the operation handler for the associate report violations operation ReportViolationsAssociateReportViolationsHandler report_violations.AssociateReportViolationsHandler // PaymentRequestsBulkDownloadHandler sets the operation handler for the bulk download operation @@ -606,6 +614,8 @@ type MymoveAPI struct { MoveSetFinancialReviewFlagHandler move.SetFinancialReviewFlagHandler // PpmShowAOAPacketHandler sets the operation handler for the show a o a packet operation PpmShowAOAPacketHandler ppm.ShowAOAPacketHandler + // TransportationOfficeShowCounselingOfficesHandler sets the operation handler for the show counseling offices operation + TransportationOfficeShowCounselingOfficesHandler transportation_office.ShowCounselingOfficesHandler // PpmShowPaymentPacketHandler sets the operation handler for the show payment packet operation PpmShowPaymentPacketHandler ppm.ShowPaymentPacketHandler // EvaluationReportsSubmitEvaluationReportHandler sets the operation handler for the submit evaluation report operation @@ -766,6 +776,9 @@ func (o *MymoveAPI) Validate() error { if o.ShipmentApproveShipmentDiversionHandler == nil { unregistered = append(unregistered, "shipment.ApproveShipmentDiversionHandler") } + if o.ShipmentApproveShipmentsHandler == nil { + unregistered = append(unregistered, "shipment.ApproveShipmentsHandler") + } if o.ReportViolationsAssociateReportViolationsHandler == nil { unregistered = append(unregistered, "report_violations.AssociateReportViolationsHandler") } @@ -988,6 +1001,9 @@ func (o *MymoveAPI) Validate() error { if o.PpmShowAOAPacketHandler == nil { unregistered = append(unregistered, "ppm.ShowAOAPacketHandler") } + if o.TransportationOfficeShowCounselingOfficesHandler == nil { + unregistered = append(unregistered, "transportation_office.ShowCounselingOfficesHandler") + } if o.PpmShowPaymentPacketHandler == nil { unregistered = append(unregistered, "ppm.ShowPaymentPacketHandler") } @@ -1195,6 +1211,10 @@ func (o *MymoveAPI) initHandlerCache() { if o.handlers["POST"] == nil { o.handlers["POST"] = make(map[string]http.Handler) } + o.handlers["POST"]["/shipments/approve"] = shipment.NewApproveShipments(o.context, o.ShipmentApproveShipmentsHandler) + if o.handlers["POST"] == nil { + o.handlers["POST"] = make(map[string]http.Handler) + } o.handlers["POST"]["/report-violations/{reportID}"] = report_violations.NewAssociateReportViolations(o.context, o.ReportViolationsAssociateReportViolationsHandler) if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) @@ -1491,6 +1511,10 @@ func (o *MymoveAPI) initHandlerCache() { if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) } + o.handlers["GET"]["/transportation_offices/{dutyLocationId}/counseling_offices/{serviceMemberId}"] = transportation_office.NewShowCounselingOffices(o.context, o.TransportationOfficeShowCounselingOfficesHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } o.handlers["GET"]["/ppm-shipments/{ppmShipmentId}/payment-packet"] = ppm.NewShowPaymentPacket(o.context, o.PpmShowPaymentPacketHandler) if o.handlers["POST"] == nil { o.handlers["POST"] = 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 f1cfe32cfae..2123cc801d9 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_parameters.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_parameters.go @@ -34,6 +34,10 @@ type GetMovesQueueParams struct { // HTTP Request Object HTTPRequest *http.Request `json:"-"` + /*user's actively logged in role + In: query + */ + ActiveRole *string /* In: query */ @@ -124,6 +128,11 @@ func (o *GetMovesQueueParams) BindRequest(r *http.Request, route *middleware.Mat qs := runtime.Values(r.URL.Query()) + qActiveRole, qhkActiveRole, _ := qs.GetOK("activeRole") + if err := o.bindActiveRole(qActiveRole, qhkActiveRole, route.Formats); err != nil { + res = append(res, err) + } + qAppearedInTooAt, qhkAppearedInTooAt, _ := qs.GetOK("appearedInTooAt") if err := o.bindAppearedInTooAt(qAppearedInTooAt, qhkAppearedInTooAt, route.Formats); err != nil { res = append(res, err) @@ -219,6 +228,24 @@ func (o *GetMovesQueueParams) BindRequest(r *http.Request, route *middleware.Mat return nil } +// bindActiveRole binds and validates parameter ActiveRole from query. +func (o *GetMovesQueueParams) bindActiveRole(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.ActiveRole = &raw + + return nil +} + // bindAppearedInTooAt binds and validates parameter AppearedInTooAt from query. func (o *GetMovesQueueParams) bindAppearedInTooAt(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_urlbuilder.go index ec05a50a3eb..7d809005df8 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_urlbuilder.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_urlbuilder.go @@ -16,6 +16,7 @@ import ( // GetMovesQueueURL generates an URL for the get moves queue operation type GetMovesQueueURL struct { + ActiveRole *string AppearedInTooAt *strfmt.DateTime AssignedTo *string Branch *string @@ -69,6 +70,14 @@ func (o *GetMovesQueueURL) Build() (*url.URL, error) { qs := make(url.Values) + var activeRoleQ string + if o.ActiveRole != nil { + activeRoleQ = *o.ActiveRole + } + if activeRoleQ != "" { + qs.Set("activeRole", activeRoleQ) + } + var appearedInTooAtQ string if o.AppearedInTooAt != nil { appearedInTooAtQ = o.AppearedInTooAt.String() 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 fe0d201031e..423c3f7eaa6 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 @@ -34,6 +34,10 @@ type GetPaymentRequestsQueueParams struct { // HTTP Request Object HTTPRequest *http.Request `json:"-"` + /*user's actively logged in role + In: query + */ + ActiveRole *string /*Used to illustrate which user is assigned to this payment request. In: query @@ -118,6 +122,11 @@ func (o *GetPaymentRequestsQueueParams) BindRequest(r *http.Request, route *midd qs := runtime.Values(r.URL.Query()) + qActiveRole, qhkActiveRole, _ := qs.GetOK("activeRole") + if err := o.bindActiveRole(qActiveRole, qhkActiveRole, route.Formats); err != nil { + res = append(res, err) + } + qAssignedTo, qhkAssignedTo, _ := qs.GetOK("assignedTo") if err := o.bindAssignedTo(qAssignedTo, qhkAssignedTo, route.Formats); err != nil { res = append(res, err) @@ -208,6 +217,24 @@ func (o *GetPaymentRequestsQueueParams) BindRequest(r *http.Request, route *midd return nil } +// bindActiveRole binds and validates parameter ActiveRole from query. +func (o *GetPaymentRequestsQueueParams) bindActiveRole(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.ActiveRole = &raw + + return nil +} + // bindAssignedTo binds and validates parameter AssignedTo from query. func (o *GetPaymentRequestsQueueParams) bindAssignedTo(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_payment_requests_queue_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/queues/get_payment_requests_queue_urlbuilder.go index 1b5aa0e8b3b..45ac4629c59 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_payment_requests_queue_urlbuilder.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_payment_requests_queue_urlbuilder.go @@ -16,6 +16,7 @@ import ( // GetPaymentRequestsQueueURL generates an URL for the get payment requests queue operation type GetPaymentRequestsQueueURL struct { + ActiveRole *string AssignedTo *string Branch *string CounselingOffice *string @@ -68,6 +69,14 @@ func (o *GetPaymentRequestsQueueURL) Build() (*url.URL, error) { qs := make(url.Values) + var activeRoleQ string + if o.ActiveRole != nil { + activeRoleQ = *o.ActiveRole + } + if activeRoleQ != "" { + qs.Set("activeRole", activeRoleQ) + } + var assignedToQ string if o.AssignedTo != nil { assignedToQ = *o.AssignedTo 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 2b03f53918f..3f596c1f60c 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 @@ -34,6 +34,10 @@ type GetServicesCounselingQueueParams struct { // HTTP Request Object HTTPRequest *http.Request `json:"-"` + /*user's actively logged in role + In: query + */ + ActiveRole *string /*Used to illustrate which user is assigned to this payment request. In: query @@ -148,6 +152,11 @@ func (o *GetServicesCounselingQueueParams) BindRequest(r *http.Request, route *m qs := runtime.Values(r.URL.Query()) + qActiveRole, qhkActiveRole, _ := qs.GetOK("activeRole") + if err := o.bindActiveRole(qActiveRole, qhkActiveRole, route.Formats); err != nil { + res = append(res, err) + } + qAssignedTo, qhkAssignedTo, _ := qs.GetOK("assignedTo") if err := o.bindAssignedTo(qAssignedTo, qhkAssignedTo, route.Formats); err != nil { res = append(res, err) @@ -273,6 +282,24 @@ func (o *GetServicesCounselingQueueParams) BindRequest(r *http.Request, route *m return nil } +// bindActiveRole binds and validates parameter ActiveRole from query. +func (o *GetServicesCounselingQueueParams) bindActiveRole(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.ActiveRole = &raw + + return nil +} + // bindAssignedTo binds and validates parameter AssignedTo from query. func (o *GetServicesCounselingQueueParams) bindAssignedTo(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_urlbuilder.go index d7ad7668c07..b013d4b0089 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_urlbuilder.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_urlbuilder.go @@ -16,6 +16,7 @@ import ( // GetServicesCounselingQueueURL generates an URL for the get services counseling queue operation type GetServicesCounselingQueueURL struct { + ActiveRole *string AssignedTo *string Branch *string CloseoutInitiated *strfmt.DateTime @@ -75,6 +76,14 @@ func (o *GetServicesCounselingQueueURL) Build() (*url.URL, error) { qs := make(url.Values) + var activeRoleQ string + if o.ActiveRole != nil { + activeRoleQ = *o.ActiveRole + } + if activeRoleQ != "" { + qs.Set("activeRole", activeRoleQ) + } + var assignedToQ string if o.AssignedTo != nil { assignedToQ = *o.AssignedTo diff --git a/pkg/gen/ghcapi/ghcoperations/shipment/approve_shipments.go b/pkg/gen/ghcapi/ghcoperations/shipment/approve_shipments.go new file mode 100644 index 00000000000..c441f320f1a --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/shipment/approve_shipments.go @@ -0,0 +1,58 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package shipment + +// 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" +) + +// ApproveShipmentsHandlerFunc turns a function with the right signature into a approve shipments handler +type ApproveShipmentsHandlerFunc func(ApproveShipmentsParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn ApproveShipmentsHandlerFunc) Handle(params ApproveShipmentsParams) middleware.Responder { + return fn(params) +} + +// ApproveShipmentsHandler interface for that can handle valid approve shipments params +type ApproveShipmentsHandler interface { + Handle(ApproveShipmentsParams) middleware.Responder +} + +// NewApproveShipments creates a new http.Handler for the approve shipments operation +func NewApproveShipments(ctx *middleware.Context, handler ApproveShipmentsHandler) *ApproveShipments { + return &ApproveShipments{Context: ctx, Handler: handler} +} + +/* + ApproveShipments swagger:route POST /shipments/approve shipment approveShipments + +# Approves multiple shipments at once + +Approves multiple shipments in one request +*/ +type ApproveShipments struct { + Context *middleware.Context + Handler ApproveShipmentsHandler +} + +func (o *ApproveShipments) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewApproveShipmentsParams() + 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/shipment/approve_shipments_parameters.go b/pkg/gen/ghcapi/ghcoperations/shipment/approve_shipments_parameters.go new file mode 100644 index 00000000000..2c11422365c --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/shipment/approve_shipments_parameters.go @@ -0,0 +1,84 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package shipment + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "io" + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/validate" + + "github.com/transcom/mymove/pkg/gen/ghcmessages" +) + +// NewApproveShipmentsParams creates a new ApproveShipmentsParams object +// +// There are no default values defined in the spec. +func NewApproveShipmentsParams() ApproveShipmentsParams { + + return ApproveShipmentsParams{} +} + +// ApproveShipmentsParams contains all the bound params for the approve shipments operation +// typically these are obtained from a http.Request +// +// swagger:parameters approveShipments +type ApproveShipmentsParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: body + */ + Body *ghcmessages.ApproveShipments +} + +// 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 NewApproveShipmentsParams() beforehand. +func (o *ApproveShipmentsParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + if runtime.HasBody(r) { + defer r.Body.Close() + var body ghcmessages.ApproveShipments + if err := route.Consumer.Consume(r.Body, &body); err != nil { + if err == io.EOF { + res = append(res, errors.Required("body", "body", "")) + } else { + res = append(res, errors.NewParseError("body", "body", "", err)) + } + } else { + // validate body object + if err := body.Validate(route.Formats); err != nil { + res = append(res, err) + } + + ctx := validate.WithOperationRequest(r.Context()) + if err := body.ContextValidate(ctx, route.Formats); err != nil { + res = append(res, err) + } + + if len(res) == 0 { + o.Body = &body + } + } + } else { + res = append(res, errors.Required("body", "body", "")) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/pkg/gen/ghcapi/ghcoperations/shipment/approve_shipments_responses.go b/pkg/gen/ghcapi/ghcoperations/shipment/approve_shipments_responses.go new file mode 100644 index 00000000000..44dd124ba3c --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/shipment/approve_shipments_responses.go @@ -0,0 +1,332 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package shipment + +// 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" +) + +// ApproveShipmentsOKCode is the HTTP code returned for type ApproveShipmentsOK +const ApproveShipmentsOKCode int = 200 + +/* +ApproveShipmentsOK Successfully approved the shipments + +swagger:response approveShipmentsOK +*/ +type ApproveShipmentsOK struct { + + /* + In: Body + */ + Payload []*ghcmessages.MTOShipment `json:"body,omitempty"` +} + +// NewApproveShipmentsOK creates ApproveShipmentsOK with default headers values +func NewApproveShipmentsOK() *ApproveShipmentsOK { + + return &ApproveShipmentsOK{} +} + +// WithPayload adds the payload to the approve shipments o k response +func (o *ApproveShipmentsOK) WithPayload(payload []*ghcmessages.MTOShipment) *ApproveShipmentsOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the approve shipments o k response +func (o *ApproveShipmentsOK) SetPayload(payload []*ghcmessages.MTOShipment) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *ApproveShipmentsOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + payload := o.Payload + if payload == nil { + // return empty array + payload = make([]*ghcmessages.MTOShipment, 0, 50) + } + + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } +} + +// ApproveShipmentsForbiddenCode is the HTTP code returned for type ApproveShipmentsForbidden +const ApproveShipmentsForbiddenCode int = 403 + +/* +ApproveShipmentsForbidden The request was denied + +swagger:response approveShipmentsForbidden +*/ +type ApproveShipmentsForbidden struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewApproveShipmentsForbidden creates ApproveShipmentsForbidden with default headers values +func NewApproveShipmentsForbidden() *ApproveShipmentsForbidden { + + return &ApproveShipmentsForbidden{} +} + +// WithPayload adds the payload to the approve shipments forbidden response +func (o *ApproveShipmentsForbidden) WithPayload(payload *ghcmessages.Error) *ApproveShipmentsForbidden { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the approve shipments forbidden response +func (o *ApproveShipmentsForbidden) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *ApproveShipmentsForbidden) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(403) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// ApproveShipmentsNotFoundCode is the HTTP code returned for type ApproveShipmentsNotFound +const ApproveShipmentsNotFoundCode int = 404 + +/* +ApproveShipmentsNotFound The requested resource wasn't found + +swagger:response approveShipmentsNotFound +*/ +type ApproveShipmentsNotFound struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewApproveShipmentsNotFound creates ApproveShipmentsNotFound with default headers values +func NewApproveShipmentsNotFound() *ApproveShipmentsNotFound { + + return &ApproveShipmentsNotFound{} +} + +// WithPayload adds the payload to the approve shipments not found response +func (o *ApproveShipmentsNotFound) WithPayload(payload *ghcmessages.Error) *ApproveShipmentsNotFound { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the approve shipments not found response +func (o *ApproveShipmentsNotFound) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *ApproveShipmentsNotFound) 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 + } + } +} + +// ApproveShipmentsConflictCode is the HTTP code returned for type ApproveShipmentsConflict +const ApproveShipmentsConflictCode int = 409 + +/* +ApproveShipmentsConflict Conflict error + +swagger:response approveShipmentsConflict +*/ +type ApproveShipmentsConflict struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewApproveShipmentsConflict creates ApproveShipmentsConflict with default headers values +func NewApproveShipmentsConflict() *ApproveShipmentsConflict { + + return &ApproveShipmentsConflict{} +} + +// WithPayload adds the payload to the approve shipments conflict response +func (o *ApproveShipmentsConflict) WithPayload(payload *ghcmessages.Error) *ApproveShipmentsConflict { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the approve shipments conflict response +func (o *ApproveShipmentsConflict) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *ApproveShipmentsConflict) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(409) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// ApproveShipmentsPreconditionFailedCode is the HTTP code returned for type ApproveShipmentsPreconditionFailed +const ApproveShipmentsPreconditionFailedCode int = 412 + +/* +ApproveShipmentsPreconditionFailed Precondition failed + +swagger:response approveShipmentsPreconditionFailed +*/ +type ApproveShipmentsPreconditionFailed struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewApproveShipmentsPreconditionFailed creates ApproveShipmentsPreconditionFailed with default headers values +func NewApproveShipmentsPreconditionFailed() *ApproveShipmentsPreconditionFailed { + + return &ApproveShipmentsPreconditionFailed{} +} + +// WithPayload adds the payload to the approve shipments precondition failed response +func (o *ApproveShipmentsPreconditionFailed) WithPayload(payload *ghcmessages.Error) *ApproveShipmentsPreconditionFailed { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the approve shipments precondition failed response +func (o *ApproveShipmentsPreconditionFailed) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *ApproveShipmentsPreconditionFailed) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(412) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// ApproveShipmentsUnprocessableEntityCode is the HTTP code returned for type ApproveShipmentsUnprocessableEntity +const ApproveShipmentsUnprocessableEntityCode int = 422 + +/* +ApproveShipmentsUnprocessableEntity The payload was unprocessable. + +swagger:response approveShipmentsUnprocessableEntity +*/ +type ApproveShipmentsUnprocessableEntity struct { + + /* + In: Body + */ + Payload *ghcmessages.ValidationError `json:"body,omitempty"` +} + +// NewApproveShipmentsUnprocessableEntity creates ApproveShipmentsUnprocessableEntity with default headers values +func NewApproveShipmentsUnprocessableEntity() *ApproveShipmentsUnprocessableEntity { + + return &ApproveShipmentsUnprocessableEntity{} +} + +// WithPayload adds the payload to the approve shipments unprocessable entity response +func (o *ApproveShipmentsUnprocessableEntity) WithPayload(payload *ghcmessages.ValidationError) *ApproveShipmentsUnprocessableEntity { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the approve shipments unprocessable entity response +func (o *ApproveShipmentsUnprocessableEntity) SetPayload(payload *ghcmessages.ValidationError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *ApproveShipmentsUnprocessableEntity) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(422) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// ApproveShipmentsInternalServerErrorCode is the HTTP code returned for type ApproveShipmentsInternalServerError +const ApproveShipmentsInternalServerErrorCode int = 500 + +/* +ApproveShipmentsInternalServerError A server error occurred + +swagger:response approveShipmentsInternalServerError +*/ +type ApproveShipmentsInternalServerError struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewApproveShipmentsInternalServerError creates ApproveShipmentsInternalServerError with default headers values +func NewApproveShipmentsInternalServerError() *ApproveShipmentsInternalServerError { + + return &ApproveShipmentsInternalServerError{} +} + +// WithPayload adds the payload to the approve shipments internal server error response +func (o *ApproveShipmentsInternalServerError) WithPayload(payload *ghcmessages.Error) *ApproveShipmentsInternalServerError { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the approve shipments internal server error response +func (o *ApproveShipmentsInternalServerError) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *ApproveShipmentsInternalServerError) 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/shipment/approve_shipments_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/shipment/approve_shipments_urlbuilder.go new file mode 100644 index 00000000000..de99aa28fa3 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/shipment/approve_shipments_urlbuilder.go @@ -0,0 +1,87 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package shipment + +// 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" +) + +// ApproveShipmentsURL generates an URL for the approve shipments operation +type ApproveShipmentsURL 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 *ApproveShipmentsURL) WithBasePath(bp string) *ApproveShipmentsURL { + 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 *ApproveShipmentsURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *ApproveShipmentsURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/shipments/approve" + + _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 *ApproveShipmentsURL) 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 *ApproveShipmentsURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *ApproveShipmentsURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on ApproveShipmentsURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on ApproveShipmentsURL") + } + + 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 *ApproveShipmentsURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/pkg/gen/ghcapi/ghcoperations/transportation_office/get_transportation_offices_g_b_l_o_cs_responses.go b/pkg/gen/ghcapi/ghcoperations/transportation_office/get_transportation_offices_g_b_l_o_cs_responses.go index 309de84d0fa..c630be03fd6 100644 --- a/pkg/gen/ghcapi/ghcoperations/transportation_office/get_transportation_offices_g_b_l_o_cs_responses.go +++ b/pkg/gen/ghcapi/ghcoperations/transportation_office/get_transportation_offices_g_b_l_o_cs_responses.go @@ -17,7 +17,7 @@ import ( const GetTransportationOfficesGBLOCsOKCode int = 200 /* -GetTransportationOfficesGBLOCsOK Successfully retrieved transportation offices +GetTransportationOfficesGBLOCsOK Successfully retrieved GBLOCs swagger:response getTransportationOfficesGBLOCsOK */ diff --git a/pkg/gen/ghcapi/ghcoperations/transportation_office/show_counseling_offices.go b/pkg/gen/ghcapi/ghcoperations/transportation_office/show_counseling_offices.go new file mode 100644 index 00000000000..b0cb58981df --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/transportation_office/show_counseling_offices.go @@ -0,0 +1,58 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package transportation_office + +// 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" +) + +// ShowCounselingOfficesHandlerFunc turns a function with the right signature into a show counseling offices handler +type ShowCounselingOfficesHandlerFunc func(ShowCounselingOfficesParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn ShowCounselingOfficesHandlerFunc) Handle(params ShowCounselingOfficesParams) middleware.Responder { + return fn(params) +} + +// ShowCounselingOfficesHandler interface for that can handle valid show counseling offices params +type ShowCounselingOfficesHandler interface { + Handle(ShowCounselingOfficesParams) middleware.Responder +} + +// NewShowCounselingOffices creates a new http.Handler for the show counseling offices operation +func NewShowCounselingOffices(ctx *middleware.Context, handler ShowCounselingOfficesHandler) *ShowCounselingOffices { + return &ShowCounselingOffices{Context: ctx, Handler: handler} +} + +/* + ShowCounselingOffices swagger:route GET /transportation_offices/{dutyLocationId}/counseling_offices/{serviceMemberId} transportationOffice showCounselingOffices + +# Returns the counseling locations in the GBLOC matching the duty location + +Returns the counseling locations matching the GBLOC from the selected duty location +*/ +type ShowCounselingOffices struct { + Context *middleware.Context + Handler ShowCounselingOfficesHandler +} + +func (o *ShowCounselingOffices) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewShowCounselingOfficesParams() + 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/transportation_office/show_counseling_offices_parameters.go b/pkg/gen/ghcapi/ghcoperations/transportation_office/show_counseling_offices_parameters.go new file mode 100644 index 00000000000..91d0a12238c --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/transportation_office/show_counseling_offices_parameters.go @@ -0,0 +1,134 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package transportation_office + +// 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" +) + +// NewShowCounselingOfficesParams creates a new ShowCounselingOfficesParams object +// +// There are no default values defined in the spec. +func NewShowCounselingOfficesParams() ShowCounselingOfficesParams { + + return ShowCounselingOfficesParams{} +} + +// ShowCounselingOfficesParams contains all the bound params for the show counseling offices operation +// typically these are obtained from a http.Request +// +// swagger:parameters showCounselingOffices +type ShowCounselingOfficesParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /*UUID of the duty location + Required: true + In: path + */ + DutyLocationID strfmt.UUID + /*UUID of the service member, some counseling offices are branch specific + Required: true + In: path + */ + ServiceMemberID 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 NewShowCounselingOfficesParams() beforehand. +func (o *ShowCounselingOfficesParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + rDutyLocationID, rhkDutyLocationID, _ := route.Params.GetOK("dutyLocationId") + if err := o.bindDutyLocationID(rDutyLocationID, rhkDutyLocationID, route.Formats); err != nil { + res = append(res, err) + } + + rServiceMemberID, rhkServiceMemberID, _ := route.Params.GetOK("serviceMemberId") + if err := o.bindServiceMemberID(rServiceMemberID, rhkServiceMemberID, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindDutyLocationID binds and validates parameter DutyLocationID from path. +func (o *ShowCounselingOfficesParams) bindDutyLocationID(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("dutyLocationId", "path", "strfmt.UUID", raw) + } + o.DutyLocationID = *(value.(*strfmt.UUID)) + + if err := o.validateDutyLocationID(formats); err != nil { + return err + } + + return nil +} + +// validateDutyLocationID carries on validations for parameter DutyLocationID +func (o *ShowCounselingOfficesParams) validateDutyLocationID(formats strfmt.Registry) error { + + if err := validate.FormatOf("dutyLocationId", "path", "uuid", o.DutyLocationID.String(), formats); err != nil { + return err + } + return nil +} + +// bindServiceMemberID binds and validates parameter ServiceMemberID from path. +func (o *ShowCounselingOfficesParams) bindServiceMemberID(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("serviceMemberId", "path", "strfmt.UUID", raw) + } + o.ServiceMemberID = *(value.(*strfmt.UUID)) + + if err := o.validateServiceMemberID(formats); err != nil { + return err + } + + return nil +} + +// validateServiceMemberID carries on validations for parameter ServiceMemberID +func (o *ShowCounselingOfficesParams) validateServiceMemberID(formats strfmt.Registry) error { + + if err := validate.FormatOf("serviceMemberId", "path", "uuid", o.ServiceMemberID.String(), formats); err != nil { + return err + } + return nil +} diff --git a/pkg/gen/ghcapi/ghcoperations/transportation_office/show_counseling_offices_responses.go b/pkg/gen/ghcapi/ghcoperations/transportation_office/show_counseling_offices_responses.go new file mode 100644 index 00000000000..0e38c7ca905 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/transportation_office/show_counseling_offices_responses.go @@ -0,0 +1,222 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package transportation_office + +// 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" +) + +// ShowCounselingOfficesOKCode is the HTTP code returned for type ShowCounselingOfficesOK +const ShowCounselingOfficesOKCode int = 200 + +/* +ShowCounselingOfficesOK Successfully retrieved counseling offices + +swagger:response showCounselingOfficesOK +*/ +type ShowCounselingOfficesOK struct { + + /* + In: Body + */ + Payload ghcmessages.CounselingOffices `json:"body,omitempty"` +} + +// NewShowCounselingOfficesOK creates ShowCounselingOfficesOK with default headers values +func NewShowCounselingOfficesOK() *ShowCounselingOfficesOK { + + return &ShowCounselingOfficesOK{} +} + +// WithPayload adds the payload to the show counseling offices o k response +func (o *ShowCounselingOfficesOK) WithPayload(payload ghcmessages.CounselingOffices) *ShowCounselingOfficesOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the show counseling offices o k response +func (o *ShowCounselingOfficesOK) SetPayload(payload ghcmessages.CounselingOffices) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *ShowCounselingOfficesOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + payload := o.Payload + if payload == nil { + // return empty array + payload = ghcmessages.CounselingOffices{} + } + + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } +} + +// ShowCounselingOfficesBadRequestCode is the HTTP code returned for type ShowCounselingOfficesBadRequest +const ShowCounselingOfficesBadRequestCode int = 400 + +/* +ShowCounselingOfficesBadRequest The request payload is invalid + +swagger:response showCounselingOfficesBadRequest +*/ +type ShowCounselingOfficesBadRequest struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewShowCounselingOfficesBadRequest creates ShowCounselingOfficesBadRequest with default headers values +func NewShowCounselingOfficesBadRequest() *ShowCounselingOfficesBadRequest { + + return &ShowCounselingOfficesBadRequest{} +} + +// WithPayload adds the payload to the show counseling offices bad request response +func (o *ShowCounselingOfficesBadRequest) WithPayload(payload *ghcmessages.Error) *ShowCounselingOfficesBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the show counseling offices bad request response +func (o *ShowCounselingOfficesBadRequest) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *ShowCounselingOfficesBadRequest) 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 + } + } +} + +// ShowCounselingOfficesForbiddenCode is the HTTP code returned for type ShowCounselingOfficesForbidden +const ShowCounselingOfficesForbiddenCode int = 403 + +/* +ShowCounselingOfficesForbidden The request was denied + +swagger:response showCounselingOfficesForbidden +*/ +type ShowCounselingOfficesForbidden struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewShowCounselingOfficesForbidden creates ShowCounselingOfficesForbidden with default headers values +func NewShowCounselingOfficesForbidden() *ShowCounselingOfficesForbidden { + + return &ShowCounselingOfficesForbidden{} +} + +// WithPayload adds the payload to the show counseling offices forbidden response +func (o *ShowCounselingOfficesForbidden) WithPayload(payload *ghcmessages.Error) *ShowCounselingOfficesForbidden { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the show counseling offices forbidden response +func (o *ShowCounselingOfficesForbidden) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *ShowCounselingOfficesForbidden) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(403) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// ShowCounselingOfficesNotFoundCode is the HTTP code returned for type ShowCounselingOfficesNotFound +const ShowCounselingOfficesNotFoundCode int = 404 + +/* +ShowCounselingOfficesNotFound The requested resource wasn't found + +swagger:response showCounselingOfficesNotFound +*/ +type ShowCounselingOfficesNotFound struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewShowCounselingOfficesNotFound creates ShowCounselingOfficesNotFound with default headers values +func NewShowCounselingOfficesNotFound() *ShowCounselingOfficesNotFound { + + return &ShowCounselingOfficesNotFound{} +} + +// WithPayload adds the payload to the show counseling offices not found response +func (o *ShowCounselingOfficesNotFound) WithPayload(payload *ghcmessages.Error) *ShowCounselingOfficesNotFound { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the show counseling offices not found response +func (o *ShowCounselingOfficesNotFound) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *ShowCounselingOfficesNotFound) 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 + } + } +} + +// ShowCounselingOfficesInternalServerErrorCode is the HTTP code returned for type ShowCounselingOfficesInternalServerError +const ShowCounselingOfficesInternalServerErrorCode int = 500 + +/* +ShowCounselingOfficesInternalServerError internal server error + +swagger:response showCounselingOfficesInternalServerError +*/ +type ShowCounselingOfficesInternalServerError struct { +} + +// NewShowCounselingOfficesInternalServerError creates ShowCounselingOfficesInternalServerError with default headers values +func NewShowCounselingOfficesInternalServerError() *ShowCounselingOfficesInternalServerError { + + return &ShowCounselingOfficesInternalServerError{} +} + +// WriteResponse to the client +func (o *ShowCounselingOfficesInternalServerError) 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/ghcapi/ghcoperations/transportation_office/show_counseling_offices_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/transportation_office/show_counseling_offices_urlbuilder.go new file mode 100644 index 00000000000..269dfc8874e --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/transportation_office/show_counseling_offices_urlbuilder.go @@ -0,0 +1,109 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package transportation_office + +// 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" +) + +// ShowCounselingOfficesURL generates an URL for the show counseling offices operation +type ShowCounselingOfficesURL struct { + DutyLocationID strfmt.UUID + ServiceMemberID 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 *ShowCounselingOfficesURL) WithBasePath(bp string) *ShowCounselingOfficesURL { + 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 *ShowCounselingOfficesURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *ShowCounselingOfficesURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/transportation_offices/{dutyLocationId}/counseling_offices/{serviceMemberId}" + + dutyLocationID := o.DutyLocationID.String() + if dutyLocationID != "" { + _path = strings.Replace(_path, "{dutyLocationId}", dutyLocationID, -1) + } else { + return nil, errors.New("dutyLocationId is required on ShowCounselingOfficesURL") + } + + serviceMemberID := o.ServiceMemberID.String() + if serviceMemberID != "" { + _path = strings.Replace(_path, "{serviceMemberId}", serviceMemberID, -1) + } else { + return nil, errors.New("serviceMemberId is required on ShowCounselingOfficesURL") + } + + _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 *ShowCounselingOfficesURL) 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 *ShowCounselingOfficesURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *ShowCounselingOfficesURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on ShowCounselingOfficesURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on ShowCounselingOfficesURL") + } + + 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 *ShowCounselingOfficesURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/pkg/gen/ghcmessages/approve_shipments.go b/pkg/gen/ghcmessages/approve_shipments.go new file mode 100644 index 00000000000..c86a5252416 --- /dev/null +++ b/pkg/gen/ghcmessages/approve_shipments.go @@ -0,0 +1,202 @@ +// 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" + "github.com/go-openapi/validate" +) + +// ApproveShipments approve shipments +// +// swagger:model ApproveShipments +type ApproveShipments struct { + + // approve shipments + // Required: true + ApproveShipments []*ApproveShipmentsApproveShipmentsItems0 `json:"approveShipments"` +} + +// Validate validates this approve shipments +func (m *ApproveShipments) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateApproveShipments(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ApproveShipments) validateApproveShipments(formats strfmt.Registry) error { + + if err := validate.Required("approveShipments", "body", m.ApproveShipments); err != nil { + return err + } + + for i := 0; i < len(m.ApproveShipments); i++ { + if swag.IsZero(m.ApproveShipments[i]) { // not required + continue + } + + if m.ApproveShipments[i] != nil { + if err := m.ApproveShipments[i].Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("approveShipments" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("approveShipments" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +// ContextValidate validate this approve shipments based on the context it is used +func (m *ApproveShipments) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateApproveShipments(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ApproveShipments) contextValidateApproveShipments(ctx context.Context, formats strfmt.Registry) error { + + for i := 0; i < len(m.ApproveShipments); i++ { + + if m.ApproveShipments[i] != nil { + + if swag.IsZero(m.ApproveShipments[i]) { // not required + return nil + } + + if err := m.ApproveShipments[i].ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("approveShipments" + "." + strconv.Itoa(i)) + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("approveShipments" + "." + strconv.Itoa(i)) + } + return err + } + } + + } + + return nil +} + +// MarshalBinary interface implementation +func (m *ApproveShipments) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ApproveShipments) UnmarshalBinary(b []byte) error { + var res ApproveShipments + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} + +// ApproveShipmentsApproveShipmentsItems0 approve shipments approve shipments items0 +// +// swagger:model ApproveShipmentsApproveShipmentsItems0 +type ApproveShipmentsApproveShipmentsItems0 struct { + + // e tag + // Required: true + ETag *string `json:"eTag"` + + // shipment ID + // Required: true + // Format: uuid + ShipmentID *strfmt.UUID `json:"shipmentID"` +} + +// Validate validates this approve shipments approve shipments items0 +func (m *ApproveShipmentsApproveShipmentsItems0) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateETag(formats); err != nil { + res = append(res, err) + } + + if err := m.validateShipmentID(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *ApproveShipmentsApproveShipmentsItems0) validateETag(formats strfmt.Registry) error { + + if err := validate.Required("eTag", "body", m.ETag); err != nil { + return err + } + + return nil +} + +func (m *ApproveShipmentsApproveShipmentsItems0) validateShipmentID(formats strfmt.Registry) error { + + if err := validate.Required("shipmentID", "body", m.ShipmentID); err != nil { + return err + } + + if err := validate.FormatOf("shipmentID", "body", "uuid", m.ShipmentID.String(), formats); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this approve shipments approve shipments items0 based on context it is used +func (m *ApproveShipmentsApproveShipmentsItems0) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *ApproveShipmentsApproveShipmentsItems0) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *ApproveShipmentsApproveShipmentsItems0) UnmarshalBinary(b []byte) error { + var res ApproveShipmentsApproveShipmentsItems0 + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/gen/ghcmessages/counseling_office.go b/pkg/gen/ghcmessages/counseling_office.go new file mode 100644 index 00000000000..b3bf2fea949 --- /dev/null +++ b/pkg/gen/ghcmessages/counseling_office.go @@ -0,0 +1,95 @@ +// 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" +) + +// CounselingOffice counseling office +// +// swagger:model CounselingOffice +type CounselingOffice struct { + + // id + // Example: c56a4180-65aa-42ec-a945-5fd21dec0538 + // Required: true + // Format: uuid + ID *strfmt.UUID `json:"id"` + + // name + // Example: Fort Bragg North Station + // Required: true + Name *string `json:"name"` +} + +// Validate validates this counseling office +func (m *CounselingOffice) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateName(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *CounselingOffice) 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 *CounselingOffice) validateName(formats strfmt.Registry) error { + + if err := validate.Required("name", "body", m.Name); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this counseling office based on context it is used +func (m *CounselingOffice) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *CounselingOffice) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *CounselingOffice) UnmarshalBinary(b []byte) error { + var res CounselingOffice + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/gen/ghcmessages/counseling_offices.go b/pkg/gen/ghcmessages/counseling_offices.go new file mode 100644 index 00000000000..28a6d79e3b3 --- /dev/null +++ b/pkg/gen/ghcmessages/counseling_offices.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" +) + +// CounselingOffices counseling offices +// +// swagger:model CounselingOffices +type CounselingOffices []*CounselingOffice + +// Validate validates this counseling offices +func (m CounselingOffices) 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 counseling offices based on the context it is used +func (m CounselingOffices) 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/counseling_update_allowance_payload.go b/pkg/gen/ghcmessages/counseling_update_allowance_payload.go index d6bed9fac0c..805a206b000 100644 --- a/pkg/gen/ghcmessages/counseling_update_allowance_payload.go +++ b/pkg/gen/ghcmessages/counseling_update_allowance_payload.go @@ -70,6 +70,10 @@ type CounselingUpdateAllowancePayload struct { // ub allowance // Example: 500 UbAllowance *int64 `json:"ubAllowance,omitempty"` + + // Indicates the weight restriction for a move to a particular location. + // Example: 1500 + WeightRestriction *int64 `json:"weightRestriction,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 25f77f42c15..a71a85a4d74 100644 --- a/pkg/gen/ghcmessages/create_orders.go +++ b/pkg/gen/ghcmessages/create_orders.go @@ -23,6 +23,11 @@ type CreateOrders struct { // Example: true AccompaniedTour *bool `json:"accompaniedTour,omitempty"` + // counseling office Id + // Example: cf1addea-a4f9-4173-8506-2bb82a064cb7 + // Format: uuid + CounselingOfficeID *strfmt.UUID `json:"counselingOfficeId,omitempty"` + // department indicator DepartmentIndicator *DeptIndicator `json:"departmentIndicator,omitempty"` @@ -100,6 +105,10 @@ type CreateOrders struct { func (m *CreateOrders) Validate(formats strfmt.Registry) error { var res []error + if err := m.validateCounselingOfficeID(formats); err != nil { + res = append(res, err) + } + if err := m.validateDepartmentIndicator(formats); err != nil { res = append(res, err) } @@ -150,6 +159,18 @@ func (m *CreateOrders) Validate(formats strfmt.Registry) error { return nil } +func (m *CreateOrders) validateCounselingOfficeID(formats strfmt.Registry) error { + if swag.IsZero(m.CounselingOfficeID) { // not required + return nil + } + + if err := validate.FormatOf("counselingOfficeId", "body", "uuid", m.CounselingOfficeID.String(), formats); err != nil { + return err + } + + return nil +} + func (m *CreateOrders) validateDepartmentIndicator(formats strfmt.Registry) error { if swag.IsZero(m.DepartmentIndicator) { // not required return nil diff --git a/pkg/gen/ghcmessages/entitlements.go b/pkg/gen/ghcmessages/entitlements.go index 2ee15f3d03a..e856534cc33 100644 --- a/pkg/gen/ghcmessages/entitlements.go +++ b/pkg/gen/ghcmessages/entitlements.go @@ -90,6 +90,10 @@ type Entitlements struct { // The amount of weight in pounds that the move is entitled for shipment types of Unaccompanied Baggage. // Example: 3 UnaccompaniedBaggageAllowance *int64 `json:"unaccompaniedBaggageAllowance,omitempty"` + + // weight restriction + // Example: 1500 + WeightRestriction *int64 `json:"weightRestriction,omitempty"` } // Validate validates this entitlements diff --git a/pkg/gen/ghcmessages/move.go b/pkg/gen/ghcmessages/move.go index 26d652a3f42..604dff87fb7 100644 --- a/pkg/gen/ghcmessages/move.go +++ b/pkg/gen/ghcmessages/move.go @@ -61,6 +61,13 @@ type Move struct { // Format: uuid ContractorID *strfmt.UUID `json:"contractorId,omitempty"` + // counseling office + CounselingOffice *TransportationOffice `json:"counselingOffice,omitempty"` + + // The transportation office that will handle services counseling for this move + // Format: uuid + CounselingOfficeID *strfmt.UUID `json:"counselingOfficeId,omitempty"` + // created at // Format: date-time CreatedAt strfmt.DateTime `json:"createdAt,omitempty"` @@ -201,6 +208,14 @@ func (m *Move) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateCounselingOffice(formats); err != nil { + res = append(res, err) + } + + if err := m.validateCounselingOfficeID(formats); err != nil { + res = append(res, err) + } + if err := m.validateCreatedAt(formats); err != nil { res = append(res, err) } @@ -457,6 +472,37 @@ func (m *Move) validateContractorID(formats strfmt.Registry) error { return nil } +func (m *Move) validateCounselingOffice(formats strfmt.Registry) error { + if swag.IsZero(m.CounselingOffice) { // not required + return nil + } + + if m.CounselingOffice != nil { + if err := m.CounselingOffice.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("counselingOffice") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("counselingOffice") + } + return err + } + } + + return nil +} + +func (m *Move) validateCounselingOfficeID(formats strfmt.Registry) error { + if swag.IsZero(m.CounselingOfficeID) { // not required + return nil + } + + if err := validate.FormatOf("counselingOfficeId", "body", "uuid", m.CounselingOfficeID.String(), formats); err != nil { + return err + } + + return nil +} + func (m *Move) validateCreatedAt(formats strfmt.Registry) error { if swag.IsZero(m.CreatedAt) { // not required return nil @@ -701,6 +747,10 @@ func (m *Move) ContextValidate(ctx context.Context, formats strfmt.Registry) err res = append(res, err) } + if err := m.contextValidateCounselingOffice(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateFinancialReviewFlag(ctx, formats); err != nil { res = append(res, err) } @@ -857,6 +907,27 @@ func (m *Move) contextValidateContractor(ctx context.Context, formats strfmt.Reg return nil } +func (m *Move) contextValidateCounselingOffice(ctx context.Context, formats strfmt.Registry) error { + + if m.CounselingOffice != nil { + + if swag.IsZero(m.CounselingOffice) { // not required + return nil + } + + if err := m.CounselingOffice.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("counselingOffice") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("counselingOffice") + } + return err + } + } + + return nil +} + func (m *Move) contextValidateFinancialReviewFlag(ctx context.Context, formats strfmt.Registry) error { if err := validate.ReadOnly(ctx, "financialReviewFlag", "body", bool(m.FinancialReviewFlag)); err != nil { diff --git a/pkg/gen/ghcmessages/p_p_m_closeout.go b/pkg/gen/ghcmessages/p_p_m_closeout.go index b0f423ba61a..a84e0e4c2e0 100644 --- a/pkg/gen/ghcmessages/p_p_m_closeout.go +++ b/pkg/gen/ghcmessages/p_p_m_closeout.go @@ -69,6 +69,15 @@ type PPMCloseout struct { // Format: uuid ID strfmt.UUID `json:"id"` + // The full price of international shipping and linehaul (ISLH) + IntlLinehaulPrice *int64 `json:"intlLinehaulPrice"` + + // The full price of international packing (IHPK) + IntlPackPrice *int64 `json:"intlPackPrice"` + + // The full price of international unpacking (IHUPK) + IntlUnpackPrice *int64 `json:"intlUnpackPrice"` + // The distance between the old address and the new address in miles. // Example: 54 // Minimum: 0 diff --git a/pkg/gen/ghcmessages/update_allowance_payload.go b/pkg/gen/ghcmessages/update_allowance_payload.go index ee0d677c0e7..c0aa957934a 100644 --- a/pkg/gen/ghcmessages/update_allowance_payload.go +++ b/pkg/gen/ghcmessages/update_allowance_payload.go @@ -70,6 +70,10 @@ type UpdateAllowancePayload struct { // ub allowance // Example: 500 UbAllowance *int64 `json:"ubAllowance,omitempty"` + + // Indicates the weight restriction for the move to a particular location. + // Example: 1500 + WeightRestriction *int64 `json:"weightRestriction,omitempty"` } // Validate validates this update allowance payload diff --git a/pkg/gen/internalapi/embedded_spec.go b/pkg/gen/internalapi/embedded_spec.go index c872ff075a7..35d92886247 100644 --- a/pkg/gen/internalapi/embedded_spec.go +++ b/pkg/gen/internalapi/embedded_spec.go @@ -4486,6 +4486,12 @@ func init() { "type": "integer", "x-nullable": true, "example": 3 + }, + "weight_restriction": { + "description": "Indicates the weight restricted to a specific location.", + "type": "integer", + "x-nullable": true, + "example": 1500 } } }, @@ -6037,6 +6043,10 @@ func init() { }, "uploaded_orders": { "$ref": "#/definitions/Document" + }, + "weightRestriction": { + "type": "integer", + "x-nullable": true } } }, @@ -13615,6 +13625,12 @@ func init() { "type": "integer", "x-nullable": true, "example": 3 + }, + "weight_restriction": { + "description": "Indicates the weight restricted to a specific location.", + "type": "integer", + "x-nullable": true, + "example": 1500 } } }, @@ -15168,6 +15184,10 @@ func init() { }, "uploaded_orders": { "$ref": "#/definitions/Document" + }, + "weightRestriction": { + "type": "integer", + "x-nullable": true } } }, diff --git a/pkg/gen/internalmessages/entitlement.go b/pkg/gen/internalmessages/entitlement.go index 703b403758f..083ea350085 100644 --- a/pkg/gen/internalmessages/entitlement.go +++ b/pkg/gen/internalmessages/entitlement.go @@ -42,6 +42,10 @@ type Entitlement struct { // 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"` + + // Indicates the weight restricted to a specific location. + // Example: 1500 + WeightRestriction *int64 `json:"weight_restriction,omitempty"` } // Validate validates this entitlement diff --git a/pkg/gen/internalmessages/orders.go b/pkg/gen/internalmessages/orders.go index 6a789163d32..f36800d8445 100644 --- a/pkg/gen/internalmessages/orders.go +++ b/pkg/gen/internalmessages/orders.go @@ -127,6 +127,9 @@ type Orders struct { // uploaded orders // Required: true UploadedOrders *Document `json:"uploaded_orders"` + + // weight restriction + WeightRestriction *int64 `json:"weightRestriction,omitempty"` } // Validate validates this orders diff --git a/pkg/gen/primeapi/embedded_spec.go b/pkg/gen/primeapi/embedded_spec.go index ab7b9225ce4..01c7e0976c2 100644 --- a/pkg/gen/primeapi/embedded_spec.go +++ b/pkg/gen/primeapi/embedded_spec.go @@ -1828,6 +1828,12 @@ func init() { "type": "integer", "x-nullable": true, "example": 3 + }, + "weightRestriction": { + "type": "integer", + "x-formatting": "weight", + "x-nullable": true, + "example": 1500 } } }, @@ -2324,6 +2330,50 @@ func init() { } ] }, + "MTOServiceItemDomesticShuttle": { + "description": "Describes a domestic shuttle service item.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reason", + "reServiceCode" + ], + "properties": { + "actualWeight": { + "description": "A record of the actual weight that was shuttled. Provided by the movers, based on weight tickets.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4000 + }, + "estimatedWeight": { + "description": "An estimate of how much weight from a shipment will be included in the shuttling service.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4200 + }, + "reServiceCode": { + "description": "A unique code for the service item. Indicates if shuttling is requested for the shipment origin (` + "`" + `DOSHUT` + "`" + `) or destination (` + "`" + `DDSHUT` + "`" + `).\n", + "type": "string", + "enum": [ + "DOSHUT", + "DDSHUT" + ] + }, + "reason": { + "description": "The contractor's explanation for why a shuttle service is requested. Used by the TOO while deciding to approve or reject the service item.\n", + "type": "string", + "example": "Storage items need to be picked up." + } + } + } + ] + }, "MTOServiceItemInternationalCrating": { "description": "Describes a international crating/uncrating service item subtype of a MTOServiceItem.", "allOf": [ @@ -2480,13 +2530,14 @@ func init() { ] }, "MTOServiceItemModelType": { - "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", + "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DOSHUT, DDSHUT - MTOServiceItemDomesticShuttle\n * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "MTOServiceItemBasic", "MTOServiceItemOriginSIT", "MTOServiceItemDestSIT", "MTOServiceItemShuttle", + "MTOServiceItemDomesticShuttle", "MTOServiceItemInternationalShuttle", "MTOServiceItemDomesticCrating", "MTOServiceItemInternationalCrating", @@ -6899,6 +6950,12 @@ func init() { "type": "integer", "x-nullable": true, "example": 3 + }, + "weightRestriction": { + "type": "integer", + "x-formatting": "weight", + "x-nullable": true, + "example": 1500 } } }, @@ -7395,6 +7452,50 @@ func init() { } ] }, + "MTOServiceItemDomesticShuttle": { + "description": "Describes a domestic shuttle service item.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reason", + "reServiceCode" + ], + "properties": { + "actualWeight": { + "description": "A record of the actual weight that was shuttled. Provided by the movers, based on weight tickets.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4000 + }, + "estimatedWeight": { + "description": "An estimate of how much weight from a shipment will be included in the shuttling service.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4200 + }, + "reServiceCode": { + "description": "A unique code for the service item. Indicates if shuttling is requested for the shipment origin (` + "`" + `DOSHUT` + "`" + `) or destination (` + "`" + `DDSHUT` + "`" + `).\n", + "type": "string", + "enum": [ + "DOSHUT", + "DDSHUT" + ] + }, + "reason": { + "description": "The contractor's explanation for why a shuttle service is requested. Used by the TOO while deciding to approve or reject the service item.\n", + "type": "string", + "example": "Storage items need to be picked up." + } + } + } + ] + }, "MTOServiceItemInternationalCrating": { "description": "Describes a international crating/uncrating service item subtype of a MTOServiceItem.", "allOf": [ @@ -7551,13 +7652,14 @@ func init() { ] }, "MTOServiceItemModelType": { - "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", + "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DOSHUT, DDSHUT - MTOServiceItemDomesticShuttle\n * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "MTOServiceItemBasic", "MTOServiceItemOriginSIT", "MTOServiceItemDestSIT", "MTOServiceItemShuttle", + "MTOServiceItemDomesticShuttle", "MTOServiceItemInternationalShuttle", "MTOServiceItemDomesticCrating", "MTOServiceItemInternationalCrating", diff --git a/pkg/gen/primemessages/entitlements.go b/pkg/gen/primemessages/entitlements.go index c51ada24273..65870bfa8e6 100644 --- a/pkg/gen/primemessages/entitlements.go +++ b/pkg/gen/primemessages/entitlements.go @@ -79,6 +79,10 @@ type Entitlements struct { // The amount of weight in pounds that the move is entitled for shipment types of Unaccompanied Baggage. // Example: 3 UnaccompaniedBaggageAllowance *int64 `json:"unaccompaniedBaggageAllowance,omitempty"` + + // weight restriction + // Example: 1500 + WeightRestriction *int64 `json:"weightRestriction,omitempty"` } // Validate validates this entitlements diff --git a/pkg/gen/primemessages/m_t_o_service_item.go b/pkg/gen/primemessages/m_t_o_service_item.go index 5ed0f248ae0..028e219d5df 100644 --- a/pkg/gen/primemessages/m_t_o_service_item.go +++ b/pkg/gen/primemessages/m_t_o_service_item.go @@ -273,6 +273,12 @@ func unmarshalMTOServiceItem(data []byte, consumer runtime.Consumer) (MTOService return nil, err } return &result, nil + case "MTOServiceItemDomesticShuttle": + var result MTOServiceItemDomesticShuttle + if err := consumer.Consume(buf2, &result); err != nil { + return nil, err + } + return &result, nil case "MTOServiceItemInternationalCrating": var result MTOServiceItemInternationalCrating if err := consumer.Consume(buf2, &result); err != nil { diff --git a/pkg/gen/primemessages/m_t_o_service_item_domestic_shuttle.go b/pkg/gen/primemessages/m_t_o_service_item_domestic_shuttle.go new file mode 100644 index 00000000000..1d5e28daaae --- /dev/null +++ b/pkg/gen/primemessages/m_t_o_service_item_domestic_shuttle.go @@ -0,0 +1,633 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package primemessages + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "bytes" + "context" + "encoding/json" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// MTOServiceItemDomesticShuttle Describes a domestic shuttle service item. +// +// swagger:model MTOServiceItemDomesticShuttle +type MTOServiceItemDomesticShuttle struct { + eTagField string + + idField strfmt.UUID + + lockedPriceCentsField *int64 + + moveTaskOrderIdField *strfmt.UUID + + mtoShipmentIdField strfmt.UUID + + reServiceNameField string + + rejectionReasonField *string + + serviceRequestDocumentsField ServiceRequestDocuments + + statusField MTOServiceItemStatus + + // A record of the actual weight that was shuttled. Provided by the movers, based on weight tickets. + // Example: 4000 + ActualWeight *int64 `json:"actualWeight"` + + // An estimate of how much weight from a shipment will be included in the shuttling service. + // Example: 4200 + EstimatedWeight *int64 `json:"estimatedWeight"` + + // A unique code for the service item. Indicates if shuttling is requested for the shipment origin (`DOSHUT`) or destination (`DDSHUT`). + // + // Required: true + // Enum: [DOSHUT DDSHUT] + ReServiceCode *string `json:"reServiceCode"` + + // The contractor's explanation for why a shuttle service is requested. Used by the TOO while deciding to approve or reject the service item. + // + // Example: Storage items need to be picked up. + // Required: true + Reason *string `json:"reason"` +} + +// ETag gets the e tag of this subtype +func (m *MTOServiceItemDomesticShuttle) ETag() string { + return m.eTagField +} + +// SetETag sets the e tag of this subtype +func (m *MTOServiceItemDomesticShuttle) SetETag(val string) { + m.eTagField = val +} + +// ID gets the id of this subtype +func (m *MTOServiceItemDomesticShuttle) ID() strfmt.UUID { + return m.idField +} + +// SetID sets the id of this subtype +func (m *MTOServiceItemDomesticShuttle) SetID(val strfmt.UUID) { + m.idField = val +} + +// LockedPriceCents gets the locked price cents of this subtype +func (m *MTOServiceItemDomesticShuttle) LockedPriceCents() *int64 { + return m.lockedPriceCentsField +} + +// SetLockedPriceCents sets the locked price cents of this subtype +func (m *MTOServiceItemDomesticShuttle) SetLockedPriceCents(val *int64) { + m.lockedPriceCentsField = val +} + +// ModelType gets the model type of this subtype +func (m *MTOServiceItemDomesticShuttle) ModelType() MTOServiceItemModelType { + return "MTOServiceItemDomesticShuttle" +} + +// SetModelType sets the model type of this subtype +func (m *MTOServiceItemDomesticShuttle) SetModelType(val MTOServiceItemModelType) { +} + +// MoveTaskOrderID gets the move task order ID of this subtype +func (m *MTOServiceItemDomesticShuttle) MoveTaskOrderID() *strfmt.UUID { + return m.moveTaskOrderIdField +} + +// SetMoveTaskOrderID sets the move task order ID of this subtype +func (m *MTOServiceItemDomesticShuttle) SetMoveTaskOrderID(val *strfmt.UUID) { + m.moveTaskOrderIdField = val +} + +// MtoShipmentID gets the mto shipment ID of this subtype +func (m *MTOServiceItemDomesticShuttle) MtoShipmentID() strfmt.UUID { + return m.mtoShipmentIdField +} + +// SetMtoShipmentID sets the mto shipment ID of this subtype +func (m *MTOServiceItemDomesticShuttle) SetMtoShipmentID(val strfmt.UUID) { + m.mtoShipmentIdField = val +} + +// ReServiceName gets the re service name of this subtype +func (m *MTOServiceItemDomesticShuttle) ReServiceName() string { + return m.reServiceNameField +} + +// SetReServiceName sets the re service name of this subtype +func (m *MTOServiceItemDomesticShuttle) SetReServiceName(val string) { + m.reServiceNameField = val +} + +// RejectionReason gets the rejection reason of this subtype +func (m *MTOServiceItemDomesticShuttle) RejectionReason() *string { + return m.rejectionReasonField +} + +// SetRejectionReason sets the rejection reason of this subtype +func (m *MTOServiceItemDomesticShuttle) SetRejectionReason(val *string) { + m.rejectionReasonField = val +} + +// ServiceRequestDocuments gets the service request documents of this subtype +func (m *MTOServiceItemDomesticShuttle) ServiceRequestDocuments() ServiceRequestDocuments { + return m.serviceRequestDocumentsField +} + +// SetServiceRequestDocuments sets the service request documents of this subtype +func (m *MTOServiceItemDomesticShuttle) SetServiceRequestDocuments(val ServiceRequestDocuments) { + m.serviceRequestDocumentsField = val +} + +// Status gets the status of this subtype +func (m *MTOServiceItemDomesticShuttle) Status() MTOServiceItemStatus { + return m.statusField +} + +// SetStatus sets the status of this subtype +func (m *MTOServiceItemDomesticShuttle) SetStatus(val MTOServiceItemStatus) { + m.statusField = val +} + +// UnmarshalJSON unmarshals this object with a polymorphic type from a JSON structure +func (m *MTOServiceItemDomesticShuttle) UnmarshalJSON(raw []byte) error { + var data struct { + + // A record of the actual weight that was shuttled. Provided by the movers, based on weight tickets. + // Example: 4000 + ActualWeight *int64 `json:"actualWeight"` + + // An estimate of how much weight from a shipment will be included in the shuttling service. + // Example: 4200 + EstimatedWeight *int64 `json:"estimatedWeight"` + + // A unique code for the service item. Indicates if shuttling is requested for the shipment origin (`DOSHUT`) or destination (`DDSHUT`). + // + // Required: true + // Enum: [DOSHUT DDSHUT] + ReServiceCode *string `json:"reServiceCode"` + + // The contractor's explanation for why a shuttle service is requested. Used by the TOO while deciding to approve or reject the service item. + // + // Example: Storage items need to be picked up. + // Required: true + Reason *string `json:"reason"` + } + buf := bytes.NewBuffer(raw) + dec := json.NewDecoder(buf) + dec.UseNumber() + + if err := dec.Decode(&data); err != nil { + return err + } + + var base struct { + /* Just the base type fields. Used for unmashalling polymorphic types.*/ + + ETag string `json:"eTag,omitempty"` + + ID strfmt.UUID `json:"id,omitempty"` + + LockedPriceCents *int64 `json:"lockedPriceCents,omitempty"` + + ModelType MTOServiceItemModelType `json:"modelType"` + + MoveTaskOrderID *strfmt.UUID `json:"moveTaskOrderID"` + + MtoShipmentID strfmt.UUID `json:"mtoShipmentID,omitempty"` + + ReServiceName string `json:"reServiceName,omitempty"` + + RejectionReason *string `json:"rejectionReason,omitempty"` + + ServiceRequestDocuments ServiceRequestDocuments `json:"serviceRequestDocuments,omitempty"` + + Status MTOServiceItemStatus `json:"status,omitempty"` + } + buf = bytes.NewBuffer(raw) + dec = json.NewDecoder(buf) + dec.UseNumber() + + if err := dec.Decode(&base); err != nil { + return err + } + + var result MTOServiceItemDomesticShuttle + + result.eTagField = base.ETag + + result.idField = base.ID + + result.lockedPriceCentsField = base.LockedPriceCents + + if base.ModelType != result.ModelType() { + /* Not the type we're looking for. */ + return errors.New(422, "invalid modelType value: %q", base.ModelType) + } + result.moveTaskOrderIdField = base.MoveTaskOrderID + + result.mtoShipmentIdField = base.MtoShipmentID + + result.reServiceNameField = base.ReServiceName + + result.rejectionReasonField = base.RejectionReason + + result.serviceRequestDocumentsField = base.ServiceRequestDocuments + + result.statusField = base.Status + + result.ActualWeight = data.ActualWeight + result.EstimatedWeight = data.EstimatedWeight + result.ReServiceCode = data.ReServiceCode + result.Reason = data.Reason + + *m = result + + return nil +} + +// MarshalJSON marshals this object with a polymorphic type to a JSON structure +func (m MTOServiceItemDomesticShuttle) MarshalJSON() ([]byte, error) { + var b1, b2, b3 []byte + var err error + b1, err = json.Marshal(struct { + + // A record of the actual weight that was shuttled. Provided by the movers, based on weight tickets. + // Example: 4000 + ActualWeight *int64 `json:"actualWeight"` + + // An estimate of how much weight from a shipment will be included in the shuttling service. + // Example: 4200 + EstimatedWeight *int64 `json:"estimatedWeight"` + + // A unique code for the service item. Indicates if shuttling is requested for the shipment origin (`DOSHUT`) or destination (`DDSHUT`). + // + // Required: true + // Enum: [DOSHUT DDSHUT] + ReServiceCode *string `json:"reServiceCode"` + + // The contractor's explanation for why a shuttle service is requested. Used by the TOO while deciding to approve or reject the service item. + // + // Example: Storage items need to be picked up. + // Required: true + Reason *string `json:"reason"` + }{ + + ActualWeight: m.ActualWeight, + + EstimatedWeight: m.EstimatedWeight, + + ReServiceCode: m.ReServiceCode, + + Reason: m.Reason, + }) + if err != nil { + return nil, err + } + b2, err = json.Marshal(struct { + ETag string `json:"eTag,omitempty"` + + ID strfmt.UUID `json:"id,omitempty"` + + LockedPriceCents *int64 `json:"lockedPriceCents,omitempty"` + + ModelType MTOServiceItemModelType `json:"modelType"` + + MoveTaskOrderID *strfmt.UUID `json:"moveTaskOrderID"` + + MtoShipmentID strfmt.UUID `json:"mtoShipmentID,omitempty"` + + ReServiceName string `json:"reServiceName,omitempty"` + + RejectionReason *string `json:"rejectionReason,omitempty"` + + ServiceRequestDocuments ServiceRequestDocuments `json:"serviceRequestDocuments,omitempty"` + + Status MTOServiceItemStatus `json:"status,omitempty"` + }{ + + ETag: m.ETag(), + + ID: m.ID(), + + LockedPriceCents: m.LockedPriceCents(), + + ModelType: m.ModelType(), + + MoveTaskOrderID: m.MoveTaskOrderID(), + + MtoShipmentID: m.MtoShipmentID(), + + ReServiceName: m.ReServiceName(), + + RejectionReason: m.RejectionReason(), + + ServiceRequestDocuments: m.ServiceRequestDocuments(), + + Status: m.Status(), + }) + if err != nil { + return nil, err + } + + return swag.ConcatJSON(b1, b2, b3), nil +} + +// Validate validates this m t o service item domestic shuttle +func (m *MTOServiceItemDomesticShuttle) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMoveTaskOrderID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMtoShipmentID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateServiceRequestDocuments(formats); err != nil { + res = append(res, err) + } + + if err := m.validateStatus(formats); err != nil { + res = append(res, err) + } + + if err := m.validateReServiceCode(formats); err != nil { + res = append(res, err) + } + + if err := m.validateReason(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateID(formats strfmt.Registry) error { + + if swag.IsZero(m.ID()) { // not required + return nil + } + + if err := validate.FormatOf("id", "body", "uuid", m.ID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateMoveTaskOrderID(formats strfmt.Registry) error { + + if err := validate.Required("moveTaskOrderID", "body", m.MoveTaskOrderID()); err != nil { + return err + } + + if err := validate.FormatOf("moveTaskOrderID", "body", "uuid", m.MoveTaskOrderID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateMtoShipmentID(formats strfmt.Registry) error { + + if swag.IsZero(m.MtoShipmentID()) { // not required + return nil + } + + if err := validate.FormatOf("mtoShipmentID", "body", "uuid", m.MtoShipmentID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateServiceRequestDocuments(formats strfmt.Registry) error { + + if swag.IsZero(m.ServiceRequestDocuments()) { // not required + return nil + } + + if err := m.ServiceRequestDocuments().Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("serviceRequestDocuments") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("serviceRequestDocuments") + } + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateStatus(formats strfmt.Registry) error { + + if swag.IsZero(m.Status()) { // not required + return nil + } + + if err := m.Status().Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("status") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("status") + } + return err + } + + return nil +} + +var mTOServiceItemDomesticShuttleTypeReServiceCodePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["DOSHUT","DDSHUT"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + mTOServiceItemDomesticShuttleTypeReServiceCodePropEnum = append(mTOServiceItemDomesticShuttleTypeReServiceCodePropEnum, v) + } +} + +// property enum +func (m *MTOServiceItemDomesticShuttle) validateReServiceCodeEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, mTOServiceItemDomesticShuttleTypeReServiceCodePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateReServiceCode(formats strfmt.Registry) error { + + if err := validate.Required("reServiceCode", "body", m.ReServiceCode); err != nil { + return err + } + + // value enum + if err := m.validateReServiceCodeEnum("reServiceCode", "body", *m.ReServiceCode); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateReason(formats strfmt.Registry) error { + + if err := validate.Required("reason", "body", m.Reason); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this m t o service item domestic shuttle based on the context it is used +func (m *MTOServiceItemDomesticShuttle) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateETag(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateID(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateReServiceName(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateRejectionReason(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateServiceRequestDocuments(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateStatus(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateETag(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "eTag", "body", string(m.ETag())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateID(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "id", "body", strfmt.UUID(m.ID())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateModelType(ctx context.Context, formats strfmt.Registry) error { + + if err := m.ModelType().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("modelType") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("modelType") + } + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateReServiceName(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "reServiceName", "body", string(m.ReServiceName())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateRejectionReason(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "rejectionReason", "body", m.RejectionReason()); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateServiceRequestDocuments(ctx context.Context, formats strfmt.Registry) error { + + if err := m.ServiceRequestDocuments().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("serviceRequestDocuments") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("serviceRequestDocuments") + } + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateStatus(ctx context.Context, formats strfmt.Registry) error { + + if swag.IsZero(m.Status()) { // not required + return nil + } + + if err := m.Status().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("status") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("status") + } + return err + } + + return nil +} + +// MarshalBinary interface implementation +func (m *MTOServiceItemDomesticShuttle) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *MTOServiceItemDomesticShuttle) UnmarshalBinary(b []byte) error { + var res MTOServiceItemDomesticShuttle + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/gen/primemessages/m_t_o_service_item_model_type.go b/pkg/gen/primemessages/m_t_o_service_item_model_type.go index 9326c1377a1..3c494ac62f3 100644 --- a/pkg/gen/primemessages/m_t_o_service_item_model_type.go +++ b/pkg/gen/primemessages/m_t_o_service_item_model_type.go @@ -20,6 +20,7 @@ import ( // - DOFSIT, DOASIT - MTOServiceItemOriginSIT // - DDFSIT, DDASIT - MTOServiceItemDestSIT // - DOSHUT, DDSHUT - MTOServiceItemShuttle +// - DOSHUT, DDSHUT - MTOServiceItemDomesticShuttle // - IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle // - DCRT, DUCRT - MTOServiceItemDomesticCrating // - ICRT, IUCRT - MTOServiceItemInternationalCrating @@ -53,6 +54,9 @@ const ( // MTOServiceItemModelTypeMTOServiceItemShuttle captures enum value "MTOServiceItemShuttle" MTOServiceItemModelTypeMTOServiceItemShuttle MTOServiceItemModelType = "MTOServiceItemShuttle" + // MTOServiceItemModelTypeMTOServiceItemDomesticShuttle captures enum value "MTOServiceItemDomesticShuttle" + MTOServiceItemModelTypeMTOServiceItemDomesticShuttle MTOServiceItemModelType = "MTOServiceItemDomesticShuttle" + // MTOServiceItemModelTypeMTOServiceItemInternationalShuttle captures enum value "MTOServiceItemInternationalShuttle" MTOServiceItemModelTypeMTOServiceItemInternationalShuttle MTOServiceItemModelType = "MTOServiceItemInternationalShuttle" @@ -71,7 +75,7 @@ var mTOServiceItemModelTypeEnum []interface{} func init() { var res []MTOServiceItemModelType - if err := json.Unmarshal([]byte(`["MTOServiceItemBasic","MTOServiceItemOriginSIT","MTOServiceItemDestSIT","MTOServiceItemShuttle","MTOServiceItemInternationalShuttle","MTOServiceItemDomesticCrating","MTOServiceItemInternationalCrating","MTOSerivceItemInternationalFuelSurcharge"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["MTOServiceItemBasic","MTOServiceItemOriginSIT","MTOServiceItemDestSIT","MTOServiceItemShuttle","MTOServiceItemDomesticShuttle","MTOServiceItemInternationalShuttle","MTOServiceItemDomesticCrating","MTOServiceItemInternationalCrating","MTOSerivceItemInternationalFuelSurcharge"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/primev2api/embedded_spec.go b/pkg/gen/primev2api/embedded_spec.go index 8cbeaf84b17..8a15212de76 100644 --- a/pkg/gen/primev2api/embedded_spec.go +++ b/pkg/gen/primev2api/embedded_spec.go @@ -1064,6 +1064,12 @@ func init() { "type": "integer", "x-nullable": true, "example": 3 + }, + "weightRestriction": { + "type": "integer", + "x-formatting": "weight", + "x-nullable": true, + "example": 1500 } } }, @@ -1429,6 +1435,50 @@ func init() { } ] }, + "MTOServiceItemDomesticShuttle": { + "description": "Describes a domestic shuttle service item.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reason", + "reServiceCode" + ], + "properties": { + "actualWeight": { + "description": "A record of the actual weight that was shuttled. Provided by the movers, based on weight tickets.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4000 + }, + "estimatedWeight": { + "description": "An estimate of how much weight from a shipment will be included in the shuttling service.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4200 + }, + "reServiceCode": { + "description": "A unique code for the service item. Indicates if shuttling is requested for the shipment origin (` + "`" + `DOSHUT` + "`" + `) or destination (` + "`" + `DDSHUT` + "`" + `).\n", + "type": "string", + "enum": [ + "DOSHUT", + "DDSHUT" + ] + }, + "reason": { + "description": "The contractor's explanation for why a shuttle service is requested. Used by the TOO while deciding to approve or reject the service item.\n", + "type": "string", + "example": "Storage items need to be picked up." + } + } + } + ] + }, "MTOServiceItemInternationalCrating": { "description": "Describes a international crating/uncrating service item subtype of a MTOServiceItem.", "allOf": [ @@ -1560,13 +1610,14 @@ func init() { ] }, "MTOServiceItemModelType": { - "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", + "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DOSHUT, DDSHUT - MTOServiceItemDomesticShuttle\n * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "MTOServiceItemBasic", "MTOServiceItemOriginSIT", "MTOServiceItemDestSIT", "MTOServiceItemShuttle", + "MTOServiceItemDomesticShuttle", "MTOServiceItemInternationalShuttle", "MTOServiceItemDomesticCrating", "MTOServiceItemInternationalCrating", @@ -4822,6 +4873,12 @@ func init() { "type": "integer", "x-nullable": true, "example": 3 + }, + "weightRestriction": { + "type": "integer", + "x-formatting": "weight", + "x-nullable": true, + "example": 1500 } } }, @@ -5187,6 +5244,50 @@ func init() { } ] }, + "MTOServiceItemDomesticShuttle": { + "description": "Describes a domestic shuttle service item.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reason", + "reServiceCode" + ], + "properties": { + "actualWeight": { + "description": "A record of the actual weight that was shuttled. Provided by the movers, based on weight tickets.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4000 + }, + "estimatedWeight": { + "description": "An estimate of how much weight from a shipment will be included in the shuttling service.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4200 + }, + "reServiceCode": { + "description": "A unique code for the service item. Indicates if shuttling is requested for the shipment origin (` + "`" + `DOSHUT` + "`" + `) or destination (` + "`" + `DDSHUT` + "`" + `).\n", + "type": "string", + "enum": [ + "DOSHUT", + "DDSHUT" + ] + }, + "reason": { + "description": "The contractor's explanation for why a shuttle service is requested. Used by the TOO while deciding to approve or reject the service item.\n", + "type": "string", + "example": "Storage items need to be picked up." + } + } + } + ] + }, "MTOServiceItemInternationalCrating": { "description": "Describes a international crating/uncrating service item subtype of a MTOServiceItem.", "allOf": [ @@ -5318,13 +5419,14 @@ func init() { ] }, "MTOServiceItemModelType": { - "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", + "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DOSHUT, DDSHUT - MTOServiceItemDomesticShuttle\n * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "MTOServiceItemBasic", "MTOServiceItemOriginSIT", "MTOServiceItemDestSIT", "MTOServiceItemShuttle", + "MTOServiceItemDomesticShuttle", "MTOServiceItemInternationalShuttle", "MTOServiceItemDomesticCrating", "MTOServiceItemInternationalCrating", diff --git a/pkg/gen/primev2messages/entitlements.go b/pkg/gen/primev2messages/entitlements.go index e29d3f733e3..58280696ab1 100644 --- a/pkg/gen/primev2messages/entitlements.go +++ b/pkg/gen/primev2messages/entitlements.go @@ -79,6 +79,10 @@ type Entitlements struct { // The amount of weight in pounds that the move is entitled for shipment types of Unaccompanied Baggage. // Example: 3 UnaccompaniedBaggageAllowance *int64 `json:"unaccompaniedBaggageAllowance,omitempty"` + + // weight restriction + // Example: 1500 + WeightRestriction *int64 `json:"weightRestriction,omitempty"` } // Validate validates this entitlements diff --git a/pkg/gen/primev2messages/m_t_o_service_item.go b/pkg/gen/primev2messages/m_t_o_service_item.go index 7dfadf4c428..c06f87e420c 100644 --- a/pkg/gen/primev2messages/m_t_o_service_item.go +++ b/pkg/gen/primev2messages/m_t_o_service_item.go @@ -273,6 +273,12 @@ func unmarshalMTOServiceItem(data []byte, consumer runtime.Consumer) (MTOService return nil, err } return &result, nil + case "MTOServiceItemDomesticShuttle": + var result MTOServiceItemDomesticShuttle + if err := consumer.Consume(buf2, &result); err != nil { + return nil, err + } + return &result, nil case "MTOServiceItemInternationalCrating": var result MTOServiceItemInternationalCrating if err := consumer.Consume(buf2, &result); err != nil { diff --git a/pkg/gen/primev2messages/m_t_o_service_item_domestic_shuttle.go b/pkg/gen/primev2messages/m_t_o_service_item_domestic_shuttle.go new file mode 100644 index 00000000000..c5767d8bddf --- /dev/null +++ b/pkg/gen/primev2messages/m_t_o_service_item_domestic_shuttle.go @@ -0,0 +1,633 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package primev2messages + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "bytes" + "context" + "encoding/json" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// MTOServiceItemDomesticShuttle Describes a domestic shuttle service item. +// +// swagger:model MTOServiceItemDomesticShuttle +type MTOServiceItemDomesticShuttle struct { + eTagField string + + idField strfmt.UUID + + lockedPriceCentsField *int64 + + moveTaskOrderIdField *strfmt.UUID + + mtoShipmentIdField strfmt.UUID + + reServiceNameField string + + rejectionReasonField *string + + serviceRequestDocumentsField ServiceRequestDocuments + + statusField MTOServiceItemStatus + + // A record of the actual weight that was shuttled. Provided by the movers, based on weight tickets. + // Example: 4000 + ActualWeight *int64 `json:"actualWeight"` + + // An estimate of how much weight from a shipment will be included in the shuttling service. + // Example: 4200 + EstimatedWeight *int64 `json:"estimatedWeight"` + + // A unique code for the service item. Indicates if shuttling is requested for the shipment origin (`DOSHUT`) or destination (`DDSHUT`). + // + // Required: true + // Enum: [DOSHUT DDSHUT] + ReServiceCode *string `json:"reServiceCode"` + + // The contractor's explanation for why a shuttle service is requested. Used by the TOO while deciding to approve or reject the service item. + // + // Example: Storage items need to be picked up. + // Required: true + Reason *string `json:"reason"` +} + +// ETag gets the e tag of this subtype +func (m *MTOServiceItemDomesticShuttle) ETag() string { + return m.eTagField +} + +// SetETag sets the e tag of this subtype +func (m *MTOServiceItemDomesticShuttle) SetETag(val string) { + m.eTagField = val +} + +// ID gets the id of this subtype +func (m *MTOServiceItemDomesticShuttle) ID() strfmt.UUID { + return m.idField +} + +// SetID sets the id of this subtype +func (m *MTOServiceItemDomesticShuttle) SetID(val strfmt.UUID) { + m.idField = val +} + +// LockedPriceCents gets the locked price cents of this subtype +func (m *MTOServiceItemDomesticShuttle) LockedPriceCents() *int64 { + return m.lockedPriceCentsField +} + +// SetLockedPriceCents sets the locked price cents of this subtype +func (m *MTOServiceItemDomesticShuttle) SetLockedPriceCents(val *int64) { + m.lockedPriceCentsField = val +} + +// ModelType gets the model type of this subtype +func (m *MTOServiceItemDomesticShuttle) ModelType() MTOServiceItemModelType { + return "MTOServiceItemDomesticShuttle" +} + +// SetModelType sets the model type of this subtype +func (m *MTOServiceItemDomesticShuttle) SetModelType(val MTOServiceItemModelType) { +} + +// MoveTaskOrderID gets the move task order ID of this subtype +func (m *MTOServiceItemDomesticShuttle) MoveTaskOrderID() *strfmt.UUID { + return m.moveTaskOrderIdField +} + +// SetMoveTaskOrderID sets the move task order ID of this subtype +func (m *MTOServiceItemDomesticShuttle) SetMoveTaskOrderID(val *strfmt.UUID) { + m.moveTaskOrderIdField = val +} + +// MtoShipmentID gets the mto shipment ID of this subtype +func (m *MTOServiceItemDomesticShuttle) MtoShipmentID() strfmt.UUID { + return m.mtoShipmentIdField +} + +// SetMtoShipmentID sets the mto shipment ID of this subtype +func (m *MTOServiceItemDomesticShuttle) SetMtoShipmentID(val strfmt.UUID) { + m.mtoShipmentIdField = val +} + +// ReServiceName gets the re service name of this subtype +func (m *MTOServiceItemDomesticShuttle) ReServiceName() string { + return m.reServiceNameField +} + +// SetReServiceName sets the re service name of this subtype +func (m *MTOServiceItemDomesticShuttle) SetReServiceName(val string) { + m.reServiceNameField = val +} + +// RejectionReason gets the rejection reason of this subtype +func (m *MTOServiceItemDomesticShuttle) RejectionReason() *string { + return m.rejectionReasonField +} + +// SetRejectionReason sets the rejection reason of this subtype +func (m *MTOServiceItemDomesticShuttle) SetRejectionReason(val *string) { + m.rejectionReasonField = val +} + +// ServiceRequestDocuments gets the service request documents of this subtype +func (m *MTOServiceItemDomesticShuttle) ServiceRequestDocuments() ServiceRequestDocuments { + return m.serviceRequestDocumentsField +} + +// SetServiceRequestDocuments sets the service request documents of this subtype +func (m *MTOServiceItemDomesticShuttle) SetServiceRequestDocuments(val ServiceRequestDocuments) { + m.serviceRequestDocumentsField = val +} + +// Status gets the status of this subtype +func (m *MTOServiceItemDomesticShuttle) Status() MTOServiceItemStatus { + return m.statusField +} + +// SetStatus sets the status of this subtype +func (m *MTOServiceItemDomesticShuttle) SetStatus(val MTOServiceItemStatus) { + m.statusField = val +} + +// UnmarshalJSON unmarshals this object with a polymorphic type from a JSON structure +func (m *MTOServiceItemDomesticShuttle) UnmarshalJSON(raw []byte) error { + var data struct { + + // A record of the actual weight that was shuttled. Provided by the movers, based on weight tickets. + // Example: 4000 + ActualWeight *int64 `json:"actualWeight"` + + // An estimate of how much weight from a shipment will be included in the shuttling service. + // Example: 4200 + EstimatedWeight *int64 `json:"estimatedWeight"` + + // A unique code for the service item. Indicates if shuttling is requested for the shipment origin (`DOSHUT`) or destination (`DDSHUT`). + // + // Required: true + // Enum: [DOSHUT DDSHUT] + ReServiceCode *string `json:"reServiceCode"` + + // The contractor's explanation for why a shuttle service is requested. Used by the TOO while deciding to approve or reject the service item. + // + // Example: Storage items need to be picked up. + // Required: true + Reason *string `json:"reason"` + } + buf := bytes.NewBuffer(raw) + dec := json.NewDecoder(buf) + dec.UseNumber() + + if err := dec.Decode(&data); err != nil { + return err + } + + var base struct { + /* Just the base type fields. Used for unmashalling polymorphic types.*/ + + ETag string `json:"eTag,omitempty"` + + ID strfmt.UUID `json:"id,omitempty"` + + LockedPriceCents *int64 `json:"lockedPriceCents,omitempty"` + + ModelType MTOServiceItemModelType `json:"modelType"` + + MoveTaskOrderID *strfmt.UUID `json:"moveTaskOrderID"` + + MtoShipmentID strfmt.UUID `json:"mtoShipmentID,omitempty"` + + ReServiceName string `json:"reServiceName,omitempty"` + + RejectionReason *string `json:"rejectionReason,omitempty"` + + ServiceRequestDocuments ServiceRequestDocuments `json:"serviceRequestDocuments,omitempty"` + + Status MTOServiceItemStatus `json:"status,omitempty"` + } + buf = bytes.NewBuffer(raw) + dec = json.NewDecoder(buf) + dec.UseNumber() + + if err := dec.Decode(&base); err != nil { + return err + } + + var result MTOServiceItemDomesticShuttle + + result.eTagField = base.ETag + + result.idField = base.ID + + result.lockedPriceCentsField = base.LockedPriceCents + + if base.ModelType != result.ModelType() { + /* Not the type we're looking for. */ + return errors.New(422, "invalid modelType value: %q", base.ModelType) + } + result.moveTaskOrderIdField = base.MoveTaskOrderID + + result.mtoShipmentIdField = base.MtoShipmentID + + result.reServiceNameField = base.ReServiceName + + result.rejectionReasonField = base.RejectionReason + + result.serviceRequestDocumentsField = base.ServiceRequestDocuments + + result.statusField = base.Status + + result.ActualWeight = data.ActualWeight + result.EstimatedWeight = data.EstimatedWeight + result.ReServiceCode = data.ReServiceCode + result.Reason = data.Reason + + *m = result + + return nil +} + +// MarshalJSON marshals this object with a polymorphic type to a JSON structure +func (m MTOServiceItemDomesticShuttle) MarshalJSON() ([]byte, error) { + var b1, b2, b3 []byte + var err error + b1, err = json.Marshal(struct { + + // A record of the actual weight that was shuttled. Provided by the movers, based on weight tickets. + // Example: 4000 + ActualWeight *int64 `json:"actualWeight"` + + // An estimate of how much weight from a shipment will be included in the shuttling service. + // Example: 4200 + EstimatedWeight *int64 `json:"estimatedWeight"` + + // A unique code for the service item. Indicates if shuttling is requested for the shipment origin (`DOSHUT`) or destination (`DDSHUT`). + // + // Required: true + // Enum: [DOSHUT DDSHUT] + ReServiceCode *string `json:"reServiceCode"` + + // The contractor's explanation for why a shuttle service is requested. Used by the TOO while deciding to approve or reject the service item. + // + // Example: Storage items need to be picked up. + // Required: true + Reason *string `json:"reason"` + }{ + + ActualWeight: m.ActualWeight, + + EstimatedWeight: m.EstimatedWeight, + + ReServiceCode: m.ReServiceCode, + + Reason: m.Reason, + }) + if err != nil { + return nil, err + } + b2, err = json.Marshal(struct { + ETag string `json:"eTag,omitempty"` + + ID strfmt.UUID `json:"id,omitempty"` + + LockedPriceCents *int64 `json:"lockedPriceCents,omitempty"` + + ModelType MTOServiceItemModelType `json:"modelType"` + + MoveTaskOrderID *strfmt.UUID `json:"moveTaskOrderID"` + + MtoShipmentID strfmt.UUID `json:"mtoShipmentID,omitempty"` + + ReServiceName string `json:"reServiceName,omitempty"` + + RejectionReason *string `json:"rejectionReason,omitempty"` + + ServiceRequestDocuments ServiceRequestDocuments `json:"serviceRequestDocuments,omitempty"` + + Status MTOServiceItemStatus `json:"status,omitempty"` + }{ + + ETag: m.ETag(), + + ID: m.ID(), + + LockedPriceCents: m.LockedPriceCents(), + + ModelType: m.ModelType(), + + MoveTaskOrderID: m.MoveTaskOrderID(), + + MtoShipmentID: m.MtoShipmentID(), + + ReServiceName: m.ReServiceName(), + + RejectionReason: m.RejectionReason(), + + ServiceRequestDocuments: m.ServiceRequestDocuments(), + + Status: m.Status(), + }) + if err != nil { + return nil, err + } + + return swag.ConcatJSON(b1, b2, b3), nil +} + +// Validate validates this m t o service item domestic shuttle +func (m *MTOServiceItemDomesticShuttle) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMoveTaskOrderID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMtoShipmentID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateServiceRequestDocuments(formats); err != nil { + res = append(res, err) + } + + if err := m.validateStatus(formats); err != nil { + res = append(res, err) + } + + if err := m.validateReServiceCode(formats); err != nil { + res = append(res, err) + } + + if err := m.validateReason(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateID(formats strfmt.Registry) error { + + if swag.IsZero(m.ID()) { // not required + return nil + } + + if err := validate.FormatOf("id", "body", "uuid", m.ID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateMoveTaskOrderID(formats strfmt.Registry) error { + + if err := validate.Required("moveTaskOrderID", "body", m.MoveTaskOrderID()); err != nil { + return err + } + + if err := validate.FormatOf("moveTaskOrderID", "body", "uuid", m.MoveTaskOrderID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateMtoShipmentID(formats strfmt.Registry) error { + + if swag.IsZero(m.MtoShipmentID()) { // not required + return nil + } + + if err := validate.FormatOf("mtoShipmentID", "body", "uuid", m.MtoShipmentID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateServiceRequestDocuments(formats strfmt.Registry) error { + + if swag.IsZero(m.ServiceRequestDocuments()) { // not required + return nil + } + + if err := m.ServiceRequestDocuments().Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("serviceRequestDocuments") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("serviceRequestDocuments") + } + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateStatus(formats strfmt.Registry) error { + + if swag.IsZero(m.Status()) { // not required + return nil + } + + if err := m.Status().Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("status") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("status") + } + return err + } + + return nil +} + +var mTOServiceItemDomesticShuttleTypeReServiceCodePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["DOSHUT","DDSHUT"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + mTOServiceItemDomesticShuttleTypeReServiceCodePropEnum = append(mTOServiceItemDomesticShuttleTypeReServiceCodePropEnum, v) + } +} + +// property enum +func (m *MTOServiceItemDomesticShuttle) validateReServiceCodeEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, mTOServiceItemDomesticShuttleTypeReServiceCodePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateReServiceCode(formats strfmt.Registry) error { + + if err := validate.Required("reServiceCode", "body", m.ReServiceCode); err != nil { + return err + } + + // value enum + if err := m.validateReServiceCodeEnum("reServiceCode", "body", *m.ReServiceCode); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateReason(formats strfmt.Registry) error { + + if err := validate.Required("reason", "body", m.Reason); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this m t o service item domestic shuttle based on the context it is used +func (m *MTOServiceItemDomesticShuttle) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateETag(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateID(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateReServiceName(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateRejectionReason(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateServiceRequestDocuments(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateStatus(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateETag(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "eTag", "body", string(m.ETag())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateID(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "id", "body", strfmt.UUID(m.ID())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateModelType(ctx context.Context, formats strfmt.Registry) error { + + if err := m.ModelType().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("modelType") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("modelType") + } + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateReServiceName(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "reServiceName", "body", string(m.ReServiceName())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateRejectionReason(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "rejectionReason", "body", m.RejectionReason()); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateServiceRequestDocuments(ctx context.Context, formats strfmt.Registry) error { + + if err := m.ServiceRequestDocuments().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("serviceRequestDocuments") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("serviceRequestDocuments") + } + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateStatus(ctx context.Context, formats strfmt.Registry) error { + + if swag.IsZero(m.Status()) { // not required + return nil + } + + if err := m.Status().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("status") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("status") + } + return err + } + + return nil +} + +// MarshalBinary interface implementation +func (m *MTOServiceItemDomesticShuttle) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *MTOServiceItemDomesticShuttle) UnmarshalBinary(b []byte) error { + var res MTOServiceItemDomesticShuttle + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/gen/primev2messages/m_t_o_service_item_model_type.go b/pkg/gen/primev2messages/m_t_o_service_item_model_type.go index 97d0c5272dc..77247b78fe0 100644 --- a/pkg/gen/primev2messages/m_t_o_service_item_model_type.go +++ b/pkg/gen/primev2messages/m_t_o_service_item_model_type.go @@ -20,6 +20,7 @@ import ( // - DOFSIT, DOASIT - MTOServiceItemOriginSIT // - DDFSIT, DDASIT - MTOServiceItemDestSIT // - DOSHUT, DDSHUT - MTOServiceItemShuttle +// - DOSHUT, DDSHUT - MTOServiceItemDomesticShuttle // - IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle // - DCRT, DUCRT - MTOServiceItemDomesticCrating // - ICRT, IUCRT - MTOServiceItemInternationalCrating @@ -53,6 +54,9 @@ const ( // MTOServiceItemModelTypeMTOServiceItemShuttle captures enum value "MTOServiceItemShuttle" MTOServiceItemModelTypeMTOServiceItemShuttle MTOServiceItemModelType = "MTOServiceItemShuttle" + // MTOServiceItemModelTypeMTOServiceItemDomesticShuttle captures enum value "MTOServiceItemDomesticShuttle" + MTOServiceItemModelTypeMTOServiceItemDomesticShuttle MTOServiceItemModelType = "MTOServiceItemDomesticShuttle" + // MTOServiceItemModelTypeMTOServiceItemInternationalShuttle captures enum value "MTOServiceItemInternationalShuttle" MTOServiceItemModelTypeMTOServiceItemInternationalShuttle MTOServiceItemModelType = "MTOServiceItemInternationalShuttle" @@ -71,7 +75,7 @@ var mTOServiceItemModelTypeEnum []interface{} func init() { var res []MTOServiceItemModelType - if err := json.Unmarshal([]byte(`["MTOServiceItemBasic","MTOServiceItemOriginSIT","MTOServiceItemDestSIT","MTOServiceItemShuttle","MTOServiceItemInternationalShuttle","MTOServiceItemDomesticCrating","MTOServiceItemInternationalCrating","MTOSerivceItemInternationalFuelSurcharge"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["MTOServiceItemBasic","MTOServiceItemOriginSIT","MTOServiceItemDestSIT","MTOServiceItemShuttle","MTOServiceItemDomesticShuttle","MTOServiceItemInternationalShuttle","MTOServiceItemDomesticCrating","MTOServiceItemInternationalCrating","MTOSerivceItemInternationalFuelSurcharge"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/primev3api/embedded_spec.go b/pkg/gen/primev3api/embedded_spec.go index b936756cd78..b926e318a3e 100644 --- a/pkg/gen/primev3api/embedded_spec.go +++ b/pkg/gen/primev3api/embedded_spec.go @@ -1226,6 +1226,12 @@ func init() { "type": "integer", "x-nullable": true, "example": 3 + }, + "weightRestriction": { + "type": "integer", + "x-formatting": "weight", + "x-nullable": true, + "example": 1500 } } }, @@ -1591,6 +1597,50 @@ func init() { } ] }, + "MTOServiceItemDomesticShuttle": { + "description": "Describes a domestic shuttle service item.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reason", + "reServiceCode" + ], + "properties": { + "actualWeight": { + "description": "A record of the actual weight that was shuttled. Provided by the movers, based on weight tickets.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4000 + }, + "estimatedWeight": { + "description": "An estimate of how much weight from a shipment will be included in the shuttling service.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4200 + }, + "reServiceCode": { + "description": "A unique code for the service item. Indicates if shuttling is requested for the shipment origin (` + "`" + `DOSHUT` + "`" + `) or destination (` + "`" + `DDSHUT` + "`" + `).\n", + "type": "string", + "enum": [ + "DOSHUT", + "DDSHUT" + ] + }, + "reason": { + "description": "The contractor's explanation for why a shuttle service is requested. Used by the TOO while deciding to approve or reject the service item.\n", + "type": "string", + "example": "Storage items need to be picked up." + } + } + } + ] + }, "MTOServiceItemInternationalCrating": { "description": "Describes a international crating/uncrating service item subtype of a MTOServiceItem.", "allOf": [ @@ -1747,13 +1797,14 @@ func init() { ] }, "MTOServiceItemModelType": { - "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", + "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DOSHUT, DDSHUT - MTOServiceItemDomesticShuttle\n * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "MTOServiceItemBasic", "MTOServiceItemOriginSIT", "MTOServiceItemDestSIT", "MTOServiceItemShuttle", + "MTOServiceItemDomesticShuttle", "MTOServiceItemInternationalShuttle", "MTOServiceItemDomesticCrating", "MTOServiceItemInternationalCrating", @@ -5694,6 +5745,12 @@ func init() { "type": "integer", "x-nullable": true, "example": 3 + }, + "weightRestriction": { + "type": "integer", + "x-formatting": "weight", + "x-nullable": true, + "example": 1500 } } }, @@ -6059,6 +6116,50 @@ func init() { } ] }, + "MTOServiceItemDomesticShuttle": { + "description": "Describes a domestic shuttle service item.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reason", + "reServiceCode" + ], + "properties": { + "actualWeight": { + "description": "A record of the actual weight that was shuttled. Provided by the movers, based on weight tickets.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4000 + }, + "estimatedWeight": { + "description": "An estimate of how much weight from a shipment will be included in the shuttling service.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4200 + }, + "reServiceCode": { + "description": "A unique code for the service item. Indicates if shuttling is requested for the shipment origin (` + "`" + `DOSHUT` + "`" + `) or destination (` + "`" + `DDSHUT` + "`" + `).\n", + "type": "string", + "enum": [ + "DOSHUT", + "DDSHUT" + ] + }, + "reason": { + "description": "The contractor's explanation for why a shuttle service is requested. Used by the TOO while deciding to approve or reject the service item.\n", + "type": "string", + "example": "Storage items need to be picked up." + } + } + } + ] + }, "MTOServiceItemInternationalCrating": { "description": "Describes a international crating/uncrating service item subtype of a MTOServiceItem.", "allOf": [ @@ -6215,13 +6316,14 @@ func init() { ] }, "MTOServiceItemModelType": { - "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", + "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DOSHUT, DDSHUT - MTOServiceItemDomesticShuttle\n * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n * PODFSC, POEFSC - MTOSerivceItemInternationalFuelSurcharge\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "MTOServiceItemBasic", "MTOServiceItemOriginSIT", "MTOServiceItemDestSIT", "MTOServiceItemShuttle", + "MTOServiceItemDomesticShuttle", "MTOServiceItemInternationalShuttle", "MTOServiceItemDomesticCrating", "MTOServiceItemInternationalCrating", diff --git a/pkg/gen/primev3messages/entitlements.go b/pkg/gen/primev3messages/entitlements.go index 1e228c6350f..2ef73ccfbf5 100644 --- a/pkg/gen/primev3messages/entitlements.go +++ b/pkg/gen/primev3messages/entitlements.go @@ -79,6 +79,10 @@ type Entitlements struct { // The amount of weight in pounds that the move is entitled for shipment types of Unaccompanied Baggage. // Example: 3 UnaccompaniedBaggageAllowance *int64 `json:"unaccompaniedBaggageAllowance,omitempty"` + + // weight restriction + // Example: 1500 + WeightRestriction *int64 `json:"weightRestriction,omitempty"` } // Validate validates this entitlements diff --git a/pkg/gen/primev3messages/m_t_o_service_item.go b/pkg/gen/primev3messages/m_t_o_service_item.go index 75d33c217f1..06f0d63c776 100644 --- a/pkg/gen/primev3messages/m_t_o_service_item.go +++ b/pkg/gen/primev3messages/m_t_o_service_item.go @@ -273,6 +273,12 @@ func unmarshalMTOServiceItem(data []byte, consumer runtime.Consumer) (MTOService return nil, err } return &result, nil + case "MTOServiceItemDomesticShuttle": + var result MTOServiceItemDomesticShuttle + if err := consumer.Consume(buf2, &result); err != nil { + return nil, err + } + return &result, nil case "MTOServiceItemInternationalCrating": var result MTOServiceItemInternationalCrating if err := consumer.Consume(buf2, &result); err != nil { diff --git a/pkg/gen/primev3messages/m_t_o_service_item_domestic_shuttle.go b/pkg/gen/primev3messages/m_t_o_service_item_domestic_shuttle.go new file mode 100644 index 00000000000..caea94b6010 --- /dev/null +++ b/pkg/gen/primev3messages/m_t_o_service_item_domestic_shuttle.go @@ -0,0 +1,633 @@ +// 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 ( + "bytes" + "context" + "encoding/json" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// MTOServiceItemDomesticShuttle Describes a domestic shuttle service item. +// +// swagger:model MTOServiceItemDomesticShuttle +type MTOServiceItemDomesticShuttle struct { + eTagField string + + idField strfmt.UUID + + lockedPriceCentsField *int64 + + moveTaskOrderIdField *strfmt.UUID + + mtoShipmentIdField strfmt.UUID + + reServiceNameField string + + rejectionReasonField *string + + serviceRequestDocumentsField ServiceRequestDocuments + + statusField MTOServiceItemStatus + + // A record of the actual weight that was shuttled. Provided by the movers, based on weight tickets. + // Example: 4000 + ActualWeight *int64 `json:"actualWeight"` + + // An estimate of how much weight from a shipment will be included in the shuttling service. + // Example: 4200 + EstimatedWeight *int64 `json:"estimatedWeight"` + + // A unique code for the service item. Indicates if shuttling is requested for the shipment origin (`DOSHUT`) or destination (`DDSHUT`). + // + // Required: true + // Enum: [DOSHUT DDSHUT] + ReServiceCode *string `json:"reServiceCode"` + + // The contractor's explanation for why a shuttle service is requested. Used by the TOO while deciding to approve or reject the service item. + // + // Example: Storage items need to be picked up. + // Required: true + Reason *string `json:"reason"` +} + +// ETag gets the e tag of this subtype +func (m *MTOServiceItemDomesticShuttle) ETag() string { + return m.eTagField +} + +// SetETag sets the e tag of this subtype +func (m *MTOServiceItemDomesticShuttle) SetETag(val string) { + m.eTagField = val +} + +// ID gets the id of this subtype +func (m *MTOServiceItemDomesticShuttle) ID() strfmt.UUID { + return m.idField +} + +// SetID sets the id of this subtype +func (m *MTOServiceItemDomesticShuttle) SetID(val strfmt.UUID) { + m.idField = val +} + +// LockedPriceCents gets the locked price cents of this subtype +func (m *MTOServiceItemDomesticShuttle) LockedPriceCents() *int64 { + return m.lockedPriceCentsField +} + +// SetLockedPriceCents sets the locked price cents of this subtype +func (m *MTOServiceItemDomesticShuttle) SetLockedPriceCents(val *int64) { + m.lockedPriceCentsField = val +} + +// ModelType gets the model type of this subtype +func (m *MTOServiceItemDomesticShuttle) ModelType() MTOServiceItemModelType { + return "MTOServiceItemDomesticShuttle" +} + +// SetModelType sets the model type of this subtype +func (m *MTOServiceItemDomesticShuttle) SetModelType(val MTOServiceItemModelType) { +} + +// MoveTaskOrderID gets the move task order ID of this subtype +func (m *MTOServiceItemDomesticShuttle) MoveTaskOrderID() *strfmt.UUID { + return m.moveTaskOrderIdField +} + +// SetMoveTaskOrderID sets the move task order ID of this subtype +func (m *MTOServiceItemDomesticShuttle) SetMoveTaskOrderID(val *strfmt.UUID) { + m.moveTaskOrderIdField = val +} + +// MtoShipmentID gets the mto shipment ID of this subtype +func (m *MTOServiceItemDomesticShuttle) MtoShipmentID() strfmt.UUID { + return m.mtoShipmentIdField +} + +// SetMtoShipmentID sets the mto shipment ID of this subtype +func (m *MTOServiceItemDomesticShuttle) SetMtoShipmentID(val strfmt.UUID) { + m.mtoShipmentIdField = val +} + +// ReServiceName gets the re service name of this subtype +func (m *MTOServiceItemDomesticShuttle) ReServiceName() string { + return m.reServiceNameField +} + +// SetReServiceName sets the re service name of this subtype +func (m *MTOServiceItemDomesticShuttle) SetReServiceName(val string) { + m.reServiceNameField = val +} + +// RejectionReason gets the rejection reason of this subtype +func (m *MTOServiceItemDomesticShuttle) RejectionReason() *string { + return m.rejectionReasonField +} + +// SetRejectionReason sets the rejection reason of this subtype +func (m *MTOServiceItemDomesticShuttle) SetRejectionReason(val *string) { + m.rejectionReasonField = val +} + +// ServiceRequestDocuments gets the service request documents of this subtype +func (m *MTOServiceItemDomesticShuttle) ServiceRequestDocuments() ServiceRequestDocuments { + return m.serviceRequestDocumentsField +} + +// SetServiceRequestDocuments sets the service request documents of this subtype +func (m *MTOServiceItemDomesticShuttle) SetServiceRequestDocuments(val ServiceRequestDocuments) { + m.serviceRequestDocumentsField = val +} + +// Status gets the status of this subtype +func (m *MTOServiceItemDomesticShuttle) Status() MTOServiceItemStatus { + return m.statusField +} + +// SetStatus sets the status of this subtype +func (m *MTOServiceItemDomesticShuttle) SetStatus(val MTOServiceItemStatus) { + m.statusField = val +} + +// UnmarshalJSON unmarshals this object with a polymorphic type from a JSON structure +func (m *MTOServiceItemDomesticShuttle) UnmarshalJSON(raw []byte) error { + var data struct { + + // A record of the actual weight that was shuttled. Provided by the movers, based on weight tickets. + // Example: 4000 + ActualWeight *int64 `json:"actualWeight"` + + // An estimate of how much weight from a shipment will be included in the shuttling service. + // Example: 4200 + EstimatedWeight *int64 `json:"estimatedWeight"` + + // A unique code for the service item. Indicates if shuttling is requested for the shipment origin (`DOSHUT`) or destination (`DDSHUT`). + // + // Required: true + // Enum: [DOSHUT DDSHUT] + ReServiceCode *string `json:"reServiceCode"` + + // The contractor's explanation for why a shuttle service is requested. Used by the TOO while deciding to approve or reject the service item. + // + // Example: Storage items need to be picked up. + // Required: true + Reason *string `json:"reason"` + } + buf := bytes.NewBuffer(raw) + dec := json.NewDecoder(buf) + dec.UseNumber() + + if err := dec.Decode(&data); err != nil { + return err + } + + var base struct { + /* Just the base type fields. Used for unmashalling polymorphic types.*/ + + ETag string `json:"eTag,omitempty"` + + ID strfmt.UUID `json:"id,omitempty"` + + LockedPriceCents *int64 `json:"lockedPriceCents,omitempty"` + + ModelType MTOServiceItemModelType `json:"modelType"` + + MoveTaskOrderID *strfmt.UUID `json:"moveTaskOrderID"` + + MtoShipmentID strfmt.UUID `json:"mtoShipmentID,omitempty"` + + ReServiceName string `json:"reServiceName,omitempty"` + + RejectionReason *string `json:"rejectionReason,omitempty"` + + ServiceRequestDocuments ServiceRequestDocuments `json:"serviceRequestDocuments,omitempty"` + + Status MTOServiceItemStatus `json:"status,omitempty"` + } + buf = bytes.NewBuffer(raw) + dec = json.NewDecoder(buf) + dec.UseNumber() + + if err := dec.Decode(&base); err != nil { + return err + } + + var result MTOServiceItemDomesticShuttle + + result.eTagField = base.ETag + + result.idField = base.ID + + result.lockedPriceCentsField = base.LockedPriceCents + + if base.ModelType != result.ModelType() { + /* Not the type we're looking for. */ + return errors.New(422, "invalid modelType value: %q", base.ModelType) + } + result.moveTaskOrderIdField = base.MoveTaskOrderID + + result.mtoShipmentIdField = base.MtoShipmentID + + result.reServiceNameField = base.ReServiceName + + result.rejectionReasonField = base.RejectionReason + + result.serviceRequestDocumentsField = base.ServiceRequestDocuments + + result.statusField = base.Status + + result.ActualWeight = data.ActualWeight + result.EstimatedWeight = data.EstimatedWeight + result.ReServiceCode = data.ReServiceCode + result.Reason = data.Reason + + *m = result + + return nil +} + +// MarshalJSON marshals this object with a polymorphic type to a JSON structure +func (m MTOServiceItemDomesticShuttle) MarshalJSON() ([]byte, error) { + var b1, b2, b3 []byte + var err error + b1, err = json.Marshal(struct { + + // A record of the actual weight that was shuttled. Provided by the movers, based on weight tickets. + // Example: 4000 + ActualWeight *int64 `json:"actualWeight"` + + // An estimate of how much weight from a shipment will be included in the shuttling service. + // Example: 4200 + EstimatedWeight *int64 `json:"estimatedWeight"` + + // A unique code for the service item. Indicates if shuttling is requested for the shipment origin (`DOSHUT`) or destination (`DDSHUT`). + // + // Required: true + // Enum: [DOSHUT DDSHUT] + ReServiceCode *string `json:"reServiceCode"` + + // The contractor's explanation for why a shuttle service is requested. Used by the TOO while deciding to approve or reject the service item. + // + // Example: Storage items need to be picked up. + // Required: true + Reason *string `json:"reason"` + }{ + + ActualWeight: m.ActualWeight, + + EstimatedWeight: m.EstimatedWeight, + + ReServiceCode: m.ReServiceCode, + + Reason: m.Reason, + }) + if err != nil { + return nil, err + } + b2, err = json.Marshal(struct { + ETag string `json:"eTag,omitempty"` + + ID strfmt.UUID `json:"id,omitempty"` + + LockedPriceCents *int64 `json:"lockedPriceCents,omitempty"` + + ModelType MTOServiceItemModelType `json:"modelType"` + + MoveTaskOrderID *strfmt.UUID `json:"moveTaskOrderID"` + + MtoShipmentID strfmt.UUID `json:"mtoShipmentID,omitempty"` + + ReServiceName string `json:"reServiceName,omitempty"` + + RejectionReason *string `json:"rejectionReason,omitempty"` + + ServiceRequestDocuments ServiceRequestDocuments `json:"serviceRequestDocuments,omitempty"` + + Status MTOServiceItemStatus `json:"status,omitempty"` + }{ + + ETag: m.ETag(), + + ID: m.ID(), + + LockedPriceCents: m.LockedPriceCents(), + + ModelType: m.ModelType(), + + MoveTaskOrderID: m.MoveTaskOrderID(), + + MtoShipmentID: m.MtoShipmentID(), + + ReServiceName: m.ReServiceName(), + + RejectionReason: m.RejectionReason(), + + ServiceRequestDocuments: m.ServiceRequestDocuments(), + + Status: m.Status(), + }) + if err != nil { + return nil, err + } + + return swag.ConcatJSON(b1, b2, b3), nil +} + +// Validate validates this m t o service item domestic shuttle +func (m *MTOServiceItemDomesticShuttle) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMoveTaskOrderID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMtoShipmentID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateServiceRequestDocuments(formats); err != nil { + res = append(res, err) + } + + if err := m.validateStatus(formats); err != nil { + res = append(res, err) + } + + if err := m.validateReServiceCode(formats); err != nil { + res = append(res, err) + } + + if err := m.validateReason(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateID(formats strfmt.Registry) error { + + if swag.IsZero(m.ID()) { // not required + return nil + } + + if err := validate.FormatOf("id", "body", "uuid", m.ID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateMoveTaskOrderID(formats strfmt.Registry) error { + + if err := validate.Required("moveTaskOrderID", "body", m.MoveTaskOrderID()); err != nil { + return err + } + + if err := validate.FormatOf("moveTaskOrderID", "body", "uuid", m.MoveTaskOrderID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateMtoShipmentID(formats strfmt.Registry) error { + + if swag.IsZero(m.MtoShipmentID()) { // not required + return nil + } + + if err := validate.FormatOf("mtoShipmentID", "body", "uuid", m.MtoShipmentID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateServiceRequestDocuments(formats strfmt.Registry) error { + + if swag.IsZero(m.ServiceRequestDocuments()) { // not required + return nil + } + + if err := m.ServiceRequestDocuments().Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("serviceRequestDocuments") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("serviceRequestDocuments") + } + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateStatus(formats strfmt.Registry) error { + + if swag.IsZero(m.Status()) { // not required + return nil + } + + if err := m.Status().Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("status") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("status") + } + return err + } + + return nil +} + +var mTOServiceItemDomesticShuttleTypeReServiceCodePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["DOSHUT","DDSHUT"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + mTOServiceItemDomesticShuttleTypeReServiceCodePropEnum = append(mTOServiceItemDomesticShuttleTypeReServiceCodePropEnum, v) + } +} + +// property enum +func (m *MTOServiceItemDomesticShuttle) validateReServiceCodeEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, mTOServiceItemDomesticShuttleTypeReServiceCodePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateReServiceCode(formats strfmt.Registry) error { + + if err := validate.Required("reServiceCode", "body", m.ReServiceCode); err != nil { + return err + } + + // value enum + if err := m.validateReServiceCodeEnum("reServiceCode", "body", *m.ReServiceCode); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateReason(formats strfmt.Registry) error { + + if err := validate.Required("reason", "body", m.Reason); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this m t o service item domestic shuttle based on the context it is used +func (m *MTOServiceItemDomesticShuttle) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateETag(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateID(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateReServiceName(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateRejectionReason(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateServiceRequestDocuments(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateStatus(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateETag(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "eTag", "body", string(m.ETag())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateID(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "id", "body", strfmt.UUID(m.ID())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateModelType(ctx context.Context, formats strfmt.Registry) error { + + if err := m.ModelType().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("modelType") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("modelType") + } + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateReServiceName(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "reServiceName", "body", string(m.ReServiceName())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateRejectionReason(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "rejectionReason", "body", m.RejectionReason()); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateServiceRequestDocuments(ctx context.Context, formats strfmt.Registry) error { + + if err := m.ServiceRequestDocuments().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("serviceRequestDocuments") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("serviceRequestDocuments") + } + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateStatus(ctx context.Context, formats strfmt.Registry) error { + + if swag.IsZero(m.Status()) { // not required + return nil + } + + if err := m.Status().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("status") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("status") + } + return err + } + + return nil +} + +// MarshalBinary interface implementation +func (m *MTOServiceItemDomesticShuttle) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *MTOServiceItemDomesticShuttle) UnmarshalBinary(b []byte) error { + var res MTOServiceItemDomesticShuttle + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/gen/primev3messages/m_t_o_service_item_model_type.go b/pkg/gen/primev3messages/m_t_o_service_item_model_type.go index 53d2a0450f6..4dea531b524 100644 --- a/pkg/gen/primev3messages/m_t_o_service_item_model_type.go +++ b/pkg/gen/primev3messages/m_t_o_service_item_model_type.go @@ -20,6 +20,7 @@ import ( // - DOFSIT, DOASIT - MTOServiceItemOriginSIT // - DDFSIT, DDASIT - MTOServiceItemDestSIT // - DOSHUT, DDSHUT - MTOServiceItemShuttle +// - DOSHUT, DDSHUT - MTOServiceItemDomesticShuttle // - IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle // - DCRT, DUCRT - MTOServiceItemDomesticCrating // - ICRT, IUCRT - MTOServiceItemInternationalCrating @@ -53,6 +54,9 @@ const ( // MTOServiceItemModelTypeMTOServiceItemShuttle captures enum value "MTOServiceItemShuttle" MTOServiceItemModelTypeMTOServiceItemShuttle MTOServiceItemModelType = "MTOServiceItemShuttle" + // MTOServiceItemModelTypeMTOServiceItemDomesticShuttle captures enum value "MTOServiceItemDomesticShuttle" + MTOServiceItemModelTypeMTOServiceItemDomesticShuttle MTOServiceItemModelType = "MTOServiceItemDomesticShuttle" + // MTOServiceItemModelTypeMTOServiceItemInternationalShuttle captures enum value "MTOServiceItemInternationalShuttle" MTOServiceItemModelTypeMTOServiceItemInternationalShuttle MTOServiceItemModelType = "MTOServiceItemInternationalShuttle" @@ -71,7 +75,7 @@ var mTOServiceItemModelTypeEnum []interface{} func init() { var res []MTOServiceItemModelType - if err := json.Unmarshal([]byte(`["MTOServiceItemBasic","MTOServiceItemOriginSIT","MTOServiceItemDestSIT","MTOServiceItemShuttle","MTOServiceItemInternationalShuttle","MTOServiceItemDomesticCrating","MTOServiceItemInternationalCrating","MTOSerivceItemInternationalFuelSurcharge"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["MTOServiceItemBasic","MTOServiceItemOriginSIT","MTOServiceItemDestSIT","MTOServiceItemShuttle","MTOServiceItemDomesticShuttle","MTOServiceItemInternationalShuttle","MTOServiceItemDomesticCrating","MTOServiceItemInternationalCrating","MTOSerivceItemInternationalFuelSurcharge"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/supportapi/embedded_spec.go b/pkg/gen/supportapi/embedded_spec.go index a8fc82230f8..c799c342054 100644 --- a/pkg/gen/supportapi/embedded_spec.go +++ b/pkg/gen/supportapi/embedded_spec.go @@ -1183,6 +1183,11 @@ func init() { "type": "integer", "x-nullable": true, "example": 3 + }, + "weightRestriction": { + "type": "integer", + "x-nullable": true, + "example": 1500 } } }, @@ -1531,14 +1536,60 @@ func init() { } ] }, + "MTOServiceItemDomesticShuttle": { + "description": "Describes a shuttle service item.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reason", + "reServiceCode", + "description" + ], + "properties": { + "actualWeight": { + "description": "Provided by the movers, based on weight tickets. Relevant for shuttling (DDSHUT \u0026 DOSHUT) service items.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4000 + }, + "estimatedWeight": { + "description": "An estimate of how much weight from a shipment will be included in a shuttling (DDSHUT \u0026 DOSHUT) service item.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4200 + }, + "reServiceCode": { + "description": "Service codes allowed for this model type.", + "type": "string", + "enum": [ + "DOSHUT", + "DDSHUT" + ] + }, + "reason": { + "description": "Explanation of why a shuttle service is required.", + "type": "string", + "example": "Storage items need to be picked up." + } + } + } + ] + }, "MTOServiceItemModelType": { - "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n\nThe documentation will then update with the supported fields.\n", + "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DOSHUT, DDSHUT - MTOServiceItemDomesticShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "MTOServiceItemBasic", "MTOServiceItemOriginSIT", "MTOServiceItemDestSIT", "MTOServiceItemShuttle", + "MTOServiceItemDomesticShuttle", "MTOServiceItemInternationalShuttle", "MTOServiceItemDomesticCrating", "MTOServiceItemInternationalCrating" @@ -4051,6 +4102,11 @@ func init() { "type": "integer", "x-nullable": true, "example": 3 + }, + "weightRestriction": { + "type": "integer", + "x-nullable": true, + "example": 1500 } } }, @@ -4399,14 +4455,60 @@ func init() { } ] }, + "MTOServiceItemDomesticShuttle": { + "description": "Describes a shuttle service item.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reason", + "reServiceCode", + "description" + ], + "properties": { + "actualWeight": { + "description": "Provided by the movers, based on weight tickets. Relevant for shuttling (DDSHUT \u0026 DOSHUT) service items.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4000 + }, + "estimatedWeight": { + "description": "An estimate of how much weight from a shipment will be included in a shuttling (DDSHUT \u0026 DOSHUT) service item.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4200 + }, + "reServiceCode": { + "description": "Service codes allowed for this model type.", + "type": "string", + "enum": [ + "DOSHUT", + "DDSHUT" + ] + }, + "reason": { + "description": "Explanation of why a shuttle service is required.", + "type": "string", + "example": "Storage items need to be picked up." + } + } + } + ] + }, "MTOServiceItemModelType": { - "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n\nThe documentation will then update with the supported fields.\n", + "description": "Describes all model sub-types for a MTOServiceItem model.\n\nUsing this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DOFSIT, DOASIT - MTOServiceItemOriginSIT\n * DDFSIT, DDASIT - MTOServiceItemDestSIT\n * DOSHUT, DDSHUT - MTOServiceItemShuttle\n * DOSHUT, DDSHUT - MTOServiceItemDomesticShuttle\n * DCRT, DUCRT - MTOServiceItemDomesticCrating\n * ICRT, IUCRT - MTOServiceItemInternationalCrating\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "MTOServiceItemBasic", "MTOServiceItemOriginSIT", "MTOServiceItemDestSIT", "MTOServiceItemShuttle", + "MTOServiceItemDomesticShuttle", "MTOServiceItemInternationalShuttle", "MTOServiceItemDomesticCrating", "MTOServiceItemInternationalCrating" diff --git a/pkg/gen/supportmessages/entitlement.go b/pkg/gen/supportmessages/entitlement.go index 434ad8aeed3..f1fd0f5f8c9 100644 --- a/pkg/gen/supportmessages/entitlement.go +++ b/pkg/gen/supportmessages/entitlement.go @@ -81,6 +81,10 @@ type Entitlement struct { // The amount of weight in pounds that the move is entitled for shipment types of Unaccompanied Baggage. // Example: 3 UnaccompaniedBaggageAllowance *int64 `json:"unaccompaniedBaggageAllowance,omitempty"` + + // weight restriction + // Example: 1500 + WeightRestriction *int64 `json:"weightRestriction,omitempty"` } // Validate validates this entitlement diff --git a/pkg/gen/supportmessages/m_t_o_service_item.go b/pkg/gen/supportmessages/m_t_o_service_item.go index 23f35835eb2..67cd5524d3f 100644 --- a/pkg/gen/supportmessages/m_t_o_service_item.go +++ b/pkg/gen/supportmessages/m_t_o_service_item.go @@ -239,6 +239,12 @@ func unmarshalMTOServiceItem(data []byte, consumer runtime.Consumer) (MTOService return nil, err } return &result, nil + case "MTOServiceItemDomesticShuttle": + var result MTOServiceItemDomesticShuttle + if err := consumer.Consume(buf2, &result); err != nil { + return nil, err + } + return &result, nil case "MTOServiceItemOriginSIT": var result MTOServiceItemOriginSIT if err := consumer.Consume(buf2, &result); err != nil { diff --git a/pkg/gen/supportmessages/m_t_o_service_item_domestic_shuttle.go b/pkg/gen/supportmessages/m_t_o_service_item_domestic_shuttle.go new file mode 100644 index 00000000000..47b7ad784ce --- /dev/null +++ b/pkg/gen/supportmessages/m_t_o_service_item_domestic_shuttle.go @@ -0,0 +1,521 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package supportmessages + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "bytes" + "context" + "encoding/json" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// MTOServiceItemDomesticShuttle Describes a shuttle service item. +// +// swagger:model MTOServiceItemDomesticShuttle +type MTOServiceItemDomesticShuttle struct { + eTagField string + + idField strfmt.UUID + + moveTaskOrderIdField *strfmt.UUID + + mtoShipmentIdField strfmt.UUID + + reServiceNameField string + + rejectionReasonField *string + + statusField MTOServiceItemStatus + + // Provided by the movers, based on weight tickets. Relevant for shuttling (DDSHUT & DOSHUT) service items. + // Example: 4000 + ActualWeight *int64 `json:"actualWeight"` + + // An estimate of how much weight from a shipment will be included in a shuttling (DDSHUT & DOSHUT) service item. + // Example: 4200 + EstimatedWeight *int64 `json:"estimatedWeight"` + + // Service codes allowed for this model type. + // Required: true + // Enum: [DOSHUT DDSHUT] + ReServiceCode *string `json:"reServiceCode"` + + // Explanation of why a shuttle service is required. + // Example: Storage items need to be picked up. + // Required: true + Reason *string `json:"reason"` +} + +// ETag gets the e tag of this subtype +func (m *MTOServiceItemDomesticShuttle) ETag() string { + return m.eTagField +} + +// SetETag sets the e tag of this subtype +func (m *MTOServiceItemDomesticShuttle) SetETag(val string) { + m.eTagField = val +} + +// ID gets the id of this subtype +func (m *MTOServiceItemDomesticShuttle) ID() strfmt.UUID { + return m.idField +} + +// SetID sets the id of this subtype +func (m *MTOServiceItemDomesticShuttle) SetID(val strfmt.UUID) { + m.idField = val +} + +// ModelType gets the model type of this subtype +func (m *MTOServiceItemDomesticShuttle) ModelType() MTOServiceItemModelType { + return "MTOServiceItemDomesticShuttle" +} + +// SetModelType sets the model type of this subtype +func (m *MTOServiceItemDomesticShuttle) SetModelType(val MTOServiceItemModelType) { +} + +// MoveTaskOrderID gets the move task order ID of this subtype +func (m *MTOServiceItemDomesticShuttle) MoveTaskOrderID() *strfmt.UUID { + return m.moveTaskOrderIdField +} + +// SetMoveTaskOrderID sets the move task order ID of this subtype +func (m *MTOServiceItemDomesticShuttle) SetMoveTaskOrderID(val *strfmt.UUID) { + m.moveTaskOrderIdField = val +} + +// MtoShipmentID gets the mto shipment ID of this subtype +func (m *MTOServiceItemDomesticShuttle) MtoShipmentID() strfmt.UUID { + return m.mtoShipmentIdField +} + +// SetMtoShipmentID sets the mto shipment ID of this subtype +func (m *MTOServiceItemDomesticShuttle) SetMtoShipmentID(val strfmt.UUID) { + m.mtoShipmentIdField = val +} + +// ReServiceName gets the re service name of this subtype +func (m *MTOServiceItemDomesticShuttle) ReServiceName() string { + return m.reServiceNameField +} + +// SetReServiceName sets the re service name of this subtype +func (m *MTOServiceItemDomesticShuttle) SetReServiceName(val string) { + m.reServiceNameField = val +} + +// RejectionReason gets the rejection reason of this subtype +func (m *MTOServiceItemDomesticShuttle) RejectionReason() *string { + return m.rejectionReasonField +} + +// SetRejectionReason sets the rejection reason of this subtype +func (m *MTOServiceItemDomesticShuttle) SetRejectionReason(val *string) { + m.rejectionReasonField = val +} + +// Status gets the status of this subtype +func (m *MTOServiceItemDomesticShuttle) Status() MTOServiceItemStatus { + return m.statusField +} + +// SetStatus sets the status of this subtype +func (m *MTOServiceItemDomesticShuttle) SetStatus(val MTOServiceItemStatus) { + m.statusField = val +} + +// UnmarshalJSON unmarshals this object with a polymorphic type from a JSON structure +func (m *MTOServiceItemDomesticShuttle) UnmarshalJSON(raw []byte) error { + var data struct { + + // Provided by the movers, based on weight tickets. Relevant for shuttling (DDSHUT & DOSHUT) service items. + // Example: 4000 + ActualWeight *int64 `json:"actualWeight"` + + // An estimate of how much weight from a shipment will be included in a shuttling (DDSHUT & DOSHUT) service item. + // Example: 4200 + EstimatedWeight *int64 `json:"estimatedWeight"` + + // Service codes allowed for this model type. + // Required: true + // Enum: [DOSHUT DDSHUT] + ReServiceCode *string `json:"reServiceCode"` + + // Explanation of why a shuttle service is required. + // Example: Storage items need to be picked up. + // Required: true + Reason *string `json:"reason"` + } + buf := bytes.NewBuffer(raw) + dec := json.NewDecoder(buf) + dec.UseNumber() + + if err := dec.Decode(&data); err != nil { + return err + } + + var base struct { + /* Just the base type fields. Used for unmashalling polymorphic types.*/ + + ETag string `json:"eTag,omitempty"` + + ID strfmt.UUID `json:"id,omitempty"` + + ModelType MTOServiceItemModelType `json:"modelType"` + + MoveTaskOrderID *strfmt.UUID `json:"moveTaskOrderID"` + + MtoShipmentID strfmt.UUID `json:"mtoShipmentID,omitempty"` + + ReServiceName string `json:"reServiceName,omitempty"` + + RejectionReason *string `json:"rejectionReason,omitempty"` + + Status MTOServiceItemStatus `json:"status,omitempty"` + } + buf = bytes.NewBuffer(raw) + dec = json.NewDecoder(buf) + dec.UseNumber() + + if err := dec.Decode(&base); err != nil { + return err + } + + var result MTOServiceItemDomesticShuttle + + result.eTagField = base.ETag + + result.idField = base.ID + + if base.ModelType != result.ModelType() { + /* Not the type we're looking for. */ + return errors.New(422, "invalid modelType value: %q", base.ModelType) + } + result.moveTaskOrderIdField = base.MoveTaskOrderID + + result.mtoShipmentIdField = base.MtoShipmentID + + result.reServiceNameField = base.ReServiceName + + result.rejectionReasonField = base.RejectionReason + + result.statusField = base.Status + + result.ActualWeight = data.ActualWeight + result.EstimatedWeight = data.EstimatedWeight + result.ReServiceCode = data.ReServiceCode + result.Reason = data.Reason + + *m = result + + return nil +} + +// MarshalJSON marshals this object with a polymorphic type to a JSON structure +func (m MTOServiceItemDomesticShuttle) MarshalJSON() ([]byte, error) { + var b1, b2, b3 []byte + var err error + b1, err = json.Marshal(struct { + + // Provided by the movers, based on weight tickets. Relevant for shuttling (DDSHUT & DOSHUT) service items. + // Example: 4000 + ActualWeight *int64 `json:"actualWeight"` + + // An estimate of how much weight from a shipment will be included in a shuttling (DDSHUT & DOSHUT) service item. + // Example: 4200 + EstimatedWeight *int64 `json:"estimatedWeight"` + + // Service codes allowed for this model type. + // Required: true + // Enum: [DOSHUT DDSHUT] + ReServiceCode *string `json:"reServiceCode"` + + // Explanation of why a shuttle service is required. + // Example: Storage items need to be picked up. + // Required: true + Reason *string `json:"reason"` + }{ + + ActualWeight: m.ActualWeight, + + EstimatedWeight: m.EstimatedWeight, + + ReServiceCode: m.ReServiceCode, + + Reason: m.Reason, + }) + if err != nil { + return nil, err + } + b2, err = json.Marshal(struct { + ETag string `json:"eTag,omitempty"` + + ID strfmt.UUID `json:"id,omitempty"` + + ModelType MTOServiceItemModelType `json:"modelType"` + + MoveTaskOrderID *strfmt.UUID `json:"moveTaskOrderID"` + + MtoShipmentID strfmt.UUID `json:"mtoShipmentID,omitempty"` + + ReServiceName string `json:"reServiceName,omitempty"` + + RejectionReason *string `json:"rejectionReason,omitempty"` + + Status MTOServiceItemStatus `json:"status,omitempty"` + }{ + + ETag: m.ETag(), + + ID: m.ID(), + + ModelType: m.ModelType(), + + MoveTaskOrderID: m.MoveTaskOrderID(), + + MtoShipmentID: m.MtoShipmentID(), + + ReServiceName: m.ReServiceName(), + + RejectionReason: m.RejectionReason(), + + Status: m.Status(), + }) + if err != nil { + return nil, err + } + + return swag.ConcatJSON(b1, b2, b3), nil +} + +// Validate validates this m t o service item domestic shuttle +func (m *MTOServiceItemDomesticShuttle) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMoveTaskOrderID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateMtoShipmentID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateStatus(formats); err != nil { + res = append(res, err) + } + + if err := m.validateReServiceCode(formats); err != nil { + res = append(res, err) + } + + if err := m.validateReason(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateID(formats strfmt.Registry) error { + + if swag.IsZero(m.ID()) { // not required + return nil + } + + if err := validate.FormatOf("id", "body", "uuid", m.ID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateMoveTaskOrderID(formats strfmt.Registry) error { + + if err := validate.Required("moveTaskOrderID", "body", m.MoveTaskOrderID()); err != nil { + return err + } + + if err := validate.FormatOf("moveTaskOrderID", "body", "uuid", m.MoveTaskOrderID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateMtoShipmentID(formats strfmt.Registry) error { + + if swag.IsZero(m.MtoShipmentID()) { // not required + return nil + } + + if err := validate.FormatOf("mtoShipmentID", "body", "uuid", m.MtoShipmentID().String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateStatus(formats strfmt.Registry) error { + + if swag.IsZero(m.Status()) { // not required + return nil + } + + if err := m.Status().Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("status") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("status") + } + return err + } + + return nil +} + +var mTOServiceItemDomesticShuttleTypeReServiceCodePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["DOSHUT","DDSHUT"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + mTOServiceItemDomesticShuttleTypeReServiceCodePropEnum = append(mTOServiceItemDomesticShuttleTypeReServiceCodePropEnum, v) + } +} + +// property enum +func (m *MTOServiceItemDomesticShuttle) validateReServiceCodeEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, mTOServiceItemDomesticShuttleTypeReServiceCodePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateReServiceCode(formats strfmt.Registry) error { + + if err := validate.Required("reServiceCode", "body", m.ReServiceCode); err != nil { + return err + } + + // value enum + if err := m.validateReServiceCodeEnum("reServiceCode", "body", *m.ReServiceCode); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) validateReason(formats strfmt.Registry) error { + + if err := validate.Required("reason", "body", m.Reason); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this m t o service item domestic shuttle based on the context it is used +func (m *MTOServiceItemDomesticShuttle) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateETag(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateReServiceName(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateStatus(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateETag(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "eTag", "body", string(m.ETag())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateModelType(ctx context.Context, formats strfmt.Registry) error { + + if err := m.ModelType().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("modelType") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("modelType") + } + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateReServiceName(ctx context.Context, formats strfmt.Registry) error { + + if err := validate.ReadOnly(ctx, "reServiceName", "body", string(m.ReServiceName())); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemDomesticShuttle) contextValidateStatus(ctx context.Context, formats strfmt.Registry) error { + + if swag.IsZero(m.Status()) { // not required + return nil + } + + if err := m.Status().ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("status") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("status") + } + return err + } + + return nil +} + +// MarshalBinary interface implementation +func (m *MTOServiceItemDomesticShuttle) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *MTOServiceItemDomesticShuttle) UnmarshalBinary(b []byte) error { + var res MTOServiceItemDomesticShuttle + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/gen/supportmessages/m_t_o_service_item_model_type.go b/pkg/gen/supportmessages/m_t_o_service_item_model_type.go index 3f957023e8a..ac2cf879c35 100644 --- a/pkg/gen/supportmessages/m_t_o_service_item_model_type.go +++ b/pkg/gen/supportmessages/m_t_o_service_item_model_type.go @@ -20,6 +20,7 @@ import ( // - DOFSIT, DOASIT - MTOServiceItemOriginSIT // - DDFSIT, DDASIT - MTOServiceItemDestSIT // - DOSHUT, DDSHUT - MTOServiceItemShuttle +// - DOSHUT, DDSHUT - MTOServiceItemDomesticShuttle // - DCRT, DUCRT - MTOServiceItemDomesticCrating // - ICRT, IUCRT - MTOServiceItemInternationalCrating // @@ -51,6 +52,9 @@ const ( // MTOServiceItemModelTypeMTOServiceItemShuttle captures enum value "MTOServiceItemShuttle" MTOServiceItemModelTypeMTOServiceItemShuttle MTOServiceItemModelType = "MTOServiceItemShuttle" + // MTOServiceItemModelTypeMTOServiceItemDomesticShuttle captures enum value "MTOServiceItemDomesticShuttle" + MTOServiceItemModelTypeMTOServiceItemDomesticShuttle MTOServiceItemModelType = "MTOServiceItemDomesticShuttle" + // MTOServiceItemModelTypeMTOServiceItemInternationalShuttle captures enum value "MTOServiceItemInternationalShuttle" MTOServiceItemModelTypeMTOServiceItemInternationalShuttle MTOServiceItemModelType = "MTOServiceItemInternationalShuttle" @@ -66,7 +70,7 @@ var mTOServiceItemModelTypeEnum []interface{} func init() { var res []MTOServiceItemModelType - if err := json.Unmarshal([]byte(`["MTOServiceItemBasic","MTOServiceItemOriginSIT","MTOServiceItemDestSIT","MTOServiceItemShuttle","MTOServiceItemInternationalShuttle","MTOServiceItemDomesticCrating","MTOServiceItemInternationalCrating"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["MTOServiceItemBasic","MTOServiceItemOriginSIT","MTOServiceItemDestSIT","MTOServiceItemShuttle","MTOServiceItemDomesticShuttle","MTOServiceItemInternationalShuttle","MTOServiceItemDomesticCrating","MTOServiceItemInternationalCrating"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/handlers/adminapi/api.go b/pkg/handlers/adminapi/api.go index 1dc3efa0343..5f8d2960b2e 100644 --- a/pkg/handlers/adminapi/api.go +++ b/pkg/handlers/adminapi/api.go @@ -53,16 +53,19 @@ func NewAdminAPI(handlerConfig handlers.HandlerConfig) *adminops.MymoveAPI { adminAPI.ServeError = handlers.ServeCustomError + transportationOfficeFetcher := transportationoffice.NewTransportationOfficesFetcher() + userRolesCreator := usersroles.NewUsersRolesCreator() + newRolesFetcher := roles.NewRolesFetcher() + adminAPI.RequestedOfficeUsersIndexRequestedOfficeUsersHandler = IndexRequestedOfficeUsersHandler{ handlerConfig, requestedofficeusers.NewRequestedOfficeUsersListFetcher(queryBuilder), query.NewQueryFilter, pagination.NewPagination, + transportationOfficeFetcher, + newRolesFetcher, } - userRolesCreator := usersroles.NewUsersRolesCreator() - newRolesFetcher := roles.NewRolesFetcher() - adminAPI.RequestedOfficeUsersGetRequestedOfficeUserHandler = GetRequestedOfficeUserHandler{ handlerConfig, requestedofficeusers.NewRequestedOfficeUserFetcher(queryBuilder), @@ -124,7 +127,6 @@ func NewAdminAPI(handlerConfig handlers.HandlerConfig) *adminops.MymoveAPI { pagination.NewPagination, } - transportationOfficeFetcher := transportationoffice.NewTransportationOfficesFetcher() adminAPI.TransportationOfficesGetOfficeByIDHandler = GetOfficeByIdHandler{ handlerConfig, transportationOfficeFetcher, diff --git a/pkg/handlers/adminapi/requested_office_users.go b/pkg/handlers/adminapi/requested_office_users.go index 5561fd7abf1..1571e8dbbc0 100644 --- a/pkg/handlers/adminapi/requested_office_users.go +++ b/pkg/handlers/adminapi/requested_office_users.go @@ -3,12 +3,14 @@ package adminapi import ( "bytes" "encoding/json" + "errors" "fmt" "io" "net/http" "strings" "github.com/go-openapi/runtime/middleware" + "github.com/gobuffalo/pop/v6" "github.com/gofrs/uuid" "github.com/spf13/viper" "go.uber.org/zap" @@ -154,15 +156,29 @@ type IndexRequestedOfficeUsersHandler struct { services.RequestedOfficeUserListFetcher services.NewQueryFilter services.NewPagination + services.TransportationOfficesFetcher + services.RoleAssociater } -var requestedOfficeUserFilterConverters = map[string]func(string) []services.QueryFilter{ - "search": func(content string) []services.QueryFilter { - nameSearch := fmt.Sprintf("%s%%", content) - return []services.QueryFilter{ - query.NewQueryFilter("email", "ILIKE", fmt.Sprintf("%%%s%%", content)), - query.NewQueryFilter("first_name", "ILIKE", nameSearch), - query.NewQueryFilter("last_name", "ILIKE", nameSearch), +var requestedOfficeUserFilterConverters = map[string]func(string) func(*pop.Query){ + "search": func(content string) func(*pop.Query) { + return func(query *pop.Query) { + nameSearch := fmt.Sprintf("%%%s%%", content) + query.Where("office_users.email ILIKE ? AND office_users.status = 'REQUESTED' OR office_users.first_name ILIKE ? AND office_users.status = 'REQUESTED' OR office_users.last_name ILIKE ? AND office_users.status = 'REQUESTED'", nameSearch, nameSearch, nameSearch) + } + }, + + "offices": func(content string) func(*pop.Query) { + return func(query *pop.Query) { + nameSearch := fmt.Sprintf("%%%s%%", content) + query.Where("transportation_offices.name ILIKE ? AND office_users.status = 'REQUESTED'", nameSearch) + } + }, + + "rolesSearch": func(content string) func(*pop.Query) { + return func(query *pop.Query) { + nameSearch := fmt.Sprintf("%%%s%%", content) + query.Where("roles.role_name ILIKE ? AND office_users.status = 'REQUESTED'", nameSearch) } }, } @@ -172,27 +188,25 @@ func (h IndexRequestedOfficeUsersHandler) Handle(params requested_office_users.I return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, func(appCtx appcontext.AppContext) (middleware.Responder, error) { - // adding in filters for when a search or filtering is done - queryFilters := generateQueryFilters(appCtx.Logger(), params.Filter, requestedOfficeUserFilterConverters) + var filtersMap map[string]string + if params.Filter != nil && *params.Filter != "" { + err := json.Unmarshal([]byte(*params.Filter), &filtersMap) + if err != nil { + return handlers.ResponseForError(appCtx.Logger(), errors.New("invalid filter format")), err + } + } - // We only want users that are in a REQUESTED status - queryFilters = append(queryFilters, query.NewQueryFilter("status", "=", "REQUESTED")) + var filterFuncs []func(*pop.Query) + for key, filterFunc := range requestedOfficeUserFilterConverters { + if filterValue, exists := filtersMap[key]; exists { + filterFuncs = append(filterFuncs, filterFunc(filterValue)) + } + } - // adding in pagination for the UI pagination := h.NewPagination(params.Page, params.PerPage) ordering := query.NewQueryOrder(params.Sort, params.Order) - // need to also get the user's roles - queryAssociations := query.NewQueryAssociationsPreload([]services.QueryAssociation{ - query.NewQueryAssociation("User.Roles"), - }) - - officeUsers, err := h.RequestedOfficeUserListFetcher.FetchRequestedOfficeUsersList(appCtx, queryFilters, queryAssociations, pagination, ordering) - if err != nil { - return handlers.ResponseForError(appCtx.Logger(), err), err - } - - totalOfficeUsersCount, err := h.RequestedOfficeUserListFetcher.FetchRequestedOfficeUsersCount(appCtx, queryFilters) + officeUsers, count, err := h.RequestedOfficeUserListFetcher.FetchRequestedOfficeUsersList(appCtx, filterFuncs, pagination, ordering) if err != nil { return handlers.ResponseForError(appCtx.Logger(), err), err } @@ -205,7 +219,7 @@ func (h IndexRequestedOfficeUsersHandler) Handle(params requested_office_users.I payload[i] = payloadForRequestedOfficeUserModel(s) } - return requested_office_users.NewIndexRequestedOfficeUsersOK().WithContentRange(fmt.Sprintf("requested office users %d-%d/%d", pagination.Offset(), pagination.Offset()+queriedOfficeUsersCount, totalOfficeUsersCount)).WithPayload(payload), nil + return requested_office_users.NewIndexRequestedOfficeUsersOK().WithContentRange(fmt.Sprintf("requested office users %d-%d/%d", pagination.Offset(), pagination.Offset()+queriedOfficeUsersCount, count)).WithPayload(payload), nil }) } diff --git a/pkg/handlers/adminapi/requested_office_users_test.go b/pkg/handlers/adminapi/requested_office_users_test.go index d84c87ad69c..38f1c9c8afd 100644 --- a/pkg/handlers/adminapi/requested_office_users_test.go +++ b/pkg/handlers/adminapi/requested_office_users_test.go @@ -25,9 +25,7 @@ import ( ) func (suite *HandlerSuite) TestIndexRequestedOfficeUsersHandler() { - // test that everything is wired up suite.Run("requested users result in ok response", func() { - // building two office user with requested status requestedOfficeUsers := models.OfficeUsers{ factory.BuildOfficeUserWithRoles(suite.DB(), factory.GetTraitRequestedOfficeUser(), []roles.RoleType{roles.RoleTypeQae}), factory.BuildOfficeUserWithRoles(suite.DB(), factory.GetTraitRequestedOfficeUser(), []roles.RoleType{roles.RoleTypeQae})} @@ -45,16 +43,377 @@ func (suite *HandlerSuite) TestIndexRequestedOfficeUsersHandler() { response := handler.Handle(params) - // should get an ok response suite.IsType(&requestedofficeuserop.IndexRequestedOfficeUsersOK{}, response) okResponse := response.(*requestedofficeuserop.IndexRequestedOfficeUsersOK) suite.Len(okResponse.Payload, 2) - suite.Equal(requestedOfficeUsers[0].ID.String(), okResponse.Payload[0].ID.String()) + requestedOfficeUser1Id := requestedOfficeUsers[0].ID.String() + requestedOfficeUser2Id := requestedOfficeUsers[1].ID.String() + payloadRequestedUser1Id := okResponse.Payload[0].ID.String() + payloadRequestedUser2Id := okResponse.Payload[1].ID.String() + + // requested office users should exist in response no matter the ordering that has been applied + user1ExistsInResponse := requestedOfficeUser1Id == payloadRequestedUser1Id || requestedOfficeUser1Id == payloadRequestedUser2Id + user2ExistsInResponse := requestedOfficeUser2Id == payloadRequestedUser1Id || requestedOfficeUser2Id == payloadRequestedUser2Id + suite.True(user1ExistsInResponse) + suite.True(user2ExistsInResponse) + }) + + suite.Run("able to search by name & email", func() { + requestedStatus := models.OfficeUserStatusREQUESTED + officeUser1 := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + FirstName: "Angelina", + LastName: "Jolie", + Email: "laraCroft@mail.mil", + Status: &requestedStatus, + }, + }, + }, []roles.RoleType{roles.RoleTypeTOO}) + officeUser2 := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + FirstName: "Billy", + LastName: "Bob", + Email: "bigBob@mail.mil", + Status: &requestedStatus, + }, + }, + }, []roles.RoleType{roles.RoleTypeTIO}) + officeUser3 := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + FirstName: "Nick", + LastName: "Cage", + Email: "conAirKilluh@mail.mil", + Status: &requestedStatus, + }, + }, + }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + + // partial first name search + filterJSON := "{\"search\":\"Angel\"}" + params := requestedofficeuserop.IndexRequestedOfficeUsersParams{ + HTTPRequest: suite.setupAuthenticatedRequest("GET", "/requested_office_users"), + Filter: &filterJSON, + } + + queryBuilder := query.NewQueryBuilder() + handler := IndexRequestedOfficeUsersHandler{ + HandlerConfig: suite.HandlerConfig(), + NewQueryFilter: query.NewQueryFilter, + RequestedOfficeUserListFetcher: requestedofficeusers.NewRequestedOfficeUsersListFetcher(queryBuilder), + NewPagination: pagination.NewPagination, + } + + response := handler.Handle(params) + + suite.IsType(&requestedofficeuserop.IndexRequestedOfficeUsersOK{}, response) + okResponse := response.(*requestedofficeuserop.IndexRequestedOfficeUsersOK) + suite.Len(okResponse.Payload, 1) + suite.Equal(officeUser1.ID.String(), okResponse.Payload[0].ID.String()) + + // search by first name + filterJSON = "{\"search\":\"Bill\"}" + params = requestedofficeuserop.IndexRequestedOfficeUsersParams{ + HTTPRequest: suite.setupAuthenticatedRequest("GET", "/requested_office_users"), + Filter: &filterJSON, + } + response = handler.Handle(params) + + suite.IsType(&requestedofficeuserop.IndexRequestedOfficeUsersOK{}, response) + okResponse = response.(*requestedofficeuserop.IndexRequestedOfficeUsersOK) + suite.Len(okResponse.Payload, 1) + suite.Equal(officeUser2.ID.String(), okResponse.Payload[0].ID.String()) + + // email search + filterJSON = "{\"search\":\"conAir\"}" + params = requestedofficeuserop.IndexRequestedOfficeUsersParams{ + HTTPRequest: suite.setupAuthenticatedRequest("GET", "/requested_office_users"), + Filter: &filterJSON, + } + response = handler.Handle(params) + + suite.IsType(&requestedofficeuserop.IndexRequestedOfficeUsersOK{}, response) + okResponse = response.(*requestedofficeuserop.IndexRequestedOfficeUsersOK) + suite.Len(okResponse.Payload, 1) + suite.Equal(officeUser3.ID.String(), okResponse.Payload[0].ID.String()) + }) + + suite.Run("test the return of sorted requested office users in asc order", func() { + requestedStatus := models.OfficeUserStatusREQUESTED + officeUser1 := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + FirstName: "Angelina", + LastName: "Jolie", + Email: "laraCroft@mail.mil", + Status: &requestedStatus, + }, + }, + { + Model: models.TransportationOffice{ + Name: "PPPO Kirtland AFB - USAF", + }, + }, + }, []roles.RoleType{roles.RoleTypeTOO}) + officeUser2 := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + FirstName: "Billy", + LastName: "Bob", + Email: "bigBob@mail.mil", + Status: &requestedStatus, + }, + }, + { + Model: models.TransportationOffice{ + Name: "PPPO Fort Knox - USA", + }, + }, + }, []roles.RoleType{roles.RoleTypeTIO}) + officeUser3 := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + FirstName: "Nick", + LastName: "Cage", + Email: "conAirKilluh@mail.mil", + Status: &requestedStatus, + }, + }, + { + Model: models.TransportationOffice{ + Name: "PPPO Detroit Arsenal - USA", + }, + }, + }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + + sortColumn := "transportation_office_id" + params := requestedofficeuserop.IndexRequestedOfficeUsersParams{ + HTTPRequest: suite.setupAuthenticatedRequest("GET", "/requested_office_users"), + Sort: &sortColumn, + Order: models.BoolPointer(true), + } + + queryBuilder := query.NewQueryBuilder() + handler := IndexRequestedOfficeUsersHandler{ + HandlerConfig: suite.HandlerConfig(), + NewQueryFilter: query.NewQueryFilter, + RequestedOfficeUserListFetcher: requestedofficeusers.NewRequestedOfficeUsersListFetcher(queryBuilder), + NewPagination: pagination.NewPagination, + } + + response := handler.Handle(params) + + suite.IsType(&requestedofficeuserop.IndexRequestedOfficeUsersOK{}, response) + okResponse := response.(*requestedofficeuserop.IndexRequestedOfficeUsersOK) + suite.Len(okResponse.Payload, 3) + suite.Equal(officeUser3.ID.String(), okResponse.Payload[0].ID.String()) + suite.Equal(officeUser2.ID.String(), okResponse.Payload[1].ID.String()) + suite.Equal(officeUser1.ID.String(), okResponse.Payload[2].ID.String()) + + // sort by transportation office name in desc order + sortColumn = "transportation_office_id" + params = requestedofficeuserop.IndexRequestedOfficeUsersParams{ + HTTPRequest: suite.setupAuthenticatedRequest("GET", "/requested_office_users"), + Sort: &sortColumn, + Order: models.BoolPointer(false), + } + + queryBuilder = query.NewQueryBuilder() + handler = IndexRequestedOfficeUsersHandler{ + HandlerConfig: suite.HandlerConfig(), + NewQueryFilter: query.NewQueryFilter, + RequestedOfficeUserListFetcher: requestedofficeusers.NewRequestedOfficeUsersListFetcher(queryBuilder), + NewPagination: pagination.NewPagination, + } + + response = handler.Handle(params) + + suite.IsType(&requestedofficeuserop.IndexRequestedOfficeUsersOK{}, response) + okResponse = response.(*requestedofficeuserop.IndexRequestedOfficeUsersOK) + suite.Len(okResponse.Payload, 3) + suite.Equal(officeUser1.ID.String(), okResponse.Payload[0].ID.String()) + suite.Equal(officeUser2.ID.String(), okResponse.Payload[1].ID.String()) + suite.Equal(officeUser3.ID.String(), okResponse.Payload[2].ID.String()) + + // sort by first name in asc order + sortColumn = "first_name" + params = requestedofficeuserop.IndexRequestedOfficeUsersParams{ + HTTPRequest: suite.setupAuthenticatedRequest("GET", "/requested_office_users"), + Sort: &sortColumn, + Order: models.BoolPointer(true), + } + + queryBuilder = query.NewQueryBuilder() + handler = IndexRequestedOfficeUsersHandler{ + HandlerConfig: suite.HandlerConfig(), + NewQueryFilter: query.NewQueryFilter, + RequestedOfficeUserListFetcher: requestedofficeusers.NewRequestedOfficeUsersListFetcher(queryBuilder), + NewPagination: pagination.NewPagination, + } + + response = handler.Handle(params) + + suite.IsType(&requestedofficeuserop.IndexRequestedOfficeUsersOK{}, response) + okResponse = response.(*requestedofficeuserop.IndexRequestedOfficeUsersOK) + suite.Len(okResponse.Payload, 3) + suite.Equal(officeUser1.ID.String(), okResponse.Payload[0].ID.String()) + suite.Equal(officeUser2.ID.String(), okResponse.Payload[1].ID.String()) + suite.Equal(officeUser3.ID.String(), okResponse.Payload[2].ID.String()) + }) + + suite.Run("able to search by transportation office", func() { + transportationOffice := factory.BuildTransportationOffice(suite.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{ + Name: "Tinker", + }, + }, + }, nil) + requestedStatus := models.OfficeUserStatusREQUESTED + officeUser1 := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + TransportationOfficeID: transportationOffice.ID, + Status: &requestedStatus, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + factory.BuildOfficeUserWithRoles(suite.DB(), factory.GetTraitRequestedOfficeUser(), []roles.RoleType{roles.RoleTypeTOO}) + factory.BuildOfficeUserWithRoles(suite.DB(), factory.GetTraitRequestedOfficeUser(), []roles.RoleType{roles.RoleTypeTIO}) + + filterJSON := "{\"offices\":\"Tinker\"}" + params := requestedofficeuserop.IndexRequestedOfficeUsersParams{ + HTTPRequest: suite.setupAuthenticatedRequest("GET", "/requested_office_users"), + Filter: &filterJSON, + } + + queryBuilder := query.NewQueryBuilder() + handler := IndexRequestedOfficeUsersHandler{ + HandlerConfig: suite.HandlerConfig(), + NewQueryFilter: query.NewQueryFilter, + RequestedOfficeUserListFetcher: requestedofficeusers.NewRequestedOfficeUsersListFetcher(queryBuilder), + NewPagination: pagination.NewPagination, + } + + response := handler.Handle(params) + + suite.IsType(&requestedofficeuserop.IndexRequestedOfficeUsersOK{}, response) + okResponse := response.(*requestedofficeuserop.IndexRequestedOfficeUsersOK) + suite.Len(okResponse.Payload, 1) + suite.Equal(officeUser1.ID.String(), okResponse.Payload[0].ID.String()) + }) + + suite.Run("able to search by role", func() { + requestedStatus := models.OfficeUserStatusREQUESTED + officeUser1 := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + Status: &requestedStatus, + }, + }, + }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + + filterJSON := "{\"rolesSearch\":\"services\"}" + params := requestedofficeuserop.IndexRequestedOfficeUsersParams{ + HTTPRequest: suite.setupAuthenticatedRequest("GET", "/requested_office_users"), + Filter: &filterJSON, + } + + queryBuilder := query.NewQueryBuilder() + handler := IndexRequestedOfficeUsersHandler{ + HandlerConfig: suite.HandlerConfig(), + NewQueryFilter: query.NewQueryFilter, + RequestedOfficeUserListFetcher: requestedofficeusers.NewRequestedOfficeUsersListFetcher(queryBuilder), + NewPagination: pagination.NewPagination, + } + + response := handler.Handle(params) + + suite.IsType(&requestedofficeuserop.IndexRequestedOfficeUsersOK{}, response) + okResponse := response.(*requestedofficeuserop.IndexRequestedOfficeUsersOK) + suite.Len(okResponse.Payload, 1) + suite.Equal(officeUser1.ID.String(), okResponse.Payload[0].ID.String()) + }) + + suite.Run("return error when querying for unhandled data", func() { + requestedStatus := models.OfficeUserStatusREQUESTED + factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + Status: &requestedStatus, + }, + }, + }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + + sortColumn := "unknown_column" + params := requestedofficeuserop.IndexRequestedOfficeUsersParams{ + HTTPRequest: suite.setupAuthenticatedRequest("GET", "/requested_office_users"), + Sort: &sortColumn, + Order: models.BoolPointer(true), + } + + queryBuilder := query.NewQueryBuilder() + handler := IndexRequestedOfficeUsersHandler{ + HandlerConfig: suite.HandlerConfig(), + NewQueryFilter: query.NewQueryFilter, + RequestedOfficeUserListFetcher: requestedofficeusers.NewRequestedOfficeUsersListFetcher(queryBuilder), + NewPagination: pagination.NewPagination, + } + + response := handler.Handle(params) + + suite.IsType(&handlers.ErrResponse{}, response) + errResponse := response.(*handlers.ErrResponse) + suite.Equal(http.StatusInternalServerError, errResponse.Code) + errMsg := errResponse.Err.Error() + suite.Equal(errMsg, "Unhandled data error encountered") + }) + + suite.Run("should error when a param filter format is incorrect", func() { + requestedStatus := models.OfficeUserStatusREQUESTED + factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + Status: &requestedStatus, + }, + }, + }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + + // Invalid format for filter params + filterJSON := "test{\"unknown\":\"value\"}test" + params := requestedofficeuserop.IndexRequestedOfficeUsersParams{ + HTTPRequest: suite.setupAuthenticatedRequest("GET", "/requested_office_users"), + Filter: &filterJSON, + } + + queryBuilder := query.NewQueryBuilder() + handler := IndexRequestedOfficeUsersHandler{ + HandlerConfig: suite.HandlerConfig(), + NewQueryFilter: query.NewQueryFilter, + RequestedOfficeUserListFetcher: requestedofficeusers.NewRequestedOfficeUsersListFetcher(queryBuilder), + NewPagination: pagination.NewPagination, + } + + response := handler.Handle(params) + + expectedError := models.ErrInvalidFilterFormat + expectedResponse := &handlers.ErrResponse{ + Code: http.StatusInternalServerError, + Err: expectedError, + } + + suite.Equal(expectedResponse, response) }) } func (suite *HandlerSuite) TestGetRequestedOfficeUserHandler() { - // test that everything is wired up suite.Run("integration test ok response", func() { requestedOfficeUser := factory.BuildOfficeUserWithRoles(suite.DB(), factory.GetTraitRequestedOfficeUser(), []roles.RoleType{roles.RoleTypeQae}) params := requestedofficeuserop.GetRequestedOfficeUserParams{ diff --git a/pkg/handlers/ghcapi/api.go b/pkg/handlers/ghcapi/api.go index b48c88402fc..e1e9c8a7b75 100644 --- a/pkg/handlers/ghcapi/api.go +++ b/pkg/handlers/ghcapi/api.go @@ -410,6 +410,20 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { mtoshipment.NewShipmentDeleter(moveTaskOrderUpdater, moveRouter), } + ghcAPI.ShipmentApproveShipmentsHandler = ApproveShipmentsHandler{ + handlerConfig, + mtoshipment.NewShipmentApprover( + mtoshipment.NewShipmentRouter(), + mtoserviceitem.NewMTOServiceItemCreator(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()), + handlerConfig.HHGPlanner(), + move.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf), + moveTaskOrderUpdater, + moveRouter, + ), + shipmentSITStatus, + moveTaskOrderUpdater, + } + ghcAPI.ShipmentApproveShipmentHandler = ApproveShipmentHandler{ handlerConfig, mtoshipment.NewShipmentApprover( @@ -417,8 +431,11 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { mtoserviceitem.NewMTOServiceItemCreator(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()), handlerConfig.HHGPlanner(), move.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf), + moveTaskOrderUpdater, + moveRouter, ), shipmentSITStatus, + moveTaskOrderUpdater, } ghcAPI.ShipmentRequestShipmentDiversionHandler = RequestShipmentDiversionHandler{ @@ -665,6 +682,11 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { transportationOfficeFetcher, } + ghcAPI.TransportationOfficeShowCounselingOfficesHandler = ShowCounselingOfficesHandler{ + handlerConfig, + transportationOfficeFetcher, + } + ghcAPI.MoveUpdateCloseoutOfficeHandler = UpdateMoveCloseoutOfficeHandler{ handlerConfig, closeoutOfficeUpdater, diff --git a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go index 353e2e7c11e..7e9e05e90fe 100644 --- a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go +++ b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go @@ -122,6 +122,8 @@ func Move(move *models.Move, storer storage.FileStorer) (*ghcmessages.Move, erro SCAssignedUser: AssignedOfficeUser(move.SCAssignedUser), TOOAssignedUser: AssignedOfficeUser(move.TOOAssignedUser), TIOAssignedUser: AssignedOfficeUser(move.TIOAssignedUser), + CounselingOfficeID: handlers.FmtUUIDPtr(move.CounselingOfficeID), + CounselingOffice: TransportationOffice(move.CounselingOffice), } return payload, nil @@ -437,6 +439,18 @@ func GBLOCs(gblocs []string) ghcmessages.GBLOCs { return payload } +func CounselingOffices(counselingOffices models.TransportationOffices) ghcmessages.CounselingOffices { + payload := make(ghcmessages.CounselingOffices, len(counselingOffices)) + + for i, counselingOffice := range counselingOffices { + payload[i] = &ghcmessages.CounselingOffice{ + ID: handlers.FmtUUID(counselingOffice.ID), + Name: models.StringPointer(counselingOffice.Name), + } + } + return payload +} + // MoveHistory payload func MoveHistory(logger *zap.Logger, moveHistory *models.MoveHistory) *ghcmessages.MoveHistory { payload := &ghcmessages.MoveHistory{ @@ -733,6 +747,11 @@ func Entitlement(entitlement *models.Entitlement) *ghcmessages.Entitlements { if entitlement.UBAllowance != nil { ubAllowance = models.Int64Pointer(int64(*entitlement.UBAllowance)) } + var weightRestriction *int64 + if entitlement.WeightRestriction != nil { + weightRestriction = models.Int64Pointer(int64(*entitlement.WeightRestriction)) + } + return &ghcmessages.Entitlements{ ID: strfmt.UUID(entitlement.ID.String()), AuthorizedWeight: authorizedWeight, @@ -750,9 +769,11 @@ func Entitlement(entitlement *models.Entitlement) *ghcmessages.Entitlements { AccompaniedTour: accompaniedTour, UnaccompaniedBaggageAllowance: ubAllowance, OrganizationalClothingAndIndividualEquipment: entitlement.OrganizationalClothingAndIndividualEquipment, - GunSafe: gunSafe, - ETag: etag.GenerateEtag(entitlement.UpdatedAt), + GunSafe: gunSafe, + WeightRestriction: weightRestriction, + ETag: etag.GenerateEtag(entitlement.UpdatedAt), } + } // DutyLocation payload @@ -1300,13 +1321,16 @@ func PPMCloseout(ppmCloseout *models.PPMCloseout) *ghcmessages.PPMCloseout { Gcc: handlers.FmtCost(ppmCloseout.GCC), Aoa: handlers.FmtCost(ppmCloseout.AOA), RemainingIncentive: handlers.FmtCost(ppmCloseout.RemainingIncentive), - HaulType: (*string)(&ppmCloseout.HaulType), + HaulType: (*string)(ppmCloseout.HaulType), HaulPrice: handlers.FmtCost(ppmCloseout.HaulPrice), HaulFSC: handlers.FmtCost(ppmCloseout.HaulFSC), Dop: handlers.FmtCost(ppmCloseout.DOP), Ddp: handlers.FmtCost(ppmCloseout.DDP), PackPrice: handlers.FmtCost(ppmCloseout.PackPrice), UnpackPrice: handlers.FmtCost(ppmCloseout.UnpackPrice), + IntlPackPrice: handlers.FmtCost((ppmCloseout.IntlPackPrice)), + IntlUnpackPrice: handlers.FmtCost((ppmCloseout.IntlUnpackPrice)), + IntlLinehaulPrice: handlers.FmtCost((ppmCloseout.IntlLinehaulPrice)), SITReimbursement: handlers.FmtCost(ppmCloseout.SITReimbursement), } @@ -2207,12 +2231,12 @@ func BulkAssignmentData(appCtx appcontext.AppContext, moves []models.MoveWithEar return *bulkAssignmentData } -func queueMoveIsAssignable(move models.Move, assignedToUser *ghcmessages.AssignedOfficeUser, isCloseoutQueue bool, officeUser models.OfficeUser, ppmCloseoutGblocs bool) bool { +func queueMoveIsAssignable(move models.Move, assignedToUser *ghcmessages.AssignedOfficeUser, isCloseoutQueue bool, officeUser models.OfficeUser, ppmCloseoutGblocs bool, activeRole string) bool { // default to false isAssignable := false // HQ role is read only - if officeUser.User.Roles.HasRole(roles.RoleTypeHQ) { + if activeRole == string(roles.RoleTypeHQ) { isAssignable = false return isAssignable } @@ -2224,13 +2248,13 @@ func queueMoveIsAssignable(move models.Move, assignedToUser *ghcmessages.Assigne isSupervisor := officeUser.User.Privileges.HasPrivilege(models.PrivilegeTypeSupervisor) // in TOO queues, all moves are assignable for supervisor users - if officeUser.User.Roles.HasRole(roles.RoleTypeTOO) && isSupervisor { + if activeRole == string(roles.RoleTypeTOO) && isSupervisor { isAssignable = true } // if it is assigned in the SCs queue // it is only assignable if the user is a supervisor... - if officeUser.User.Roles.HasRole(roles.RoleTypeServicesCounselor) && isSupervisor { + if activeRole == string(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 @@ -2250,35 +2274,36 @@ func queueMoveIsAssignable(move models.Move, assignedToUser *ghcmessages.Assigne } 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 { - officeUsers = append(officeUsers, *move.SCAssignedUser) - } + // 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 { + officeUsers = append(officeUsers, *move.SCAssignedUser) + } - // if there is no counseling office - // OR if our current user doesn't work at the move's counseling office - // only available user should be themself - if !isCloseoutQueue && (move.CounselingOfficeID == nil) || (move.CounselingOfficeID != nil && *move.CounselingOfficeID != officeUser.TransportationOfficeID) { - officeUsers = models.OfficeUsers{officeUser} - } + var onlySelfAssign bool - // if its the closeout queue and its not a Navy, Marine, or Coast Guard user - // and the move doesn't have a closeout office - // OR the move's closeout office is not the office users office - // only available user should be themself - if isCloseoutQueue && !ppmCloseoutGblocs && move.CloseoutOfficeID == nil || (move.CloseoutOfficeID != nil && *move.CloseoutOfficeID != officeUser.TransportationOfficeID) { - officeUsers = models.OfficeUsers{officeUser} + // if there is no counseling office + // OR if our current user doesn't work at the move's counseling office + // only available user should be themself + onlySelfAssign = (move.CounselingOfficeID == nil) || (move.CounselingOfficeID != nil && *move.CounselingOfficeID != officeUser.TransportationOfficeID) + if !isCloseoutQueue && onlySelfAssign { + officeUsers = models.OfficeUsers{officeUser} + } - } + // if its the closeout queue and its not a Navy, Marine, or Coast Guard user + // and the move doesn't have a closeout office + // OR the move's closeout office is not the office users office + // only available user should be themself + onlySelfAssign = (move.CloseoutOfficeID == nil) || (move.CloseoutOfficeID != nil && *move.CloseoutOfficeID != officeUser.TransportationOfficeID) + if isCloseoutQueue && !ppmCloseoutGblocs && onlySelfAssign { + officeUsers = models.OfficeUsers{officeUser} } return officeUsers } // QueueMoves payload -func QueueMoves(moves []models.Move, officeUsers []models.OfficeUser, requestedPpmStatus *models.PPMShipmentStatus, officeUser models.OfficeUser, officeUsersSafety []models.OfficeUser) *ghcmessages.QueueMoves { +func QueueMoves(moves []models.Move, officeUsers []models.OfficeUser, requestedPpmStatus *models.PPMShipmentStatus, officeUser models.OfficeUser, officeUsersSafety []models.OfficeUser, activeRole string) *ghcmessages.QueueMoves { queueMoves := make(ghcmessages.QueueMoves, len(moves)) for i, move := range moves { customer := move.Orders.ServiceMember @@ -2350,10 +2375,10 @@ func QueueMoves(moves []models.Move, officeUsers []models.OfficeUser, requestedP // determine if there is an assigned user var assignedToUser *ghcmessages.AssignedOfficeUser - if officeUser.User.Roles.HasRole(roles.RoleTypeServicesCounselor) && move.SCAssignedUser != nil { + if (activeRole == string(roles.RoleTypeServicesCounselor) || activeRole == string(roles.RoleTypeHQ)) && move.SCAssignedUser != nil { assignedToUser = AssignedOfficeUser(move.SCAssignedUser) } - if officeUser.User.Roles.HasRole(roles.RoleTypeTOO) && move.TOOAssignedUser != nil { + if (activeRole == string(roles.RoleTypeTOO) || activeRole == string(roles.RoleTypeHQ)) && move.TOOAssignedUser != nil { assignedToUser = AssignedOfficeUser(move.TOOAssignedUser) } @@ -2362,7 +2387,7 @@ 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, officeUser, ppmCloseoutGblocs) + assignable := queueMoveIsAssignable(move, assignedToUser, isCloseoutQueue, officeUser, ppmCloseoutGblocs, activeRole) isSupervisor := officeUser.User.Privileges.HasPrivilege(models.PrivilegeTypeSupervisor) // only need to attach available office users if move is assignable @@ -2374,7 +2399,22 @@ func QueueMoves(moves []models.Move, officeUsers []models.OfficeUser, requestedP if isSupervisor && move.Orders.OrdersType == "SAFETY" { availableOfficeUsers = officeUsersSafety } - if officeUser.User.Roles.HasRole(roles.RoleTypeServicesCounselor) { + + // if the assigned user is not in the returned list of available users append them to the end + if (activeRole == string(roles.RoleTypeTOO)) && (move.TOOAssignedUser != nil) { + userFound := false + for _, officeUser := range availableOfficeUsers { + if officeUser.ID == *move.TOOAssignedID { + userFound = true + break + } + } + if !userFound { + availableOfficeUsers = append(availableOfficeUsers, *move.TOOAssignedUser) + } + } + + if activeRole == string(roles.RoleTypeServicesCounselor) { availableOfficeUsers = servicesCounselorAvailableOfficeUsers(move, availableOfficeUsers, officeUser, ppmCloseoutGblocs, isCloseoutQueue) } @@ -2475,7 +2515,7 @@ func queuePaymentRequestStatus(paymentRequest models.PaymentRequest) string { } // QueuePaymentRequests payload -func QueuePaymentRequests(paymentRequests *models.PaymentRequests, officeUsers []models.OfficeUser, officeUser models.OfficeUser, officeUsersSafety []models.OfficeUser) *ghcmessages.QueuePaymentRequests { +func QueuePaymentRequests(paymentRequests *models.PaymentRequests, officeUsers []models.OfficeUser, officeUser models.OfficeUser, officeUsersSafety []models.OfficeUser, activeRole string) *ghcmessages.QueuePaymentRequests { queuePaymentRequests := make(ghcmessages.QueuePaymentRequests, len(*paymentRequests)) @@ -2520,7 +2560,7 @@ func QueuePaymentRequests(paymentRequests *models.PaymentRequests, officeUsers [ isAssignable = true } - if officeUser.User.Roles.HasRole(roles.RoleTypeHQ) { + if activeRole == string(roles.RoleTypeHQ) { isAssignable = false } @@ -2532,6 +2572,22 @@ func QueuePaymentRequests(paymentRequests *models.PaymentRequests, officeUsers [ if isSupervisor && orders.OrdersType == "SAFETY" { availableOfficeUsers = officeUsersSafety } + + // if the assigned user is not in the returned list of available users append them to the end + if paymentRequest.MoveTaskOrder.TIOAssignedUser != nil { + userFound := false + for _, officeUser := range availableOfficeUsers { + if officeUser.ID == paymentRequest.MoveTaskOrder.TIOAssignedUser.ID { + userFound = true + break + } + } + if !userFound { + availableOfficeUsers = append(availableOfficeUsers, *paymentRequest.MoveTaskOrder.TIOAssignedUser) + } + } + + // if they're not a supervisor and it is assignable, the only option should be themself if !isSupervisor { availableOfficeUsers = models.OfficeUsers{officeUser} } 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 5c883654ecf..e1f22b64ee8 100644 --- a/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go +++ b/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go @@ -221,7 +221,8 @@ func (suite *PayloadsSuite) TestPaymentRequestQueue() { var officeUsers models.OfficeUsers var officeUsersSafety models.OfficeUsers officeUsers = append(officeUsers, officeUser) - var paymentRequestsQueue = QueuePaymentRequests(&paymentRequests, officeUsers, officeUser, officeUsersSafety) + activeRole := string(roles.RoleTypeTIO) + var paymentRequestsQueue = QueuePaymentRequests(&paymentRequests, officeUsers, officeUser, officeUsersSafety, activeRole) suite.Run("Test Payment request is assignable due to not being assigend", func() { paymentRequestCopy := *paymentRequestsQueue @@ -240,7 +241,7 @@ func (suite *PayloadsSuite) TestPaymentRequestQueue() { paymentRequests[0].MoveTaskOrder.TIOAssignedUser = &officeUserTIO paymentRequests[0].MoveTaskOrder.CounselingOffice = &transportationOffice - paymentRequestsQueue = QueuePaymentRequests(&paymentRequests, officeUsers, officeUser, officeUsersSafety) + paymentRequestsQueue = QueuePaymentRequests(&paymentRequests, officeUsers, officeUser, officeUsersSafety, activeRole) suite.Run("Test PaymentRequest has both Counseling Office and TIO AssignedUser ", func() { PaymentRequestsCopy := *paymentRequestsQueue @@ -254,14 +255,14 @@ func (suite *PayloadsSuite) TestPaymentRequestQueue() { }) suite.Run("Test PaymentRequest is assignable due to user Supervisor role", func() { - paymentRequests := QueuePaymentRequests(&paymentRequests, officeUsers, officeUser, officeUsersSafety) + paymentRequests := QueuePaymentRequests(&paymentRequests, officeUsers, officeUser, officeUsersSafety, activeRole) paymentRequestCopy := *paymentRequests suite.Equal(paymentRequestCopy[0].Assignable, true) }) - officeUserHQ := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeHQ}) + activeRole = string(roles.RoleTypeHQ) suite.Run("Test PaymentRequest is not assignable due to user HQ role", func() { - paymentRequests := QueuePaymentRequests(&paymentRequests, officeUsers, officeUserHQ, officeUsersSafety) + paymentRequests := QueuePaymentRequests(&paymentRequests, officeUsers, officeUser, officeUsersSafety, activeRole) paymentRequestCopy := *paymentRequests suite.Equal(paymentRequestCopy[0].Assignable, false) }) @@ -667,6 +668,7 @@ func (suite *PayloadsSuite) TestEntitlement() { dependentsTwelveAndOver := 1 authorizedWeight := 8000 ubAllowance := 300 + weightRestriction := 1000 entitlement := &models.Entitlement{ ID: entitlementID, @@ -684,6 +686,7 @@ func (suite *PayloadsSuite) TestEntitlement() { DependentsTwelveAndOver: &dependentsTwelveAndOver, UpdatedAt: time.Now(), UBAllowance: &ubAllowance, + WeightRestriction: &weightRestriction, } returnedEntitlement := Entitlement(entitlement) @@ -705,6 +708,7 @@ func (suite *PayloadsSuite) TestEntitlement() { suite.Equal(models.BoolPointer(accompaniedTour), returnedEntitlement.AccompaniedTour) suite.Equal(dependentsUnderTwelve, int(*returnedEntitlement.DependentsUnderTwelve)) suite.Equal(dependentsTwelveAndOver, int(*returnedEntitlement.DependentsTwelveAndOver)) + suite.Equal(weightRestriction, int(*returnedEntitlement.WeightRestriction)) } func (suite *PayloadsSuite) TestCreateCustomer() { @@ -1681,6 +1685,83 @@ func (suite *PayloadsSuite) TestMTOShipment_POE_POD_Locations() { }) } +func (suite *PayloadsSuite) TestPPMCloseout() { + plannedMoveDate := time.Now() + actualMoveDate := time.Now() + miles := 1200 + estimatedWeight := unit.Pound(5000) + actualWeight := unit.Pound(5200) + proGearWeightCustomer := unit.Pound(300) + proGearWeightSpouse := unit.Pound(100) + grossIncentive := unit.Cents(100000) + gcc := unit.Cents(50000) + aoa := unit.Cents(20000) + remainingIncentive := unit.Cents(30000) + haulType := "Linehaul" + haulPrice := unit.Cents(40000) + haulFSC := unit.Cents(5000) + dop := unit.Cents(10000) + ddp := unit.Cents(8000) + packPrice := unit.Cents(7000) + unpackPrice := unit.Cents(6000) + intlPackPrice := unit.Cents(15000) + intlUnpackPrice := unit.Cents(14000) + intlLinehaulPrice := unit.Cents(13000) + sitReimbursement := unit.Cents(12000) + + ppmCloseout := models.PPMCloseout{ + ID: models.UUIDPointer(uuid.Must(uuid.NewV4())), + PlannedMoveDate: &plannedMoveDate, + ActualMoveDate: &actualMoveDate, + Miles: &miles, + EstimatedWeight: &estimatedWeight, + ActualWeight: &actualWeight, + ProGearWeightCustomer: &proGearWeightCustomer, + ProGearWeightSpouse: &proGearWeightSpouse, + GrossIncentive: &grossIncentive, + GCC: &gcc, + AOA: &aoa, + RemainingIncentive: &remainingIncentive, + HaulType: (*models.HaulType)(&haulType), + HaulPrice: &haulPrice, + HaulFSC: &haulFSC, + DOP: &dop, + DDP: &ddp, + PackPrice: &packPrice, + UnpackPrice: &unpackPrice, + IntlPackPrice: &intlPackPrice, + IntlUnpackPrice: &intlUnpackPrice, + IntlLinehaulPrice: &intlLinehaulPrice, + SITReimbursement: &sitReimbursement, + } + + payload := PPMCloseout(&ppmCloseout) + suite.NotNil(payload) + suite.Equal(ppmCloseout.ID.String(), payload.ID.String()) + suite.Equal(handlers.FmtDatePtr(ppmCloseout.PlannedMoveDate), payload.PlannedMoveDate) + suite.Equal(handlers.FmtDatePtr(ppmCloseout.ActualMoveDate), payload.ActualMoveDate) + suite.Equal(handlers.FmtIntPtrToInt64(ppmCloseout.Miles), payload.Miles) + suite.Equal(handlers.FmtPoundPtr(ppmCloseout.EstimatedWeight), payload.EstimatedWeight) + suite.Equal(handlers.FmtPoundPtr(ppmCloseout.ActualWeight), payload.ActualWeight) + suite.Equal(handlers.FmtPoundPtr(ppmCloseout.ProGearWeightCustomer), payload.ProGearWeightCustomer) + suite.Equal(handlers.FmtPoundPtr(ppmCloseout.ProGearWeightSpouse), payload.ProGearWeightSpouse) + suite.Equal(handlers.FmtCost(ppmCloseout.GrossIncentive), payload.GrossIncentive) + suite.Equal(handlers.FmtCost(ppmCloseout.GCC), payload.Gcc) + suite.Equal(handlers.FmtCost(ppmCloseout.AOA), payload.Aoa) + suite.Equal(handlers.FmtCost(ppmCloseout.RemainingIncentive), payload.RemainingIncentive) + suite.Equal((*string)(ppmCloseout.HaulType), payload.HaulType) + suite.Equal(handlers.FmtCost(ppmCloseout.HaulPrice), payload.HaulPrice) + suite.Equal(handlers.FmtCost(ppmCloseout.HaulFSC), payload.HaulFSC) + suite.Equal(handlers.FmtCost(ppmCloseout.DOP), payload.Dop) + suite.Equal(handlers.FmtCost(ppmCloseout.DDP), payload.Ddp) + suite.Equal(handlers.FmtCost(ppmCloseout.PackPrice), payload.PackPrice) + suite.Equal(handlers.FmtCost(ppmCloseout.UnpackPrice), payload.UnpackPrice) + suite.Equal(handlers.FmtCost(ppmCloseout.IntlPackPrice), payload.IntlPackPrice) + suite.Equal(handlers.FmtCost(ppmCloseout.IntlUnpackPrice), payload.IntlUnpackPrice) + suite.Equal(handlers.FmtCost(ppmCloseout.IntlLinehaulPrice), payload.IntlLinehaulPrice) + suite.Equal(handlers.FmtCost(ppmCloseout.SITReimbursement), payload.SITReimbursement) +} + func (suite *PayloadsSuite) TestPaymentServiceItemPayload() { mtoServiceItemID := uuid.Must(uuid.NewV4()) mtoShipmentID := uuid.Must(uuid.NewV4()) @@ -1836,3 +1917,34 @@ func (suite *PayloadsSuite) TestPaymentServiceItemsPayload() { suite.Nil(psItem2.TppsInvoiceAmountPaidPerServiceItemMillicents) }) } + +func (suite *PayloadsSuite) TestCounselingOffices() { + suite.Run("correctly maps transportaion offices to counseling offices payload", func() { + office1 := factory.BuildTransportationOffice(nil, []factory.Customization{ + { + Model: models.TransportationOffice{ + ID: uuid.Must(uuid.NewV4()), + Name: "PPPO Fort Liberty", + }, + }, + }, nil) + + office2 := factory.BuildTransportationOffice(nil, []factory.Customization{ + { + Model: models.TransportationOffice{ + ID: uuid.Must(uuid.NewV4()), + Name: "PPPO Fort Walker", + }, + }, + }, nil) + + offices := models.TransportationOffices{office1, office2} + + payload := CounselingOffices(offices) + + suite.IsType(payload, ghcmessages.CounselingOffices{}) + suite.Equal(2, len(payload)) + suite.Equal(office1.ID.String(), payload[0].ID.String()) + suite.Equal(office2.ID.String(), payload[1].ID.String()) + }) +} diff --git a/pkg/handlers/ghcapi/move_task_order.go b/pkg/handlers/ghcapi/move_task_order.go index 2c9196b68be..5edab32d99a 100644 --- a/pkg/handlers/ghcapi/move_task_order.go +++ b/pkg/handlers/ghcapi/move_task_order.go @@ -17,7 +17,6 @@ import ( "github.com/transcom/mymove/pkg/services" "github.com/transcom/mymove/pkg/services/audit" "github.com/transcom/mymove/pkg/services/event" - movetaskorder "github.com/transcom/mymove/pkg/services/move_task_order" ) // GetMoveTaskOrderHandler fetches a Move Task Order @@ -74,13 +73,7 @@ func (h UpdateMoveTaskOrderStatusHandlerFunc) Handle(params movetaskorderops.Upd serviceItemCodes = *params.ServiceItemCodes } - checker := movetaskorder.NewMoveTaskOrderChecker() - availableBefore, err := checker.MTOAvailableToPrime(appCtx, moveTaskOrderID) - if err != nil { - return movetaskorderops.NewUpdateMoveTaskOrderStatusInternalServerError(), err - } - - mto, err := h.moveTaskOrderStatusUpdater.MakeAvailableToPrime(appCtx, moveTaskOrderID, eTag, + mto, err := h.moveTaskOrderStatusUpdater.ApproveMoveAndCreateServiceItems(appCtx, moveTaskOrderID, eTag, serviceItemCodes.ServiceCodeMS, serviceItemCodes.ServiceCodeCS) if err != nil { @@ -106,35 +99,11 @@ func (h UpdateMoveTaskOrderStatusHandlerFunc) Handle(params movetaskorderops.Upd } } - if !availableBefore { - availableAfter, checkErr := checker.MTOAvailableToPrime(appCtx, moveTaskOrderID) - if checkErr != nil { - return movetaskorderops.NewUpdateMoveTaskOrderStatusInternalServerError(), err - } - - /* Do not send TOO approving and submitting service items email if BLUEBARK/SAFETY */ - if availableAfter && mto.Orders.CanSendEmailWithOrdersType() { - emailErr := h.NotificationSender().SendNotification(appCtx, - notifications.NewMoveIssuedToPrime(moveTaskOrderID), - ) - if emailErr != nil { - return movetaskorderops.NewUpdateMoveTaskOrderStatusInternalServerError(), err - } - } - } - moveTaskOrderPayload, err := payloads.Move(mto, h.FileStorer()) if err != nil { return movetaskorderops.NewUpdateMoveTaskOrderStatusInternalServerError(), err } - // Audit attempt to make MTO available to prime - _, err = audit.Capture(appCtx, mto, moveTaskOrderPayload, params.HTTPRequest) - if err != nil { - appCtx.Logger().Error("Auditing service error for making MTO available to Prime.", zap.Error(err)) - return movetaskorderops.NewUpdateMoveTaskOrderStatusInternalServerError(), err - } - _, err = event.TriggerEvent(event.Event{ EventKey: event.MoveTaskOrderUpdateEventKey, MtoID: mto.ID, diff --git a/pkg/handlers/ghcapi/move_task_order_test.go b/pkg/handlers/ghcapi/move_task_order_test.go index 316eae9b0c8..2500e841f51 100644 --- a/pkg/handlers/ghcapi/move_task_order_test.go +++ b/pkg/handlers/ghcapi/move_task_order_test.go @@ -217,9 +217,7 @@ func (suite *HandlerSuite) TestUpdateMoveTaskOrderHandlerIntegrationSuccess() { suite.Assertions.IsType(&movetaskorderops.UpdateMoveTaskOrderStatusOK{}, response) suite.Equal(strfmt.UUID(move.ID.String()), movePayload.ID) - suite.NotNil(movePayload.AvailableToPrimeAt) suite.NotNil(movePayload.ApprovedAt) - suite.HasWebhookNotification(move.ID, traceID) // this action always creates a notification for the Prime // also check MTO level service items are properly created var serviceItems models.MTOServiceItems @@ -263,7 +261,7 @@ func (suite *HandlerSuite) TestUpdateMoveTaskOrderHandlerIntegrationWithStaleEta // Stale ETags are already unit tested in the move_task_order_updater_test, // so we can mock this here to speed up the test and avoid hitting the DB moveUpdater := &mocks.MoveTaskOrderUpdater{} - moveUpdater.On("MakeAvailableToPrime", + moveUpdater.On("ApproveMoveAndCreateServiceItems", mock.AnythingOfType("*appcontext.appContext"), mock.Anything, mock.Anything, diff --git a/pkg/handlers/ghcapi/mto_shipment.go b/pkg/handlers/ghcapi/mto_shipment.go index 53011950b49..1e5453049e7 100644 --- a/pkg/handlers/ghcapi/mto_shipment.go +++ b/pkg/handlers/ghcapi/mto_shipment.go @@ -23,6 +23,7 @@ import ( "github.com/transcom/mymove/pkg/models/roles" "github.com/transcom/mymove/pkg/notifications" "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/services/audit" "github.com/transcom/mymove/pkg/services/event" mtoshipment "github.com/transcom/mymove/pkg/services/mto_shipment" ppmshipment "github.com/transcom/mymove/pkg/services/ppmshipment" @@ -511,6 +512,7 @@ type ApproveShipmentHandler struct { handlers.HandlerConfig services.ShipmentApprover services.ShipmentSITStatus + services.MoveTaskOrderUpdater } // Handle approves a shipment @@ -551,6 +553,49 @@ func (h ApproveShipmentHandler) Handle(params shipmentops.ApproveShipmentParams) return handleError(err) } + move, wasMadeAvailableToPrime, err := h.MakeAvailableToPrime(appCtx, shipment.MoveTaskOrderID) + if err != nil { + appCtx.Logger().Error("Error making move available to prime", zap.Error(err)) + return handleError(err) + } + + // Execute tasks if the move has just become available to Prime (migrated from move_task_order.go) + if wasMadeAvailableToPrime { + /* Do not send TOO approving and submitting service items email if BLUEBARK/SAFETY */ + if move.Orders.CanSendEmailWithOrdersType() { + emailErr := h.NotificationSender().SendNotification(appCtx, + notifications.NewMoveIssuedToPrime(move.ID), + ) + if emailErr != nil { + return handleError(err) + } + } + + // Prepare move payload for auditing + moveTaskOrderPayload, err := payloads.Move(move, h.FileStorer()) + if err != nil { + return handleError(err) + } + // Audit attempt to make MTO available to prime + _, err = audit.Capture(appCtx, move, moveTaskOrderPayload, params.HTTPRequest) + if err != nil { + appCtx.Logger().Error("Auditing service error for making MTO available to Prime.", zap.Error(err)) + return handleError(err) + } + // Move update event + _, err = event.TriggerEvent(event.Event{ + EventKey: event.MoveTaskOrderUpdateEventKey, + MtoID: move.ID, + UpdatedObjectID: move.ID, + EndpointKey: event.GhcUpdateMoveTaskOrderStatusEndpointKey, + AppContext: appCtx, + TraceID: h.GetTraceIDFromRequest(params.HTTPRequest), + }) + if err != nil { + appCtx.Logger().Error("ghcapi.ApproveShipmentHandlerFunc could not generate the event") + } + } + h.triggerShipmentApprovalEvent(appCtx, shipmentID, shipment.MoveTaskOrderID, params) shipmentSITStatus, _, err := h.CalculateShipmentSITStatus(appCtx, *shipment) @@ -582,6 +627,164 @@ func (h ApproveShipmentHandler) triggerShipmentApprovalEvent(appCtx appcontext.A } } +// ApproveShipmentsHandler approves one or more shipments +type ApproveShipmentsHandler struct { + handlers.HandlerConfig + services.ShipmentApprover + services.ShipmentSITStatus + services.MoveTaskOrderUpdater +} + +// Handle approves one or more shipments +func (h ApproveShipmentsHandler) Handle(params shipmentops.ApproveShipmentsParams) middleware.Responder { + return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, + func(appCtx appcontext.AppContext) (middleware.Responder, error) { + + // Check user permissions (TOO role required) + if !appCtx.Session().IsOfficeUser() || !appCtx.Session().Roles.HasRole(roles.RoleTypeTOO) { + forbiddenError := apperror.NewForbiddenError("Only TOO role can approve shipments") + appCtx.Logger().Error(forbiddenError.Error()) + return shipmentops.NewApproveShipmentsForbidden(), forbiddenError + } + + handleError := func(err error) (middleware.Responder, error) { + appCtx.Logger().Error("ghcapi.ApproveShipmentsHandler", zap.Error(err)) + payload := &ghcmessages.Error{Message: handlers.FmtString(err.Error())} + + switch e := err.(type) { + case apperror.NotFoundError: + return shipmentops.NewApproveShipmentsNotFound().WithPayload(payload), err + case apperror.InvalidInputError: + payload := payloadForValidationError("Validation errors", "ApproveShipments", h.GetTraceIDFromRequest(params.HTTPRequest), e.ValidationErrors) + return shipmentops.NewApproveShipmentsUnprocessableEntity().WithPayload(payload), err + case apperror.PreconditionFailedError: + return shipmentops.NewApproveShipmentsPreconditionFailed(). + WithPayload(&ghcmessages.Error{Message: handlers.FmtString(err.Error())}), err + case apperror.ConflictError, mtoshipment.ConflictStatusError: + return shipmentops.NewApproveShipmentsConflict().WithPayload(&ghcmessages.Error{Message: handlers.FmtString(err.Error())}), err + default: + return shipmentops.NewApproveShipmentsInternalServerError(). + WithPayload(&ghcmessages.Error{Message: handlers.FmtString("Internal server errors")}), err + } + } + + if len(params.Body.ApproveShipments) == 0 { + appCtx.Logger().Error("Invalid mto shipment: params Body is nil") + emptyBodyError := apperror.NewBadDataError("The MTO Shipment request body cannot be empty.") + payload := payloadForValidationError( + "Empty body error", + emptyBodyError.Error(), + h.GetTraceIDFromRequest(params.HTTPRequest), + validate.NewErrors(), + ) + + return shipmentops.NewApproveShipmentsUnprocessableEntity().WithPayload(payload), emptyBodyError + } + + var shipmentIdWithEtagArr []services.ShipmentIdWithEtag + for _, shipment := range params.Body.ApproveShipments { + shipmentID := uuid.FromStringOrNil(shipment.ShipmentID.String()) + + if shipment.ETag == nil { + return shipmentops.NewApproveShipmentsPreconditionFailed(). + WithPayload(&ghcmessages.Error{Message: handlers.FmtString("eTag is required for each shipment")}), nil + } + + shipmentIdWithEtagArr = append(shipmentIdWithEtagArr, services.ShipmentIdWithEtag{ + ShipmentID: shipmentID, + ETag: *shipment.ETag, + }) + } + + // Approve shipments + approvedShipments, err := h.ShipmentApprover.ApproveShipments(appCtx, shipmentIdWithEtagArr) + if err != nil { + appCtx.Logger().Error("Error approving shipments", zap.Error(err)) + return handleError(err) + } + + // Make the move available to prime + if approvedShipments != nil && len(*approvedShipments) > 0 { + move, wasMadeAvailableToPrime, err := h.MakeAvailableToPrime(appCtx, (*approvedShipments)[0].MoveTaskOrderID) + if err != nil { + appCtx.Logger().Error("Error making move available to prime", zap.Error(err)) + return handleError(err) + } + + // Execute tasks if the move has just become available to Prime (migrated from move_task_order.go) + if wasMadeAvailableToPrime { + /* Do not send TOO approving and submitting service items email if BLUEBARK/SAFETY */ + if move.Orders.CanSendEmailWithOrdersType() { + emailErr := h.NotificationSender().SendNotification(appCtx, + notifications.NewMoveIssuedToPrime(move.ID), + ) + if emailErr != nil { + return handleError(err) + } + } + + // Prepare move payload for auditing + moveTaskOrderPayload, err := payloads.Move(move, h.FileStorer()) + if err != nil { + return handleError(err) + } + // Audit attempt to make MTO available to prime + _, err = audit.Capture(appCtx, move, moveTaskOrderPayload, params.HTTPRequest) + if err != nil { + appCtx.Logger().Error("Auditing service error for making MTO available to Prime.", zap.Error(err)) + return handleError(err) + } + // Move update event + _, err = event.TriggerEvent(event.Event{ + EventKey: event.MoveTaskOrderUpdateEventKey, + MtoID: move.ID, + UpdatedObjectID: move.ID, + EndpointKey: event.GhcUpdateMoveTaskOrderStatusEndpointKey, + AppContext: appCtx, + TraceID: h.GetTraceIDFromRequest(params.HTTPRequest), + }) + if err != nil { + appCtx.Logger().Error("ghcapi.ApproveShipmentsHandlerFunc could not generate the event") + } + } + } + + // Prepare successful response payload + payload := make(ghcmessages.MTOShipments, len(*approvedShipments)) + for i, approvedShipment := range *approvedShipments { + h.triggerShipmentApprovalEvent(appCtx, approvedShipment.ID, approvedShipment.MoveTaskOrderID, params) + + shipmentSITStatus, _, err := h.CalculateShipmentSITStatus(appCtx, approvedShipment) + if err != nil { + return handleError(err) + } + + sitStatusPayload := payloads.SITStatus(shipmentSITStatus, h.FileStorer()) + payload[i] = payloads.MTOShipment(h.FileStorer(), &approvedShipment, sitStatusPayload) + } + + return shipmentops.NewApproveShipmentsOK().WithPayload(payload), nil + }) +} + +func (h ApproveShipmentsHandler) triggerShipmentApprovalEvent(appCtx appcontext.AppContext, shipmentID uuid.UUID, moveID uuid.UUID, params shipmentops.ApproveShipmentsParams) { + + _, err := event.TriggerEvent(event.Event{ + EndpointKey: event.GhcApproveShipmentsEndpointKey, + // Endpoint that is being handled + EventKey: event.ShipmentApproveEventKey, // Event that you want to trigger + UpdatedObjectID: shipmentID, // ID of the updated logical object + MtoID: moveID, // ID of the associated Move + AppContext: appCtx, + TraceID: h.GetTraceIDFromRequest(params.HTTPRequest), + }) + + // If the event trigger fails, just log the error. + if err != nil { + appCtx.Logger().Error("ghcapi.ApproveShipmentsHandler could not generate the event", zap.Error(err)) + } +} + // RequestShipmentDiversionHandler Requests a shipment diversion type RequestShipmentDiversionHandler struct { handlers.HandlerConfig diff --git a/pkg/handlers/ghcapi/mto_shipment_test.go b/pkg/handlers/ghcapi/mto_shipment_test.go index 67c79389e8c..70ac78cbaac 100644 --- a/pkg/handlers/ghcapi/mto_shipment_test.go +++ b/pkg/handlers/ghcapi/mto_shipment_test.go @@ -582,8 +582,62 @@ func (suite *HandlerSuite) TestGetShipmentHandler() { func (suite *HandlerSuite) TestApproveShipmentHandler() { waf := entitlements.NewWeightAllotmentFetcher() + setUpSignedCertificationCreatorMock := func(returnValue ...interface{}) services.SignedCertificationCreator { + mockCreator := &mocks.SignedCertificationCreator{} + + mockCreator.On( + "CreateSignedCertification", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.SignedCertification"), + ).Return(returnValue...) + + return mockCreator + } + + setUpSignedCertificationUpdaterMock := func(returnValue ...interface{}) services.SignedCertificationUpdater { + mockUpdater := &mocks.SignedCertificationUpdater{} + + mockUpdater.On( + "UpdateSignedCertification", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.SignedCertification"), + mock.AnythingOfType("string"), + ).Return(returnValue...) + + return mockUpdater + } + + builder := query.NewQueryBuilder() + moveRouter := moveservices.NewMoveRouter() + planner := &routemocks.Planner{} + planner.On("ZipTransitDistance", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + false, + false, + ).Return(400, nil) + ppmEstimator := mocks.PPMEstimator{} + planner.On("ZipTransitDistance", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + false, + ).Return(400, nil) + moveTaskOrderUpdater := movetaskorder.NewMoveTaskOrderUpdater( + builder, + mtoserviceitem.NewMTOServiceItemCreator(planner, builder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()), + moveRouter, setUpSignedCertificationCreatorMock(nil, nil), setUpSignedCertificationUpdaterMock(nil, nil), &ppmEstimator, + ) + suite.Run("Returns 200 when all validations pass", func() { - move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + }, + }, + }, nil) shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ { Model: move, @@ -598,9 +652,6 @@ func (suite *HandlerSuite) TestApproveShipmentHandler() { eTag := etag.GenerateEtag(shipment.UpdatedAt) officeUser := factory.BuildOfficeUserWithRoles(nil, nil, []roles.RoleType{roles.RoleTypeTOO}) - builder := query.NewQueryBuilder() - moveRouter := moveservices.NewMoveRouter() - planner := &routemocks.Planner{} moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), @@ -613,6 +664,8 @@ func (suite *HandlerSuite) TestApproveShipmentHandler() { mtoserviceitem.NewMTOServiceItemCreator(planner, builder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()), &routemocks.Planner{}, moveWeights, + moveTaskOrderUpdater, + moveRouter, ) req := httptest.NewRequest("POST", fmt.Sprintf("/shipments/%s/approve", shipment.ID.String()), nil) @@ -628,6 +681,7 @@ func (suite *HandlerSuite) TestApproveShipmentHandler() { handlerConfig, approver, sitstatus.NewShipmentSITStatus(), + moveTaskOrderUpdater, } approveParams := shipmentops.ApproveShipmentParams{ @@ -646,6 +700,7 @@ func (suite *HandlerSuite) TestApproveShipmentHandler() { suite.NoError(payload.Validate(strfmt.Default)) suite.HasWebhookNotification(shipment.ID, traceID) + suite.HasWebhookNotification(move.ID, traceID) // this action always creates a notification for the Prime }) suite.Run("Returns a 403 when the office user is not a TOO", func() { @@ -663,6 +718,7 @@ func (suite *HandlerSuite) TestApproveShipmentHandler() { handlerConfig, approver, sitstatus.NewShipmentSITStatus(), + moveTaskOrderUpdater, } approveParams := shipmentops.ApproveShipmentParams{ HTTPRequest: req, @@ -702,6 +758,7 @@ func (suite *HandlerSuite) TestApproveShipmentHandler() { handlerConfig, approver, sitstatus.NewShipmentSITStatus(), + moveTaskOrderUpdater, } approveParams := shipmentops.ApproveShipmentParams{ HTTPRequest: req, @@ -741,6 +798,7 @@ func (suite *HandlerSuite) TestApproveShipmentHandler() { handlerConfig, approver, sitstatus.NewShipmentSITStatus(), + moveTaskOrderUpdater, } approveParams := shipmentops.ApproveShipmentParams{ HTTPRequest: req, @@ -780,6 +838,7 @@ func (suite *HandlerSuite) TestApproveShipmentHandler() { handlerConfig, approver, sitstatus.NewShipmentSITStatus(), + moveTaskOrderUpdater, } approveParams := shipmentops.ApproveShipmentParams{ HTTPRequest: req, @@ -819,6 +878,7 @@ func (suite *HandlerSuite) TestApproveShipmentHandler() { handlerConfig, approver, sitstatus.NewShipmentSITStatus(), + moveTaskOrderUpdater, } approveParams := shipmentops.ApproveShipmentParams{ HTTPRequest: req, @@ -858,6 +918,7 @@ func (suite *HandlerSuite) TestApproveShipmentHandler() { handlerConfig, approver, sitstatus.NewShipmentSITStatus(), + moveTaskOrderUpdater, } approveParams := shipmentops.ApproveShipmentParams{ HTTPRequest: req, @@ -876,6 +937,462 @@ func (suite *HandlerSuite) TestApproveShipmentHandler() { }) } +// ApproveShipment(s)Handler +func (suite *HandlerSuite) TestApproveShipmentsHandler() { + waf := entitlements.NewWeightAllotmentFetcher() + + setUpSignedCertificationCreatorMock := func(returnValue ...interface{}) services.SignedCertificationCreator { + mockCreator := &mocks.SignedCertificationCreator{} + + mockCreator.On( + "CreateSignedCertification", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.SignedCertification"), + ).Return(returnValue...) + + return mockCreator + } + + setUpSignedCertificationUpdaterMock := func(returnValue ...interface{}) services.SignedCertificationUpdater { + mockUpdater := &mocks.SignedCertificationUpdater{} + + mockUpdater.On( + "UpdateSignedCertification", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.SignedCertification"), + mock.AnythingOfType("string"), + ).Return(returnValue...) + + return mockUpdater + } + + officeUser := factory.BuildOfficeUserWithRoles(nil, nil, []roles.RoleType{roles.RoleTypeTOO}) + builder := query.NewQueryBuilder() + moveRouter := moveservices.NewMoveRouter() + planner := &routemocks.Planner{} + moveWeights := moveservices.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) + planner.On("ZipTransitDistance", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + false, + false, + ).Return(400, nil) + + ppmEstimator := mocks.PPMEstimator{} + planner.On("ZipTransitDistance", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + false, + ).Return(400, nil) + moveTaskOrderUpdater := movetaskorder.NewMoveTaskOrderUpdater( + builder, + mtoserviceitem.NewMTOServiceItemCreator(planner, builder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()), + moveRouter, setUpSignedCertificationCreatorMock(nil, nil), setUpSignedCertificationUpdaterMock(nil, nil), &ppmEstimator, + ) + + suite.Run("Returns 200 when all validations pass", func() { + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + }, + }, + }, nil) + shipment1 := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusSubmitted, + }, + }, + }, nil) + + shipment2 := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusSubmitted, + }, + }, + }, nil) + + eTag1 := etag.GenerateEtag(shipment1.UpdatedAt) + eTag2 := etag.GenerateEtag(shipment2.UpdatedAt) + + approver := mtoshipment.NewShipmentApprover( + mtoshipment.NewShipmentRouter(), + mtoserviceitem.NewMTOServiceItemCreator(planner, builder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()), + &routemocks.Planner{}, + moveWeights, + moveTaskOrderUpdater, + moveRouter, + ) + + req := httptest.NewRequest("POST", "/shipments/approve", nil) + req = suite.AuthenticateOfficeRequest(req, officeUser) + + traceID, err := uuid.NewV4() + suite.FatalNoError(err, "Error creating a new trace ID.") + req = req.WithContext(trace.NewContext(req.Context(), traceID)) + + handlerConfig := suite.HandlerConfig() + + handler := ApproveShipmentsHandler{ + handlerConfig, + approver, + sitstatus.NewShipmentSITStatus(), + moveTaskOrderUpdater, + } + + approveParams := shipmentops.ApproveShipmentsParams{ + HTTPRequest: req, + Body: &ghcmessages.ApproveShipments{ + ApproveShipments: []*ghcmessages.ApproveShipmentsApproveShipmentsItems0{ + { + ShipmentID: handlers.FmtUUID(shipment1.ID), + ETag: &eTag1, + }, + { + ShipmentID: handlers.FmtUUID(shipment2.ID), + ETag: &eTag2, + }, + }, + }, + } + + // Validate incoming payload: no body to validate + + response := handler.Handle(approveParams) + suite.IsType(&shipmentops.ApproveShipmentsOK{}, response) + payload := response.(*shipmentops.ApproveShipmentsOK).Payload + + // Validate outgoing payload + suite.NoError(payload[0].Validate(strfmt.Default)) + suite.NoError(payload[1].Validate(strfmt.Default)) + + suite.HasWebhookNotification(move.ID, traceID) // this action always creates a notification for the Prime + suite.HasWebhookNotification(shipment1.ID, traceID) + suite.HasWebhookNotification(shipment2.ID, traceID) + }) + + suite.Run("Returns a 403 when the office user is not a TOO", func() { + move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusSubmitted, + }, + }, + }, nil) + + eTag := etag.GenerateEtag(shipment.UpdatedAt) + + officeUser := factory.BuildOfficeUserWithRoles(nil, nil, []roles.RoleType{roles.RoleTypeServicesCounselor}) + approver := &mocks.ShipmentApprover{} + + approver.AssertNumberOfCalls(suite.T(), "ApproveShipments", 0) + + req := httptest.NewRequest("POST", "/shipments/approve", nil) + req = suite.AuthenticateOfficeRequest(req, officeUser) + handlerConfig := suite.HandlerConfig() + + handler := ApproveShipmentsHandler{ + handlerConfig, + approver, + sitstatus.NewShipmentSITStatus(), + moveTaskOrderUpdater, + } + approveParams := shipmentops.ApproveShipmentsParams{ + HTTPRequest: req, + Body: &ghcmessages.ApproveShipments{ + ApproveShipments: []*ghcmessages.ApproveShipmentsApproveShipmentsItems0{ + { + ShipmentID: handlers.FmtUUID(shipment.ID), + ETag: &eTag, + }, + }, + }, + } + + // Validate incoming payload: no body to validate + response := handler.Handle(approveParams) + suite.IsType(&shipmentops.ApproveShipmentsForbidden{}, response) + payload := response.(*shipmentops.ApproveShipmentsForbidden).Payload + + // Validate outgoing payload: nil payload + suite.Nil(payload) + }) + + suite.Run("Returns 404 when approver returns NotFoundError", func() { + shipment := factory.BuildMTOShipmentMinimal(nil, []factory.Customization{ + { + Model: models.MTOShipment{ + ID: uuid.Must(uuid.NewV4()), + }, + }, + }, nil) + eTag := etag.GenerateEtag(shipment.UpdatedAt) + officeUser := factory.BuildOfficeUserWithRoles(nil, nil, []roles.RoleType{roles.RoleTypeTOO}) + approver := &mocks.ShipmentApprover{} + + approver.On("ApproveShipments", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("[]services.ShipmentIdWithEtag")).Return(nil, apperror.NotFoundError{}) + + req := httptest.NewRequest("POST", "/shipments/approve", nil) + req = suite.AuthenticateOfficeRequest(req, officeUser) + handlerConfig := suite.HandlerConfig() + + handler := ApproveShipmentsHandler{ + handlerConfig, + approver, + sitstatus.NewShipmentSITStatus(), + moveTaskOrderUpdater, + } + approveParams := shipmentops.ApproveShipmentsParams{ + HTTPRequest: req, + Body: &ghcmessages.ApproveShipments{ + ApproveShipments: []*ghcmessages.ApproveShipmentsApproveShipmentsItems0{ + { + ShipmentID: handlers.FmtUUID(shipment.ID), + ETag: &eTag, + }, + }, + }, + } + + // Validate incoming payload: no body to validate + response := handler.Handle(approveParams) + suite.IsType(&shipmentops.ApproveShipmentsNotFound{}, response) + payload := response.(*shipmentops.ApproveShipmentsNotFound).Payload + + // Validate outgoing payload + suite.NoError(payload.Validate(strfmt.Default)) + }) + + suite.Run("Returns 409 when approver returns Conflict Error", func() { + shipment := factory.BuildMTOShipmentMinimal(nil, []factory.Customization{ + { + Model: models.MTOShipment{ + ID: uuid.Must(uuid.NewV4()), + }, + }, + }, nil) + eTag := etag.GenerateEtag(shipment.UpdatedAt) + officeUser := factory.BuildOfficeUserWithRoles(nil, nil, []roles.RoleType{roles.RoleTypeTOO}) + approver := &mocks.ShipmentApprover{} + + approver.On("ApproveShipments", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("[]services.ShipmentIdWithEtag")).Return(nil, apperror.ConflictError{}) + + req := httptest.NewRequest("POST", "/shipments/approve", nil) + req = suite.AuthenticateOfficeRequest(req, officeUser) + handlerConfig := suite.HandlerConfig() + + handler := ApproveShipmentsHandler{ + handlerConfig, + approver, + sitstatus.NewShipmentSITStatus(), + moveTaskOrderUpdater, + } + approveParams := shipmentops.ApproveShipmentsParams{ + HTTPRequest: req, + Body: &ghcmessages.ApproveShipments{ + ApproveShipments: []*ghcmessages.ApproveShipmentsApproveShipmentsItems0{ + { + ShipmentID: handlers.FmtUUID(shipment.ID), + ETag: &eTag, + }, + }, + }, + } + + // Validate incoming payload: no body to validate + response := handler.Handle(approveParams) + suite.IsType(&shipmentops.ApproveShipmentsConflict{}, response) + payload := response.(*shipmentops.ApproveShipmentsConflict).Payload + + // Validate outgoing payload + suite.NoError(payload.Validate(strfmt.Default)) + }) + + suite.Run("Returns 412 when eTag does not match", func() { + shipment := factory.BuildMTOShipmentMinimal(nil, []factory.Customization{ + { + Model: models.MTOShipment{ + ID: uuid.Must(uuid.NewV4()), + }, + }, + }, nil) + eTag := etag.GenerateEtag(time.Now()) + officeUser := factory.BuildOfficeUserWithRoles(nil, nil, []roles.RoleType{roles.RoleTypeTOO}) + approver := &mocks.ShipmentApprover{} + + approver.On("ApproveShipments", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("[]services.ShipmentIdWithEtag")).Return(nil, apperror.PreconditionFailedError{}) + + req := httptest.NewRequest("POST", "/shipments/approve", nil) + req = suite.AuthenticateOfficeRequest(req, officeUser) + handlerConfig := suite.HandlerConfig() + + handler := ApproveShipmentsHandler{ + handlerConfig, + approver, + sitstatus.NewShipmentSITStatus(), + moveTaskOrderUpdater, + } + approveParams := shipmentops.ApproveShipmentsParams{ + HTTPRequest: req, + Body: &ghcmessages.ApproveShipments{ + ApproveShipments: []*ghcmessages.ApproveShipmentsApproveShipmentsItems0{ + { + ShipmentID: handlers.FmtUUID(shipment.ID), + ETag: &eTag, + }, + }, + }, + } + + // Validate incoming payload: no body to validate + response := handler.Handle(approveParams) + suite.IsType(&shipmentops.ApproveShipmentsPreconditionFailed{}, response) + payload := response.(*shipmentops.ApproveShipmentsPreconditionFailed).Payload + + // Validate outgoing payload + suite.NoError(payload.Validate(strfmt.Default)) + }) + + suite.Run("Returns 422 when approver returns validation errors", func() { + shipment := factory.BuildMTOShipmentMinimal(nil, []factory.Customization{ + { + Model: models.MTOShipment{ + ID: uuid.Must(uuid.NewV4()), + }, + }, + }, nil) + eTag := etag.GenerateEtag(shipment.UpdatedAt) + officeUser := factory.BuildOfficeUserWithRoles(nil, nil, []roles.RoleType{roles.RoleTypeTOO}) + approver := &mocks.ShipmentApprover{} + + approver.On("ApproveShipments", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("[]services.ShipmentIdWithEtag")).Return(nil, apperror.InvalidInputError{ValidationErrors: &validate.Errors{}}) + + req := httptest.NewRequest("POST", "/shipments/approve", nil) + req = suite.AuthenticateOfficeRequest(req, officeUser) + handlerConfig := suite.HandlerConfig() + + handler := ApproveShipmentsHandler{ + handlerConfig, + approver, + sitstatus.NewShipmentSITStatus(), + moveTaskOrderUpdater, + } + approveParams := shipmentops.ApproveShipmentsParams{ + HTTPRequest: req, + Body: &ghcmessages.ApproveShipments{ + ApproveShipments: []*ghcmessages.ApproveShipmentsApproveShipmentsItems0{ + { + ShipmentID: handlers.FmtUUID(shipment.ID), + ETag: &eTag, + }, + }, + }, + } + + // Validate incoming payload: no body to validate + response := handler.Handle(approveParams) + suite.IsType(&shipmentops.ApproveShipmentsUnprocessableEntity{}, response) + payload := response.(*shipmentops.ApproveShipmentsUnprocessableEntity).Payload + + // Validate outgoing payload + suite.NoError(payload.Validate(strfmt.Default)) + }) + + suite.Run("Returns 422 when ApproveShipments body is empty", func() { + officeUser := factory.BuildOfficeUserWithRoles(nil, nil, []roles.RoleType{roles.RoleTypeTOO}) + approver := &mocks.ShipmentApprover{} + + approver.On("ApproveShipments", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("[]services.ShipmentIdWithEtag")).Return(nil, apperror.InvalidInputError{ValidationErrors: &validate.Errors{}}) + + req := httptest.NewRequest("POST", "/shipments/approve", nil) + req = suite.AuthenticateOfficeRequest(req, officeUser) + handlerConfig := suite.HandlerConfig() + + handler := ApproveShipmentsHandler{ + handlerConfig, + approver, + sitstatus.NewShipmentSITStatus(), + moveTaskOrderUpdater, + } + approveParams := shipmentops.ApproveShipmentsParams{ + HTTPRequest: req, + Body: &ghcmessages.ApproveShipments{ + ApproveShipments: []*ghcmessages.ApproveShipmentsApproveShipmentsItems0{}, + }, + } + + // Validate incoming payload: no body to validate + response := handler.Handle(approveParams) + suite.IsType(&shipmentops.ApproveShipmentsUnprocessableEntity{}, response) + payload := response.(*shipmentops.ApproveShipmentsUnprocessableEntity).Payload + + // Validate outgoing payload + suite.NoError(payload.Validate(strfmt.Default)) + }) + + suite.Run("Returns 500 when approver returns unexpected error", func() { + shipment := factory.BuildMTOShipmentMinimal(nil, []factory.Customization{ + { + Model: models.MTOShipment{ + ID: uuid.Must(uuid.NewV4()), + }, + }, + }, nil) + eTag := etag.GenerateEtag(shipment.UpdatedAt) + officeUser := factory.BuildOfficeUserWithRoles(nil, nil, []roles.RoleType{roles.RoleTypeTOO}) + approver := &mocks.ShipmentApprover{} + + approver.On("ApproveShipments", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("[]services.ShipmentIdWithEtag")).Return(nil, errors.New("UnexpectedError")) + + req := httptest.NewRequest("POST", "/shipments/approve", nil) + req = suite.AuthenticateOfficeRequest(req, officeUser) + handlerConfig := suite.HandlerConfig() + + handler := ApproveShipmentsHandler{ + handlerConfig, + approver, + sitstatus.NewShipmentSITStatus(), + moveTaskOrderUpdater, + } + approveParams := shipmentops.ApproveShipmentsParams{ + HTTPRequest: req, + Body: &ghcmessages.ApproveShipments{ + ApproveShipments: []*ghcmessages.ApproveShipmentsApproveShipmentsItems0{ + { + ShipmentID: handlers.FmtUUID(shipment.ID), + ETag: &eTag, + }, + }, + }, + } + + // Validate incoming payload: no body to validate + response := handler.Handle(approveParams) + suite.IsType(&shipmentops.ApproveShipmentsInternalServerError{}, response) + payload := response.(*shipmentops.ApproveShipmentsInternalServerError).Payload + + // Validate outgoing payload + suite.NoError(payload.Validate(strfmt.Default)) + }) +} + func (suite *HandlerSuite) TestRequestShipmentDiversionHandler() { diversionReason := "Test Reason" @@ -4313,9 +4830,7 @@ func (suite *HandlerSuite) TestUpdateShipmentHandler() { suite.Equal(handlers.FmtPoundPtr(&sitEstimatedWeight), updatedShipment.PpmShipment.SitEstimatedWeight) suite.Equal(handlers.FmtDate(sitEstimatedEntryDate), updatedShipment.PpmShipment.SitEstimatedEntryDate) suite.Equal(handlers.FmtDate(sitEstimatedDepartureDate), updatedShipment.PpmShipment.SitEstimatedDepartureDate) - suite.Equal(int64(sitEstimatedCost), *updatedShipment.PpmShipment.SitEstimatedCost) suite.Equal(handlers.FmtPoundPtr(&estimatedWeight), updatedShipment.PpmShipment.EstimatedWeight) - suite.Equal(int64(estimatedIncentive), *updatedShipment.PpmShipment.EstimatedIncentive) suite.Equal(handlers.FmtBool(hasProGear), updatedShipment.PpmShipment.HasProGear) suite.Equal(handlers.FmtPoundPtr(&proGearWeight), updatedShipment.PpmShipment.ProGearWeight) suite.Equal(handlers.FmtPoundPtr(&spouseProGearWeight), updatedShipment.PpmShipment.SpouseProGearWeight) diff --git a/pkg/handlers/ghcapi/orders.go b/pkg/handlers/ghcapi/orders.go index 51fec571935..8a8ca3cafcf 100644 --- a/pkg/handlers/ghcapi/orders.go +++ b/pkg/handlers/ghcapi/orders.go @@ -290,6 +290,8 @@ func (h CreateOrderHandler) Handle(params orderop.CreateOrderParams) middleware. weightAllotment.UnaccompaniedBaggageAllowance = unaccompaniedBaggageAllowance } + var weightRestriction *int + entitlement := models.Entitlement{ DependentsAuthorized: payload.HasDependents, DBAuthorizedWeight: models.IntPointer(weight), @@ -300,6 +302,7 @@ func (h CreateOrderHandler) Handle(params orderop.CreateOrderParams) middleware. DependentsUnderTwelve: dependentsUnderTwelve, DependentsTwelveAndOver: dependentsTwelveAndOver, UBAllowance: &weightAllotment.UnaccompaniedBaggageAllowance, + WeightRestriction: weightRestriction, } if saveEntitlementErr := appCtx.DB().Save(&entitlement); saveEntitlementErr != nil { @@ -362,15 +365,13 @@ func (h CreateOrderHandler) Handle(params orderop.CreateOrderParams) middleware. Show: models.BoolPointer(true), Status: &status, } - if !appCtx.Session().OfficeUserID.IsNil() { - officeUser, err := models.FetchOfficeUserByID(appCtx.DB(), appCtx.Session().OfficeUserID) + + if payload.CounselingOfficeID != nil { + counselingOffice, err := uuid.FromString(payload.CounselingOfficeID.String()) if err != nil { - err = apperror.NewBadDataError("Unable to fetch office user.") - appCtx.Logger().Error(err.Error()) - return orderop.NewCreateOrderUnprocessableEntity(), err - } else { - moveOptions.CounselingOfficeID = &officeUser.TransportationOfficeID + return handlers.ResponseForError(appCtx.Logger(), err), err } + moveOptions.CounselingOfficeID = &counselingOffice } if newOrder.OrdersType == "SAFETY" { diff --git a/pkg/handlers/ghcapi/orders_test.go b/pkg/handlers/ghcapi/orders_test.go index 68b984f2cef..4496c7c1146 100644 --- a/pkg/handlers/ghcapi/orders_test.go +++ b/pkg/handlers/ghcapi/orders_test.go @@ -76,6 +76,7 @@ func (suite *HandlerSuite) TestCreateOrder() { Sac: handlers.FmtString("SacNumber"), DepartmentIndicator: ghcmessages.NewDeptIndicator(deptIndicator), Grade: ghcmessages.GradeE1.Pointer(), + CounselingOfficeID: handlers.FmtUUID(*dutyLocation.TransportationOfficeID), } params := orderop.CreateOrderParams{ @@ -1531,6 +1532,7 @@ func (suite *HandlerSuite) TestCounselingUpdateAllowanceHandler() { ProGearWeightSpouse: proGearWeightSpouse, RequiredMedicalEquipmentWeight: rmeWeight, StorageInTransit: models.Int64Pointer(80), + WeightRestriction: models.Int64Pointer(0), } request := httptest.NewRequest("PATCH", "/counseling/orders/{orderID}/allowances", nil) diff --git a/pkg/handlers/ghcapi/queues.go b/pkg/handlers/ghcapi/queues.go index f4b124cbb1c..88c040e4c16 100644 --- a/pkg/handlers/ghcapi/queues.go +++ b/pkg/handlers/ghcapi/queues.go @@ -64,6 +64,11 @@ func (h GetMovesQueueHandler) Handle(params queues.GetMovesQueueParams) middlewa CounselingOffice: params.CounselingOffice, } + var activeRole string + if params.ActiveRole != nil { + activeRole = *params.ActiveRole + } + // When no status filter applied, TOO should only see moves with status of New Move, Service Counseling Completed, or Approvals Requested if params.Status == nil { ListOrderParams.Status = []string{string(models.MoveStatusServiceCounselingCompleted), string(models.MoveStatusAPPROVALSREQUESTED), string(models.MoveStatusSUBMITTED)} @@ -165,7 +170,7 @@ func (h GetMovesQueueHandler) Handle(params queues.GetMovesQueueParams) middlewa } } - queueMoves := payloads.QueueMoves(moves, officeUsers, nil, officeUser, officeUsersSafety) + queueMoves := payloads.QueueMoves(moves, officeUsers, nil, officeUser, officeUsersSafety, activeRole) result := &ghcmessages.QueueMovesResult{ Page: *ListOrderParams.Page, @@ -391,6 +396,11 @@ func (h GetPaymentRequestsQueueHandler) Handle( CounselingOffice: params.CounselingOffice, } + var activeRole string + if params.ActiveRole != nil { + activeRole = *params.ActiveRole + } + listPaymentRequestParams.Status = []string{string(models.QueuePaymentRequestPaymentRequested)} // Let's set default values for page and perPage if we don't get arguments for them. We'll use 1 for page and 20 @@ -488,7 +498,7 @@ func (h GetPaymentRequestsQueueHandler) Handle( } } - queuePaymentRequests := payloads.QueuePaymentRequests(paymentRequests, officeUsers, officeUser, officeUsersSafety) + queuePaymentRequests := payloads.QueuePaymentRequests(paymentRequests, officeUsers, officeUser, officeUsersSafety, activeRole) result := &ghcmessages.QueuePaymentRequestsResult{ TotalCount: int64(count), @@ -549,6 +559,11 @@ func (h GetServicesCounselingQueueHandler) Handle( SCAssignedUser: params.AssignedTo, } + var activeRole string + if params.ActiveRole != nil { + activeRole = *params.ActiveRole + } + var requestedPpmStatus models.PPMShipmentStatus if params.NeedsPPMCloseout != nil && *params.NeedsPPMCloseout { requestedPpmStatus = models.PPMShipmentStatusNeedsCloseout @@ -658,7 +673,7 @@ func (h GetServicesCounselingQueueHandler) Handle( } } - queueMoves := payloads.QueueMoves(moves, officeUsers, &requestedPpmStatus, officeUser, officeUsersSafety) + queueMoves := payloads.QueueMoves(moves, officeUsers, &requestedPpmStatus, officeUser, officeUsersSafety, activeRole) result := &ghcmessages.QueueMovesResult{ Page: *ListOrderParams.Page, @@ -773,6 +788,27 @@ func (h GetBulkAssignmentDataHandler) Handle( return queues.NewGetBulkAssignmentDataInternalServerError(), err } + officeUserData = payloads.BulkAssignmentData(appCtx, moves, officeUsers, officeUser.TransportationOffice.ID) + case string(models.QueueTypePaymentRequest): + // fetch the TIOs who work at their office + officeUsers, err := h.OfficeUserFetcherPop.FetchOfficeUsersWithWorkloadByRoleAndOffice( + appCtx, + roles.RoleTypeTIO, + officeUser.TransportationOfficeID, + ) + if err != nil { + appCtx.Logger().Error("Error retreiving office users", zap.Error(err)) + return queues.NewGetBulkAssignmentDataInternalServerError(), err + } + // fetch the moves available to be assigned to their office users + moves, err := h.MoveFetcherBulkAssignment.FetchMovesForBulkAssignmentPaymentRequest( + appCtx, officeUser.TransportationOffice.Gbloc, officeUser.TransportationOffice.ID, + ) + if err != nil { + appCtx.Logger().Error("Error retreiving moves", zap.Error(err)) + return queues.NewGetBulkAssignmentDataInternalServerError(), err + } + officeUserData = payloads.BulkAssignmentData(appCtx, moves, officeUsers, officeUser.TransportationOffice.ID) } return queues.NewGetBulkAssignmentDataOK().WithPayload(&officeUserData), nil diff --git a/pkg/handlers/ghcapi/queues_test.go b/pkg/handlers/ghcapi/queues_test.go index 6de9b13dd91..90088cf2ec0 100644 --- a/pkg/handlers/ghcapi/queues_test.go +++ b/pkg/handlers/ghcapi/queues_test.go @@ -1655,7 +1655,7 @@ func (suite *HandlerSuite) TestGetServicesCounselingQueueHandler() { } func (suite *HandlerSuite) TestGetBulkAssignmentDataHandler() { - suite.Run("returns an unauthorized error when an attempt is made by a non supervisor", func() { + suite.Run("SC - returns an unauthorized error when an attempt is made by a non supervisor", func() { officeUser := factory.BuildOfficeUserWithPrivileges(suite.DB(), []factory.Customization{ { Model: models.OfficeUser{ @@ -1689,7 +1689,7 @@ func (suite *HandlerSuite) TestGetBulkAssignmentDataHandler() { suite.IsNotErrResponse(response) suite.IsType(&queues.GetBulkAssignmentDataUnauthorized{}, response) }) - suite.Run("returns properly formatted bulk assignment data", func() { + suite.Run("SC - returns properly formatted bulk assignment data", func() { transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) officeUser := factory.BuildOfficeUserWithPrivileges(suite.DB(), []factory.Customization{ @@ -1892,6 +1892,362 @@ func (suite *HandlerSuite) TestGetBulkAssignmentDataHandler() { suite.Len(payload.AvailableOfficeUsers, 1) suite.Len(payload.BulkAssignmentMoveIDs, 1) }) + + suite.Run("TIO - returns an unauthorized error when an attempt is made by a non supervisor", func() { + officeUser := factory.BuildOfficeUserWithPrivileges(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + Email: "officeuser1@example.com", + }, + }, + { + Model: models.User{ + Roles: []roles.Role{ + { + RoleType: roles.RoleTypeTIO, + }, + }, + }, + }, + }, nil) + + request := httptest.NewRequest("GET", "/queues/bulk-assignment", nil) + request = suite.AuthenticateOfficeRequest(request, officeUser) + params := queues.GetBulkAssignmentDataParams{ + HTTPRequest: request, + QueueType: models.StringPointer("PAYMENT_REQUEST"), + } + handlerConfig := suite.HandlerConfig() + handler := GetBulkAssignmentDataHandler{ + handlerConfig, + officeusercreator.NewOfficeUserFetcherPop(), + movefetcher.NewMoveFetcherBulkAssignment(), + } + response := handler.Handle(params) + suite.IsNotErrResponse(response) + suite.IsType(&queues.GetBulkAssignmentDataUnauthorized{}, response) + }) + suite.Run("TIO - returns properly formatted bulk assignment data", func() { + transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) + + officeUser := factory.BuildOfficeUserWithPrivileges(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + Email: "officeuser1@example.com", + Active: true, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + { + Model: models.User{ + Privileges: []models.Privilege{ + { + PrivilegeType: models.PrivilegeTypeSupervisor, + }, + }, + Roles: []roles.Role{ + { + RoleType: roles.RoleTypeTIO, + }, + }, + }, + }, + }, nil) + + // payment request to appear in the return + move := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, nil) + factory.BuildPaymentRequest(suite.DB(), []factory.Customization{ + { + Model: models.PaymentRequest{ + ID: uuid.Must(uuid.NewV4()), + IsFinal: false, + Status: models.PaymentRequestStatusPending, + RejectionReason: nil, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + request := httptest.NewRequest("GET", "/queues/bulk-assignment", nil) + request = suite.AuthenticateOfficeRequest(request, officeUser) + params := queues.GetBulkAssignmentDataParams{ + HTTPRequest: request, + QueueType: models.StringPointer("PAYMENT_REQUEST"), + } + handlerConfig := suite.HandlerConfig() + handler := GetBulkAssignmentDataHandler{ + handlerConfig, + officeusercreator.NewOfficeUserFetcherPop(), + movefetcher.NewMoveFetcherBulkAssignment(), + } + response := handler.Handle(params) + suite.IsNotErrResponse(response) + suite.IsType(&queues.GetBulkAssignmentDataOK{}, response) + payload := response.(*queues.GetBulkAssignmentDataOK).Payload + suite.NoError(payload.Validate(strfmt.Default)) + suite.Len(payload.AvailableOfficeUsers, 1) + suite.Len(payload.BulkAssignmentMoveIDs, 1) + }) +} + +type availableOfficeUserSubtestData struct { + officeUsers []models.OfficeUser + office models.TransportationOffice +} + +func (suite *HandlerSuite) TestAvailableOfficeUsers() { + setupOfficeUserData := func(role1 roles.RoleType, role2 roles.RoleType) availableOfficeUserSubtestData { + subtestData := &availableOfficeUserSubtestData{} + transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil) + + // lets generate a few office users + // these first two are what we want returned in the query + // office user 1 is the supervisor making the request + officeUser1 := factory.BuildOfficeUserWithPrivileges(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + LastName: "Aname", + Email: "officeuser1@example.com", + Active: true, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + { + Model: models.User{ + Privileges: []models.Privilege{ + { + PrivilegeType: models.PrivilegeTypeSupervisor, + }, + }, + Roles: []roles.Role{ + { + RoleType: role1, + }, + }, + }, + }, + }, nil) + + // officeUser2 is their underling + officeUser2 := factory.BuildOfficeUserWithPrivileges(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + LastName: "Bname", + Email: "officeuser2@example.com", + Active: true, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + { + Model: models.User{ + Roles: []roles.Role{ + { + RoleType: role1, + }, + }, + }, + }, + }, nil) + + // this office user shares their role but does NOT work at their office so should not be returned + factory.BuildOfficeUserWithPrivileges(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + Email: "officeuser3@example.com", + Active: true, + }, + }, + { + Model: models.User{ + Roles: []roles.Role{ + { + RoleType: role1, + }, + }, + }, + }, + }, nil) + + // this office users works at their office, but doesn't share the same role, and should not be returned + factory.BuildOfficeUserWithPrivileges(suite.DB(), []factory.Customization{ + { + Model: models.OfficeUser{ + Email: "officeuser4@example.com", + Active: true, + }, + }, + { + Model: transportationOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + { + Model: models.User{ + Roles: []roles.Role{ + { + RoleType: role2, + }, + }, + }, + }, + }, nil) + + availableOfficeUsers := []models.OfficeUser{officeUser1, officeUser2} + subtestData.officeUsers = availableOfficeUsers + subtestData.office = transportationOffice + return *subtestData + } + suite.Run("properly fetches a TOO supervisor's available office users for assignment", func() { + subtestData := setupOfficeUserData(roles.RoleTypeTOO, roles.RoleTypeServicesCounselor) + waf := entitlements.NewWeightAllotmentFetcher() + + hhgMove := factory.BuildSubmittedMove(suite.DB(), nil, nil) + + factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: hhgMove, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusSubmitted, + }, + }, + }, nil) + + request := httptest.NewRequest("GET", "/queues/moves", nil) + request = suite.AuthenticateOfficeRequest(request, subtestData.officeUsers[0]) + params := queues.GetMovesQueueParams{ + HTTPRequest: request, + } + handlerConfig := suite.HandlerConfig() + mockUnlocker := movelocker.NewMoveUnlocker() + handler := GetMovesQueueHandler{ + handlerConfig, + order.NewOrderFetcher(waf), + mockUnlocker, + officeusercreator.NewOfficeUserFetcherPop(), + } + + response := handler.Handle(params) + suite.IsNotErrResponse(response) + suite.IsType(&queues.GetMovesQueueOK{}, response) + payload := response.(*queues.GetMovesQueueOK).Payload + + suite.NotNil(payload.QueueMoves) + suite.NotNil(payload.QueueMoves[0].AvailableOfficeUsers) + suite.Equal(2, len(payload.QueueMoves[0].AvailableOfficeUsers)) + suite.Equal(subtestData.officeUsers[0].ID.String(), payload.QueueMoves[0].AvailableOfficeUsers[0].OfficeUserID.String()) + suite.Equal(subtestData.officeUsers[1].ID.String(), payload.QueueMoves[0].AvailableOfficeUsers[1].OfficeUserID.String()) + }) + suite.Run("properly fetches a SC supervisor's available office users for assignment", func() { + subtestData := setupOfficeUserData(roles.RoleTypeServicesCounselor, roles.RoleTypeTOO) + waf := entitlements.NewWeightAllotmentFetcher() + + needsCounselingMove := factory.BuildNeedsServiceCounselingMove(suite.DB(), []factory.Customization{ + { + Model: subtestData.office, + LinkOnly: true, + Type: &factory.TransportationOffices.CounselingOffice, + }, + }, nil) + + factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: needsCounselingMove, + LinkOnly: true, + }, + }, nil) + + request := httptest.NewRequest("GET", "/queues/counseling", nil) + request = suite.AuthenticateOfficeRequest(request, subtestData.officeUsers[0]) + params := queues.GetServicesCounselingQueueParams{ + HTTPRequest: request, + } + handlerConfig := suite.HandlerConfig() + mockUnlocker := movelocker.NewMoveUnlocker() + handler := GetServicesCounselingQueueHandler{ + handlerConfig, + order.NewOrderFetcher(waf), + mockUnlocker, + officeusercreator.NewOfficeUserFetcherPop(), + } + + response := handler.Handle(params) + suite.IsNotErrResponse(response) + suite.IsType(&queues.GetServicesCounselingQueueOK{}, response) + payload := response.(*queues.GetServicesCounselingQueueOK).Payload + + suite.NotNil(payload.QueueMoves) + suite.NotNil(payload.QueueMoves[0].AvailableOfficeUsers) + suite.Equal(2, len(payload.QueueMoves[0].AvailableOfficeUsers)) + suite.Equal(subtestData.officeUsers[0].ID.String(), payload.QueueMoves[0].AvailableOfficeUsers[0].OfficeUserID.String()) + suite.Equal(subtestData.officeUsers[1].ID.String(), payload.QueueMoves[0].AvailableOfficeUsers[1].OfficeUserID.String()) + }) + + suite.Run("properly fetches a TIO supervisor's available office users for assignment", func() { + subtestData := setupOfficeUserData(roles.RoleTypeTIO, roles.RoleTypeTOO) + hhgMove := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + + factory.BuildPaymentRequest(suite.DB(), []factory.Customization{ + { + Model: hhgMove, + LinkOnly: true, + }, + }, nil) + + request := httptest.NewRequest("GET", "/queues/payment-requests", nil) + request = suite.AuthenticateOfficeRequest(request, subtestData.officeUsers[0]) + params := queues.GetPaymentRequestsQueueParams{ + HTTPRequest: request, + } + handlerConfig := suite.HandlerConfig() + mockUnlocker := movelocker.NewMoveUnlocker() + handler := GetPaymentRequestsQueueHandler{ + handlerConfig, + paymentrequest.NewPaymentRequestListFetcher(), + mockUnlocker, + officeusercreator.NewOfficeUserFetcherPop(), + } + + response := handler.Handle(params) + suite.IsNotErrResponse(response) + suite.IsType(&queues.GetPaymentRequestsQueueOK{}, response) + payload := response.(*queues.GetPaymentRequestsQueueOK).Payload + + suite.NotNil(payload.QueuePaymentRequests) + suite.NotNil(payload.QueuePaymentRequests[0].AvailableOfficeUsers) + suite.Equal(2, len(payload.QueuePaymentRequests[0].AvailableOfficeUsers)) + suite.Equal(subtestData.officeUsers[0].ID.String(), payload.QueuePaymentRequests[0].AvailableOfficeUsers[0].OfficeUserID.String()) + suite.Equal(subtestData.officeUsers[1].ID.String(), payload.QueuePaymentRequests[0].AvailableOfficeUsers[1].OfficeUserID.String()) + }) + } func (suite *HandlerSuite) TestGetDestinationRequestsQueuesHandler() { diff --git a/pkg/handlers/ghcapi/tranportation_offices.go b/pkg/handlers/ghcapi/tranportation_offices.go index 405580923bb..b08f45cfb04 100644 --- a/pkg/handlers/ghcapi/tranportation_offices.go +++ b/pkg/handlers/ghcapi/tranportation_offices.go @@ -2,6 +2,7 @@ package ghcapi import ( "github.com/go-openapi/runtime/middleware" + "github.com/gofrs/uuid" "go.uber.org/zap" "github.com/transcom/mymove/pkg/appcontext" @@ -22,7 +23,7 @@ func (h GetTransportationOfficesHandler) Handle(params transportationofficeop.Ge // B-21022: forPpm param is set true. This is used by PPM closeout widget. Need to ensure certain offices are included/excluded // if location has ppm closedout enabled. - transportationOffices, err := h.TransportationOfficesFetcher.GetTransportationOffices(appCtx, params.Search, true) + transportationOffices, err := h.TransportationOfficesFetcher.GetTransportationOffices(appCtx, params.Search, true, false) if err != nil { appCtx.Logger().Error("Error searching for Transportation Offices: ", zap.Error(err)) @@ -43,7 +44,7 @@ func (h GetTransportationOfficesOpenHandler) Handle(params transportationofficeo return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, func(appCtx appcontext.AppContext) (middleware.Responder, error) { - transportationOffices, err := h.TransportationOfficesFetcher.GetTransportationOffices(appCtx, params.Search, false) + transportationOffices, err := h.TransportationOfficesFetcher.GetTransportationOffices(appCtx, params.Search, false, false) if err != nil { appCtx.Logger().Error("Error searching for Transportation Offices: ", zap.Error(err)) return transportationofficeop.NewGetTransportationOfficesOpenInternalServerError(), err @@ -73,3 +74,27 @@ func (h GetTransportationOfficesGBLOCsHandler) Handle(params transportationoffic return transportationofficeop.NewGetTransportationOfficesGBLOCsOK().WithPayload(returnPayload), nil }) } + +// ShowCounselingOfficesHandler returns the counseling offices for a duty location ID +type ShowCounselingOfficesHandler struct { + handlers.HandlerConfig + services.TransportationOfficesFetcher +} + +// Handle retrieves the counseling offices in the system for a given duty location ID +func (h ShowCounselingOfficesHandler) Handle(params transportationofficeop.ShowCounselingOfficesParams) middleware.Responder { + return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, + func(appCtx appcontext.AppContext) (middleware.Responder, error) { + dutyLocationID := uuid.FromStringOrNil(params.DutyLocationID.String()) + serviceMemberID := uuid.FromStringOrNil(params.ServiceMemberID.String()) + + counselingOffices, err := h.TransportationOfficesFetcher.GetCounselingOffices(appCtx, dutyLocationID, serviceMemberID) + if err != nil { + appCtx.Logger().Error("Error searching for Counseling Offices: ", zap.Error(err)) + return transportationofficeop.NewShowCounselingOfficesInternalServerError(), err + } + + returnPayload := payloads.CounselingOffices(*counselingOffices) + return transportationofficeop.NewShowCounselingOfficesOK().WithPayload(returnPayload), nil + }) +} diff --git a/pkg/handlers/ghcapi/transportation_offices_test.go b/pkg/handlers/ghcapi/transportation_offices_test.go index 92ac98f630b..b9305f4d4b7 100644 --- a/pkg/handlers/ghcapi/transportation_offices_test.go +++ b/pkg/handlers/ghcapi/transportation_offices_test.go @@ -1,13 +1,16 @@ package ghcapi import ( + "fmt" "net/http/httptest" "github.com/go-openapi/strfmt" "github.com/transcom/mymove/pkg/factory" transportationofficeop "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/transportation_office" + "github.com/transcom/mymove/pkg/handlers" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services/address" transportationofficeservice "github.com/transcom/mymove/pkg/services/transportation_office" ) @@ -145,3 +148,63 @@ func (suite *HandlerSuite) TestGetTransportationOfficesGBLOCsHandler() { suite.Equal(transportationOffice1.Gbloc, responsePayload.Payload[0]) suite.Equal(transportationOffice2.Gbloc, responsePayload.Payload[1]) } + +func (suite *HandlerSuite) TestShowCounselingOfficesHandler() { + user := factory.BuildDefaultUser(suite.DB()) + serviceMember := factory.BuildServiceMember(suite.DB(), nil, nil) + + fetcher := transportationofficeservice.NewTransportationOfficesFetcher() + + newAddress := models.Address{ + StreetAddress1: "some address", + City: "city", + State: "CA", + PostalCode: "59801", + County: models.StringPointer("County"), + } + addressCreator := address.NewAddressCreator() + createdAddress, err := addressCreator.CreateAddress(suite.AppContextForTest(), &newAddress) + suite.NoError(err) + + origDutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + AddressID: createdAddress.ID, + ProvidesServicesCounseling: true, + }, + }, + { + Model: models.TransportationOffice{ + Name: "New PPPO Travis AFB - USAF", + Gbloc: "KKFA", + ProvidesCloseout: true, + }, + }, + }, nil) + + path := fmt.Sprintf("/transportation_offices/%v/counseling_offices/%v", origDutyLocation.ID.String(), serviceMember.ID.String()) + req := httptest.NewRequest("GET", path, nil) + req = suite.AuthenticateUserRequest(req, user) + params := transportationofficeop.ShowCounselingOfficesParams{ + HTTPRequest: req, + DutyLocationID: *handlers.FmtUUID(origDutyLocation.ID), + } + + handler := ShowCounselingOfficesHandler{ + HandlerConfig: suite.HandlerConfig(), + TransportationOfficesFetcher: fetcher} + + response := handler.Handle(params) + suite.Assertions.IsType(&transportationofficeop.ShowCounselingOfficesOK{}, response) + responsePayload := response.(*transportationofficeop.ShowCounselingOfficesOK) + + // Validate outgoing payload + suite.NoError(responsePayload.Payload.Validate(strfmt.Default)) + var i int + for index, office := range responsePayload.Payload { + if *office.Name == "New PPPO Travis AFB - USAF" { + i = index + } + } + suite.NotNil(i) +} 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 80f07a55433..3fa132f085a 100644 --- a/pkg/handlers/internalapi/internal/payloads/model_to_payload_test.go +++ b/pkg/handlers/internalapi/internal/payloads/model_to_payload_test.go @@ -6,6 +6,7 @@ import ( "github.com/gofrs/uuid" "github.com/transcom/mymove/pkg/etag" + "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/gen/internalmessages" "github.com/transcom/mymove/pkg/handlers" "github.com/transcom/mymove/pkg/models" @@ -183,3 +184,34 @@ func (suite *PayloadsSuite) TestWeightTicket() { suite.Equal(etag.GenerateEtag(weightTicket.UpdatedAt), parsedWeightTicket.ETag) }) } + +func (suite *PayloadsSuite) TestCounselingOffices() { + suite.Run("correctly maps transportaion offices to counseling offices payload", func() { + office1 := factory.BuildTransportationOffice(nil, []factory.Customization{ + { + Model: models.TransportationOffice{ + ID: uuid.Must(uuid.NewV4()), + Name: "PPPO Fort Liberty", + }, + }, + }, nil) + + office2 := factory.BuildTransportationOffice(nil, []factory.Customization{ + { + Model: models.TransportationOffice{ + ID: uuid.Must(uuid.NewV4()), + Name: "PPPO Fort Walker", + }, + }, + }, nil) + + offices := models.TransportationOffices{office1, office2} + + payload := CounselingOffices(offices) + + suite.IsType(payload, internalmessages.CounselingOffices{}) + suite.Equal(2, len(payload)) + suite.Equal(office1.ID.String(), payload[0].ID.String()) + suite.Equal(office2.ID.String(), payload[1].ID.String()) + }) +} diff --git a/pkg/handlers/internalapi/orders.go b/pkg/handlers/internalapi/orders.go index 84c8dfe126f..0097e94b7e3 100644 --- a/pkg/handlers/internalapi/orders.go +++ b/pkg/handlers/internalapi/orders.go @@ -85,6 +85,9 @@ func payloadForOrdersModel(storer storage.FileStorer, order models.Order) (*inte if order.Entitlement.UBAllowance != nil { entitlement.UbAllowance = models.Int64Pointer(int64(*order.Entitlement.UBAllowance)) } + if order.Entitlement.WeightRestriction != nil { + entitlement.WeightRestriction = models.Int64Pointer(int64(*order.Entitlement.WeightRestriction)) + } } var originDutyLocation models.DutyLocation originDutyLocation = models.DutyLocation{} diff --git a/pkg/handlers/internalapi/transportation_offices.go b/pkg/handlers/internalapi/transportation_offices.go index 6b535fd9ba9..7f8529fe479 100644 --- a/pkg/handlers/internalapi/transportation_offices.go +++ b/pkg/handlers/internalapi/transportation_offices.go @@ -51,7 +51,7 @@ func (h GetTransportationOfficesHandler) Handle(params transportationofficeop.Ge return transportationofficeop.NewGetTransportationOfficesForbidden(), noServiceMemberIDErr } - transportationOffices, err := h.TransportationOfficesFetcher.GetTransportationOffices(appCtx, params.Search, true) + transportationOffices, err := h.TransportationOfficesFetcher.GetTransportationOffices(appCtx, params.Search, true, false) if err != nil { appCtx.Logger().Error("Error searching for Transportation Offices: ", zap.Error(err)) return transportationofficeop.NewGetTransportationOfficesInternalServerError(), err @@ -74,7 +74,7 @@ func (h ShowCounselingOfficesHandler) Handle(params transportationofficeop.ShowC func(appCtx appcontext.AppContext) (middleware.Responder, error) { dutyLocationID := uuid.FromStringOrNil(params.DutyLocationID.String()) - counselingOffices, err := h.TransportationOfficesFetcher.GetCounselingOffices(appCtx, dutyLocationID) + counselingOffices, err := h.TransportationOfficesFetcher.GetCounselingOffices(appCtx, dutyLocationID, appCtx.Session().ServiceMemberID) if err != nil { appCtx.Logger().Error("Error searching for Counseling Offices: ", zap.Error(err)) return transportationofficeop.NewShowCounselingOfficesInternalServerError(), err diff --git a/pkg/handlers/internalapi/transportation_offices_test.go b/pkg/handlers/internalapi/transportation_offices_test.go index b41c5aff7bf..64cd3db6cb2 100644 --- a/pkg/handlers/internalapi/transportation_offices_test.go +++ b/pkg/handlers/internalapi/transportation_offices_test.go @@ -153,7 +153,6 @@ func (suite *HandlerSuite) TestShowCounselingOfficesHandler() { }, }, }, nil) - suite.MustSave(&origDutyLocation) path := fmt.Sprintf("/transportation_offices/%v/counseling_offices", origDutyLocation.ID.String()) req := httptest.NewRequest("GET", path, nil) @@ -174,4 +173,13 @@ func (suite *HandlerSuite) TestShowCounselingOfficesHandler() { // Validate outgoing payload suite.NoError(responsePayload.Payload.Validate(strfmt.Default)) + // Validate outgoing payload + suite.NoError(responsePayload.Payload.Validate(strfmt.Default)) + var i int + for index, office := range responsePayload.Payload { + if *office.Name == "New PPPO Travis AFB - USAF" { + i = index + } + } + suite.NotNil(i) } diff --git a/pkg/handlers/primeapi/move_task_order_test.go b/pkg/handlers/primeapi/move_task_order_test.go index cfbc270140d..f22e72e5e41 100644 --- a/pkg/handlers/primeapi/move_task_order_test.go +++ b/pkg/handlers/primeapi/move_task_order_test.go @@ -1329,7 +1329,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.NotNil(payload.ETag()) }) - suite.Run("Success - return all MTOServiceItemShuttle fields assoicated with the getMoveTaskOrder", func() { + suite.Run("Success - return all MTOServiceItemDomesticShuttle fields assoicated with the getMoveTaskOrder", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), movetaskorder.NewMoveTaskOrderFetcher(waf), @@ -1396,14 +1396,14 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { json, err := json.Marshal(serviceItemPayload) suite.NoError(err) - payload := primemessages.MTOServiceItemShuttle{} + payload := primemessages.MTOServiceItemDomesticShuttle{} err = payload.UnmarshalJSON(json) suite.NoError(err) suite.Equal(serviceItem.MoveTaskOrderID.String(), payload.MoveTaskOrderID().String()) suite.Equal(serviceItem.MTOShipmentID.String(), payload.MtoShipmentID().String()) suite.Equal(serviceItem.ID.String(), payload.ID().String()) - suite.Equal("MTOServiceItemShuttle", string(payload.ModelType())) + suite.Equal("MTOServiceItemDomesticShuttle", string(payload.ModelType())) suite.Equal(string(serviceItem.ReService.Code), string(*payload.ReServiceCode)) suite.Equal(serviceItem.ReService.Name, payload.ReServiceName()) suite.Equal(string(serviceItem.Status), string(payload.Status())) diff --git a/pkg/handlers/primeapi/mto_service_item.go b/pkg/handlers/primeapi/mto_service_item.go index 646c6d17bfb..c78f37b6950 100644 --- a/pkg/handlers/primeapi/mto_service_item.go +++ b/pkg/handlers/primeapi/mto_service_item.go @@ -28,6 +28,7 @@ var CreateableServiceItemMap = map[primemessages.MTOServiceItemModelType]bool{ primemessages.MTOServiceItemModelTypeMTOServiceItemOriginSIT: true, primemessages.MTOServiceItemModelTypeMTOServiceItemDestSIT: true, primemessages.MTOServiceItemModelTypeMTOServiceItemShuttle: true, + primemessages.MTOServiceItemModelTypeMTOServiceItemDomesticShuttle: true, primemessages.MTOServiceItemModelTypeMTOServiceItemInternationalShuttle: true, primemessages.MTOServiceItemModelTypeMTOServiceItemDomesticCrating: true, primemessages.MTOServiceItemModelTypeMTOServiceItemInternationalCrating: true, diff --git a/pkg/handlers/primeapi/payloads/model_to_payload.go b/pkg/handlers/primeapi/payloads/model_to_payload.go index af39f03827f..5a675099271 100644 --- a/pkg/handlers/primeapi/payloads/model_to_payload.go +++ b/pkg/handlers/primeapi/payloads/model_to_payload.go @@ -262,6 +262,10 @@ func Entitlement(entitlement *models.Entitlement) *primemessages.Entitlements { if entitlement.UBAllowance != nil { ubAllowance = int64(*entitlement.UBAllowance) } + var weightRestriction int64 + if entitlement.WeightRestriction != nil { + weightRestriction = int64(*entitlement.WeightRestriction) + } return &primemessages.Entitlements{ ID: strfmt.UUID(entitlement.ID.String()), AuthorizedWeight: authorizedWeight, @@ -274,10 +278,11 @@ func Entitlement(entitlement *models.Entitlement) *primemessages.Entitlements { ProGearWeightSpouse: int64(entitlement.ProGearWeightSpouse), RequiredMedicalEquipmentWeight: int64(entitlement.RequiredMedicalEquipmentWeight), OrganizationalClothingAndIndividualEquipment: entitlement.OrganizationalClothingAndIndividualEquipment, - StorageInTransit: sit, - TotalDependents: totalDependents, - TotalWeight: totalWeight, - ETag: etag.GenerateEtag(entitlement.UpdatedAt), + StorageInTransit: sit, + TotalDependents: totalDependents, + TotalWeight: totalWeight, + WeightRestriction: &weightRestriction, + ETag: etag.GenerateEtag(entitlement.UpdatedAt), } } @@ -809,7 +814,7 @@ func MTOServiceItem(mtoServiceItem *models.MTOServiceItem) primemessages.MTOServ payload = &cratingSI case models.ReServiceCodeDDSHUT, models.ReServiceCodeDOSHUT: - payload = &primemessages.MTOServiceItemShuttle{ + payload = &primemessages.MTOServiceItemDomesticShuttle{ ReServiceCode: handlers.FmtString(string(mtoServiceItem.ReService.Code)), Reason: mtoServiceItem.Reason, EstimatedWeight: handlers.FmtPoundPtr(mtoServiceItem.EstimatedWeight), diff --git a/pkg/handlers/primeapi/payloads/model_to_payload_test.go b/pkg/handlers/primeapi/payloads/model_to_payload_test.go index 44cf2c2e40d..e54c61bd5fb 100644 --- a/pkg/handlers/primeapi/payloads/model_to_payload_test.go +++ b/pkg/handlers/primeapi/payloads/model_to_payload_test.go @@ -351,6 +351,7 @@ func (suite *PayloadsSuite) TestEntitlement() { ProGearWeightSpouse: 750, CreatedAt: time.Now(), UpdatedAt: time.Now(), + WeightRestriction: models.IntPointer(1000), } // TotalWeight needs to read from the internal weightAllotment, in this case 7000 lbs w/o dependents and @@ -373,6 +374,7 @@ func (suite *PayloadsSuite) TestEntitlement() { suite.Equal(true, payload.OrganizationalClothingAndIndividualEquipment) suite.Equal(int64(1000), payload.ProGearWeight) suite.Equal(int64(750), payload.ProGearWeightSpouse) + suite.Equal(int64(1000), *payload.WeightRestriction) suite.NotEmpty(payload.ETag) suite.Equal(etag.GenerateEtag(entitlement.UpdatedAt), payload.ETag) }) @@ -958,7 +960,7 @@ func (suite *PayloadsSuite) TestMTOServiceItemDDSHUT() { suite.NotNil(resultDDSHUT) - _, ok := resultDDSHUT.(*primemessages.MTOServiceItemShuttle) + _, ok := resultDDSHUT.(*primemessages.MTOServiceItemDomesticShuttle) suite.True(ok) } diff --git a/pkg/handlers/primeapi/payloads/payload_to_model.go b/pkg/handlers/primeapi/payloads/payload_to_model.go index 53362a88b84..77d0db1e1f6 100644 --- a/pkg/handlers/primeapi/payloads/payload_to_model.go +++ b/pkg/handlers/primeapi/payloads/payload_to_model.go @@ -537,6 +537,14 @@ func MTOServiceItemModel(mtoServiceItem primemessages.MTOServiceItem) (*models.M model.EstimatedWeight = handlers.PoundPtrFromInt64Ptr(shuttleService.EstimatedWeight) model.ActualWeight = handlers.PoundPtrFromInt64Ptr(shuttleService.ActualWeight) + case primemessages.MTOServiceItemModelTypeMTOServiceItemDomesticShuttle: + shuttleService := mtoServiceItem.(*primemessages.MTOServiceItemDomesticShuttle) + // values to get from payload + model.ReService.Code = models.ReServiceCode(*shuttleService.ReServiceCode) + model.Reason = shuttleService.Reason + model.EstimatedWeight = handlers.PoundPtrFromInt64Ptr(shuttleService.EstimatedWeight) + model.ActualWeight = handlers.PoundPtrFromInt64Ptr(shuttleService.ActualWeight) + case primemessages.MTOServiceItemModelTypeMTOServiceItemInternationalShuttle: shuttleService := mtoServiceItem.(*primemessages.MTOServiceItemInternationalShuttle) // values to get from payload diff --git a/pkg/handlers/primeapi/payloads/payload_to_model_test.go b/pkg/handlers/primeapi/payloads/payload_to_model_test.go index 9b5ec6f69a5..d45071aa7fa 100644 --- a/pkg/handlers/primeapi/payloads/payload_to_model_test.go +++ b/pkg/handlers/primeapi/payloads/payload_to_model_test.go @@ -65,7 +65,7 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { DCRTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) DCRTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) - DDSHUTServiceItem := &primemessages.MTOServiceItemShuttle{ + DDSHUTServiceItem := &primemessages.MTOServiceItemDomesticShuttle{ ReServiceCode: &ddshutCode, Reason: &reason, EstimatedWeight: &estimatedWeight, @@ -74,7 +74,7 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { DDSHUTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) DDSHUTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) - DOSHUTServiceItem := &primemessages.MTOServiceItemShuttle{ + DOSHUTServiceItem := &primemessages.MTOServiceItemDomesticShuttle{ ReServiceCode: &doshutCode, Reason: &reason, EstimatedWeight: &estimatedWeight, diff --git a/pkg/handlers/primeapiv2/move_task_order_test.go b/pkg/handlers/primeapiv2/move_task_order_test.go index 0b4fc4c56d8..f173af34bda 100644 --- a/pkg/handlers/primeapiv2/move_task_order_test.go +++ b/pkg/handlers/primeapiv2/move_task_order_test.go @@ -1190,7 +1190,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.NotNil(payload.ETag()) }) - suite.Run("Success - return all MTOServiceItemShuttle fields assoicated with the getMoveTaskOrder", func() { + suite.Run("Success - return all MTOServiceItemDomesticShuttle fields assoicated with the getMoveTaskOrder", func() { handler := GetMoveTaskOrderHandler{ suite.HandlerConfig(), movetaskorder.NewMoveTaskOrderFetcher(waf), @@ -1257,14 +1257,14 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { json, err := json.Marshal(serviceItemPayload) suite.NoError(err) - payload := primev2messages.MTOServiceItemShuttle{} + payload := primev2messages.MTOServiceItemDomesticShuttle{} err = payload.UnmarshalJSON(json) suite.NoError(err) suite.Equal(serviceItem.MoveTaskOrderID.String(), payload.MoveTaskOrderID().String()) suite.Equal(serviceItem.MTOShipmentID.String(), payload.MtoShipmentID().String()) suite.Equal(serviceItem.ID.String(), payload.ID().String()) - suite.Equal("MTOServiceItemShuttle", string(payload.ModelType())) + suite.Equal("MTOServiceItemDomesticShuttle", string(payload.ModelType())) suite.Equal(string(serviceItem.ReService.Code), string(*payload.ReServiceCode)) suite.Equal(serviceItem.ReService.Name, payload.ReServiceName()) suite.Equal(string(serviceItem.Status), string(payload.Status())) diff --git a/pkg/handlers/primeapiv2/mto_service_item.go b/pkg/handlers/primeapiv2/mto_service_item.go index 5188ccea511..495a597b88f 100644 --- a/pkg/handlers/primeapiv2/mto_service_item.go +++ b/pkg/handlers/primeapiv2/mto_service_item.go @@ -26,6 +26,7 @@ var CreateableServiceItemMap = map[primev2messages.MTOServiceItemModelType]bool{ primev2messages.MTOServiceItemModelTypeMTOServiceItemOriginSIT: true, primev2messages.MTOServiceItemModelTypeMTOServiceItemDestSIT: true, primev2messages.MTOServiceItemModelTypeMTOServiceItemShuttle: true, + primev2messages.MTOServiceItemModelTypeMTOServiceItemDomesticShuttle: true, primev2messages.MTOServiceItemModelTypeMTOServiceItemInternationalShuttle: true, primev2messages.MTOServiceItemModelTypeMTOServiceItemDomesticCrating: true, primev2messages.MTOServiceItemModelTypeMTOServiceItemInternationalCrating: true, diff --git a/pkg/handlers/primeapiv2/payloads/model_to_payload.go b/pkg/handlers/primeapiv2/payloads/model_to_payload.go index 3a49ba8b5fb..09f107a9e04 100644 --- a/pkg/handlers/primeapiv2/payloads/model_to_payload.go +++ b/pkg/handlers/primeapiv2/payloads/model_to_payload.go @@ -191,6 +191,10 @@ func Entitlement(entitlement *models.Entitlement) *primev2messages.Entitlements if entitlement.UBAllowance != nil { ubAllowance = int64(*entitlement.UBAllowance) } + var weightRestriction int64 + if entitlement.WeightRestriction != nil { + weightRestriction = int64(*entitlement.WeightRestriction) + } return &primev2messages.Entitlements{ ID: strfmt.UUID(entitlement.ID.String()), AuthorizedWeight: authorizedWeight, @@ -203,10 +207,11 @@ func Entitlement(entitlement *models.Entitlement) *primev2messages.Entitlements ProGearWeightSpouse: int64(entitlement.ProGearWeightSpouse), RequiredMedicalEquipmentWeight: int64(entitlement.RequiredMedicalEquipmentWeight), OrganizationalClothingAndIndividualEquipment: entitlement.OrganizationalClothingAndIndividualEquipment, - StorageInTransit: sit, - TotalDependents: totalDependents, - TotalWeight: totalWeight, - ETag: etag.GenerateEtag(entitlement.UpdatedAt), + StorageInTransit: sit, + TotalDependents: totalDependents, + TotalWeight: totalWeight, + WeightRestriction: &weightRestriction, + ETag: etag.GenerateEtag(entitlement.UpdatedAt), } } @@ -720,7 +725,7 @@ func MTOServiceItem(mtoServiceItem *models.MTOServiceItem) primev2messages.MTOSe payload = &cratingSI case models.ReServiceCodeDDSHUT, models.ReServiceCodeDOSHUT: - payload = &primev2messages.MTOServiceItemShuttle{ + payload = &primev2messages.MTOServiceItemDomesticShuttle{ ReServiceCode: handlers.FmtString(string(mtoServiceItem.ReService.Code)), Reason: mtoServiceItem.Reason, EstimatedWeight: handlers.FmtPoundPtr(mtoServiceItem.EstimatedWeight), diff --git a/pkg/handlers/primeapiv2/payloads/model_to_payload_test.go b/pkg/handlers/primeapiv2/payloads/model_to_payload_test.go index cb69d490a44..2119ecd1a8e 100644 --- a/pkg/handlers/primeapiv2/payloads/model_to_payload_test.go +++ b/pkg/handlers/primeapiv2/payloads/model_to_payload_test.go @@ -303,6 +303,7 @@ func (suite *PayloadsSuite) TestEntitlement() { ProGearWeightSpouse: 0, CreatedAt: time.Now(), UpdatedAt: time.Now(), + WeightRestriction: models.IntPointer(1000), } payload := Entitlement(&entitlement) @@ -325,6 +326,7 @@ func (suite *PayloadsSuite) TestEntitlement() { suite.Equal(int64(0), payload.TotalDependents) suite.Equal(int64(0), payload.TotalWeight) suite.Equal(int64(0), *payload.UnaccompaniedBaggageAllowance) + suite.Equal(int64(1000), *payload.WeightRestriction) }) suite.Run("Success - Returns the entitlement payload with all optional fields populated", func() { @@ -868,7 +870,7 @@ func (suite *PayloadsSuite) TestMTOServiceItemDDSHUT() { suite.NotNil(resultDDSHUT) - _, ok := resultDDSHUT.(*primev2messages.MTOServiceItemShuttle) + _, ok := resultDDSHUT.(*primev2messages.MTOServiceItemDomesticShuttle) suite.True(ok) } diff --git a/pkg/handlers/primeapiv2/payloads/payload_to_model.go b/pkg/handlers/primeapiv2/payloads/payload_to_model.go index b628bcc0502..b57e5ca541b 100644 --- a/pkg/handlers/primeapiv2/payloads/payload_to_model.go +++ b/pkg/handlers/primeapiv2/payloads/payload_to_model.go @@ -621,7 +621,6 @@ func MTOServiceItemModel(mtoServiceItem primev2messages.MTOServiceItem) (*models if model.SITDestinationFinalAddress != nil { model.SITDestinationFinalAddressID = &model.SITDestinationFinalAddress.ID } - case primev2messages.MTOServiceItemModelTypeMTOServiceItemShuttle: shuttleService := mtoServiceItem.(*primev2messages.MTOServiceItemShuttle) // values to get from payload @@ -630,6 +629,14 @@ func MTOServiceItemModel(mtoServiceItem primev2messages.MTOServiceItem) (*models model.EstimatedWeight = handlers.PoundPtrFromInt64Ptr(shuttleService.EstimatedWeight) model.ActualWeight = handlers.PoundPtrFromInt64Ptr(shuttleService.ActualWeight) + case primev2messages.MTOServiceItemModelTypeMTOServiceItemDomesticShuttle: + shuttleService := mtoServiceItem.(*primev2messages.MTOServiceItemDomesticShuttle) + // values to get from payload + model.ReService.Code = models.ReServiceCode(*shuttleService.ReServiceCode) + model.Reason = shuttleService.Reason + model.EstimatedWeight = handlers.PoundPtrFromInt64Ptr(shuttleService.EstimatedWeight) + model.ActualWeight = handlers.PoundPtrFromInt64Ptr(shuttleService.ActualWeight) + case primev2messages.MTOServiceItemModelTypeMTOServiceItemInternationalShuttle: shuttleService := mtoServiceItem.(*primev2messages.MTOServiceItemInternationalShuttle) // values to get from payload diff --git a/pkg/handlers/primeapiv2/payloads/payload_to_model_test.go b/pkg/handlers/primeapiv2/payloads/payload_to_model_test.go index 3df180b58ea..5a1e7844ab6 100644 --- a/pkg/handlers/primeapiv2/payloads/payload_to_model_test.go +++ b/pkg/handlers/primeapiv2/payloads/payload_to_model_test.go @@ -52,7 +52,7 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { Length: &crateMeasurement, } - DDSHUTServiceItem := &primev2messages.MTOServiceItemShuttle{ + DDSHUTServiceItem := &primev2messages.MTOServiceItemDomesticShuttle{ ReServiceCode: &ddshutCode, Reason: &reason, EstimatedWeight: &estimatedWeight, @@ -61,7 +61,7 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { DDSHUTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) DDSHUTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) - DOSHUTServiceItem := &primev2messages.MTOServiceItemShuttle{ + DOSHUTServiceItem := &primev2messages.MTOServiceItemDomesticShuttle{ ReServiceCode: &doshutCode, Reason: &reason, EstimatedWeight: &estimatedWeight, diff --git a/pkg/handlers/primeapiv3/move_task_order.go b/pkg/handlers/primeapiv3/move_task_order.go index c725fe7210b..1c4978d43a0 100644 --- a/pkg/handlers/primeapiv3/move_task_order.go +++ b/pkg/handlers/primeapiv3/move_task_order.go @@ -106,14 +106,14 @@ func (h GetMoveTaskOrderHandler) Handle(params movetaskorderops.GetMoveTaskOrder /** End of Feature Flag **/ // Add oconus rate area information to payload - shipmentPostalCodeRateArea, err := h.shipmentRateAreaFinder.GetPrimeMoveShipmentOconusRateArea(appCtx, *mto) + shipmentPostalCodeRateArea, err := h.shipmentRateAreaFinder.GetPrimeMoveShipmentRateAreas(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) + moveTaskOrderPayload := payloads.MoveTaskOrderWithShipmentRateAreas(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 852fb0358c8..06880f84230 100644 --- a/pkg/handlers/primeapiv3/move_task_order_test.go +++ b/pkg/handlers/primeapiv3/move_task_order_test.go @@ -46,7 +46,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { setupDefaultTestHandler := func() GetMoveTaskOrderHandler { mockShipmentRateAreaFinder := &mocks.ShipmentRateAreaFinder{} - mockShipmentRateAreaFinder.On("GetPrimeMoveShipmentOconusRateArea", + mockShipmentRateAreaFinder.On("GetPrimeMoveShipmentRateAreas", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("models.Move"), ).Return(nil, nil) @@ -1169,7 +1169,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.NotNil(payload.ETag()) }) - suite.Run("Success - return all MTOServiceItemShuttle fields assoicated with the getMoveTaskOrder", func() { + suite.Run("Success - return all MTOServiceItemDomesticShuttle fields assoicated with the getMoveTaskOrder", func() { handler := setupDefaultTestHandler() successMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) @@ -1233,14 +1233,14 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { json, err := json.Marshal(serviceItemPayload) suite.NoError(err) - payload := primev3messages.MTOServiceItemShuttle{} + payload := primev3messages.MTOServiceItemDomesticShuttle{} err = payload.UnmarshalJSON(json) suite.NoError(err) suite.Equal(serviceItem.MoveTaskOrderID.String(), payload.MoveTaskOrderID().String()) suite.Equal(serviceItem.MTOShipmentID.String(), payload.MtoShipmentID().String()) suite.Equal(serviceItem.ID.String(), payload.ID().String()) - suite.Equal("MTOServiceItemShuttle", string(payload.ModelType())) + suite.Equal("MTOServiceItemDomesticShuttle", string(payload.ModelType())) suite.Equal(string(serviceItem.ReService.Code), string(*payload.ReServiceCode)) suite.Equal(serviceItem.ReService.Name, payload.ReServiceName()) suite.Equal(string(serviceItem.Status), string(payload.Status())) @@ -1459,7 +1459,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { }, } - mockShipmentRateAreaFinder.On("GetPrimeMoveShipmentOconusRateArea", + mockShipmentRateAreaFinder.On("GetPrimeMoveShipmentRateAreas", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("models.Move"), ).Return(&shipmentPostalCodeRateArea, nil) @@ -1538,7 +1538,7 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { defaultAddress := factory.BuildAddress(suite.DB(), nil, nil) - mockShipmentRateAreaFinder.On("GetPrimeMoveShipmentOconusRateArea", + mockShipmentRateAreaFinder.On("GetPrimeMoveShipmentRateAreas", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("models.Move"), ).Return(nil, apperror.InternalServerError{}) diff --git a/pkg/handlers/primeapiv3/mto_service_item.go b/pkg/handlers/primeapiv3/mto_service_item.go index f3c16b46e60..d3ab85fac3b 100644 --- a/pkg/handlers/primeapiv3/mto_service_item.go +++ b/pkg/handlers/primeapiv3/mto_service_item.go @@ -26,6 +26,7 @@ var CreateableServiceItemMap = map[primev3messages.MTOServiceItemModelType]bool{ primev3messages.MTOServiceItemModelTypeMTOServiceItemOriginSIT: true, primev3messages.MTOServiceItemModelTypeMTOServiceItemDestSIT: true, primev3messages.MTOServiceItemModelTypeMTOServiceItemShuttle: true, + primev3messages.MTOServiceItemModelTypeMTOServiceItemDomesticShuttle: true, primev3messages.MTOServiceItemModelTypeMTOServiceItemInternationalShuttle: true, primev3messages.MTOServiceItemModelTypeMTOServiceItemDomesticCrating: true, primev3messages.MTOServiceItemModelTypeMTOServiceItemInternationalCrating: true, diff --git a/pkg/handlers/primeapiv3/payloads/model_to_payload.go b/pkg/handlers/primeapiv3/payloads/model_to_payload.go index d61f70f997c..aba7a9c1718 100644 --- a/pkg/handlers/primeapiv3/payloads/model_to_payload.go +++ b/pkg/handlers/primeapiv3/payloads/model_to_payload.go @@ -80,7 +80,7 @@ func MoveTaskOrder(appCtx appcontext.AppContext, moveTaskOrder *models.Move) *pr return payload } -func MoveTaskOrderWithShipmentOconusRateArea(appCtx appcontext.AppContext, moveTaskOrder *models.Move, shipmentRateArea *[]services.ShipmentPostalCodeRateArea) *primev3messages.MoveTaskOrder { +func MoveTaskOrderWithShipmentRateAreas(appCtx appcontext.AppContext, moveTaskOrder *models.Move, shipmentRateArea *[]services.ShipmentPostalCodeRateArea) *primev3messages.MoveTaskOrder { // create default payload var payload = MoveTaskOrder(appCtx, moveTaskOrder) @@ -219,6 +219,10 @@ func Entitlement(entitlement *models.Entitlement) *primev3messages.Entitlements if entitlement.UBAllowance != nil { ubAllowance = int64(*entitlement.UBAllowance) } + var weightRestriction int64 + if entitlement.WeightRestriction != nil { + weightRestriction = int64(*entitlement.WeightRestriction) + } return &primev3messages.Entitlements{ ID: strfmt.UUID(entitlement.ID.String()), AuthorizedWeight: authorizedWeight, @@ -230,10 +234,11 @@ func Entitlement(entitlement *models.Entitlement) *primev3messages.Entitlements ProGearWeightSpouse: int64(entitlement.ProGearWeightSpouse), RequiredMedicalEquipmentWeight: int64(entitlement.RequiredMedicalEquipmentWeight), OrganizationalClothingAndIndividualEquipment: entitlement.OrganizationalClothingAndIndividualEquipment, - StorageInTransit: sit, - TotalDependents: totalDependents, - TotalWeight: totalWeight, - ETag: etag.GenerateEtag(entitlement.UpdatedAt), + StorageInTransit: sit, + TotalDependents: totalDependents, + TotalWeight: totalWeight, + WeightRestriction: &weightRestriction, + ETag: etag.GenerateEtag(entitlement.UpdatedAt), } } @@ -354,14 +359,12 @@ func MTOAgents(mtoAgents *models.MTOAgents) *primev3messages.MTOAgents { if mtoAgents == nil { return nil } - agents := make(primev3messages.MTOAgents, len(*mtoAgents)) for i, m := range *mtoAgents { copyOfM := m // Make copy to avoid implicit memory aliasing of items from a range statement. agents[i] = MTOAgent(©OfM) } - return &agents } @@ -865,7 +868,7 @@ func MTOServiceItem(mtoServiceItem *models.MTOServiceItem) primev3messages.MTOSe payload = &cratingSI case models.ReServiceCodeDDSHUT, models.ReServiceCodeDOSHUT: - payload = &primev3messages.MTOServiceItemShuttle{ + payload = &primev3messages.MTOServiceItemDomesticShuttle{ ReServiceCode: handlers.FmtString(string(mtoServiceItem.ReService.Code)), Reason: mtoServiceItem.Reason, EstimatedWeight: handlers.FmtPoundPtr(mtoServiceItem.EstimatedWeight), diff --git a/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go b/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go index 3ff9bf25b94..f0f7036eac6 100644 --- a/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go +++ b/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go @@ -274,7 +274,7 @@ func (suite *PayloadsSuite) TestMoveTaskOrder() { }) // no ShipmentPostalCodeRateArea passed in - returnedModel := MoveTaskOrderWithShipmentOconusRateArea(suite.AppContextForTest(), newMove, nil) + returnedModel := MoveTaskOrderWithShipmentRateAreas(suite.AppContextForTest(), newMove, nil) suite.IsType(&primev3messages.MoveTaskOrder{}, returnedModel) suite.Equal(strfmt.UUID(newMove.ID.String()), returnedModel.ID) @@ -337,7 +337,7 @@ func (suite *PayloadsSuite) TestMoveTaskOrder() { }, } - returnedModel = MoveTaskOrderWithShipmentOconusRateArea(suite.AppContextForTest(), newMove, &shipmentPostalCodeRateArea) + returnedModel = MoveTaskOrderWithShipmentRateAreas(suite.AppContextForTest(), newMove, &shipmentPostalCodeRateArea) var shipmentPostalCodeRateAreaLookupMap = make(map[string]services.ShipmentPostalCodeRateArea) for _, i := range shipmentPostalCodeRateArea { @@ -545,6 +545,7 @@ func (suite *PayloadsSuite) TestEntitlement() { ProGearWeightSpouse: 0, CreatedAt: time.Now(), UpdatedAt: time.Now(), + WeightRestriction: models.IntPointer(1000), } payload := Entitlement(&entitlement) @@ -567,6 +568,7 @@ func (suite *PayloadsSuite) TestEntitlement() { suite.Equal(int64(0), payload.TotalDependents) suite.Equal(int64(0), payload.TotalWeight) suite.Equal(int64(0), *payload.UnaccompaniedBaggageAllowance) + suite.Equal(int64(1000), *payload.WeightRestriction) }) suite.Run("Success - Returns the entitlement payload with all optional fields populated", func() { @@ -1145,7 +1147,7 @@ func (suite *PayloadsSuite) TestMTOServiceItemDDSHUT() { suite.NotNil(resultDDSHUT) - _, ok := resultDDSHUT.(*primev3messages.MTOServiceItemShuttle) + _, ok := resultDDSHUT.(*primev3messages.MTOServiceItemDomesticShuttle) suite.True(ok) } diff --git a/pkg/handlers/primeapiv3/payloads/payload_to_model.go b/pkg/handlers/primeapiv3/payloads/payload_to_model.go index f33d8b2ff34..2acc20eb04a 100644 --- a/pkg/handlers/primeapiv3/payloads/payload_to_model.go +++ b/pkg/handlers/primeapiv3/payloads/payload_to_model.go @@ -786,7 +786,6 @@ func MTOServiceItemModel(mtoServiceItem primev3messages.MTOServiceItem) (*models if model.SITDestinationFinalAddress != nil { model.SITDestinationFinalAddressID = &model.SITDestinationFinalAddress.ID } - case primev3messages.MTOServiceItemModelTypeMTOServiceItemShuttle: shuttleService := mtoServiceItem.(*primev3messages.MTOServiceItemShuttle) // values to get from payload @@ -795,6 +794,14 @@ func MTOServiceItemModel(mtoServiceItem primev3messages.MTOServiceItem) (*models model.EstimatedWeight = handlers.PoundPtrFromInt64Ptr(shuttleService.EstimatedWeight) model.ActualWeight = handlers.PoundPtrFromInt64Ptr(shuttleService.ActualWeight) + case primev3messages.MTOServiceItemModelTypeMTOServiceItemDomesticShuttle: + shuttleService := mtoServiceItem.(*primev3messages.MTOServiceItemDomesticShuttle) + // values to get from payload + model.ReService.Code = models.ReServiceCode(*shuttleService.ReServiceCode) + model.Reason = shuttleService.Reason + model.EstimatedWeight = handlers.PoundPtrFromInt64Ptr(shuttleService.EstimatedWeight) + model.ActualWeight = handlers.PoundPtrFromInt64Ptr(shuttleService.ActualWeight) + case primev3messages.MTOServiceItemModelTypeMTOServiceItemInternationalShuttle: shuttleService := mtoServiceItem.(*primev3messages.MTOServiceItemInternationalShuttle) // values to get from payload diff --git a/pkg/handlers/primeapiv3/payloads/payload_to_model_test.go b/pkg/handlers/primeapiv3/payloads/payload_to_model_test.go index fd9430379f0..4f12b050b83 100644 --- a/pkg/handlers/primeapiv3/payloads/payload_to_model_test.go +++ b/pkg/handlers/primeapiv3/payloads/payload_to_model_test.go @@ -64,7 +64,7 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { DCRTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) DCRTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) - DDSHUTServiceItem := &primev3messages.MTOServiceItemShuttle{ + DDSHUTServiceItem := &primev3messages.MTOServiceItemDomesticShuttle{ ReServiceCode: &ddshutCode, Reason: &reason, EstimatedWeight: &estimatedWeight, @@ -73,7 +73,7 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { DDSHUTServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) DDSHUTServiceItem.SetMtoShipmentID(*mtoShipmentIDString) - DOSHUTServiceItem := &primev3messages.MTOServiceItemShuttle{ + DOSHUTServiceItem := &primev3messages.MTOServiceItemDomesticShuttle{ ReServiceCode: &doshutCode, Reason: &reason, EstimatedWeight: &estimatedWeight, diff --git a/pkg/handlers/supportapi/internal/payloads/model_to_payload.go b/pkg/handlers/supportapi/internal/payloads/model_to_payload.go index 12ebfd4b8a1..506c2251256 100644 --- a/pkg/handlers/supportapi/internal/payloads/model_to_payload.go +++ b/pkg/handlers/supportapi/internal/payloads/model_to_payload.go @@ -148,6 +148,7 @@ func Entitlement(entitlement *models.Entitlement) *supportmessages.Entitlement { if entitlement.UBAllowance != nil { ubAllowance = int64(*entitlement.UBAllowance) } + return &supportmessages.Entitlement{ ID: strfmt.UUID(entitlement.ID.String()), AuthorizedWeight: authorizedWeight, @@ -350,7 +351,7 @@ func MTOServiceItem(mtoServiceItem *models.MTOServiceItem) supportmessages.MTOSe StandaloneCrate: mtoServiceItem.StandaloneCrate, } case models.ReServiceCodeDDSHUT, models.ReServiceCodeDOSHUT: - payload = &supportmessages.MTOServiceItemShuttle{ + payload = &supportmessages.MTOServiceItemDomesticShuttle{ ReServiceCode: handlers.FmtString(string(mtoServiceItem.ReService.Code)), Reason: mtoServiceItem.Reason, EstimatedWeight: handlers.FmtPoundPtr(mtoServiceItem.EstimatedWeight), diff --git a/pkg/handlers/supportapi/move_task_order.go b/pkg/handlers/supportapi/move_task_order.go index 98862bfbcc3..41bc92fbf9c 100644 --- a/pkg/handlers/supportapi/move_task_order.go +++ b/pkg/handlers/supportapi/move_task_order.go @@ -63,9 +63,7 @@ func (h MakeMoveTaskOrderAvailableHandlerFunc) Handle(params movetaskorderops.Ma moveTaskOrderID := uuid.FromStringOrNil(params.MoveTaskOrderID) - mto, err := h.moveTaskOrderAvailabilityUpdater.MakeAvailableToPrime(appCtx, moveTaskOrderID, eTag, false, false) - - if err != nil { + handleError := func(err error) (middleware.Responder, error) { appCtx.Logger().Error("supportapi.MakeMoveTaskOrderAvailableHandlerFunc error", zap.Error(err)) switch typedErr := err.(type) { case apperror.NotFoundError: @@ -83,6 +81,16 @@ func (h MakeMoveTaskOrderAvailableHandlerFunc) Handle(params movetaskorderops.Ma } } + _, err := h.moveTaskOrderAvailabilityUpdater.ApproveMoveAndCreateServiceItems(appCtx, moveTaskOrderID, eTag, false, false) + if err != nil { + return handleError(err) + } + + mto, _, err := h.moveTaskOrderAvailabilityUpdater.MakeAvailableToPrime(appCtx, moveTaskOrderID) + if err != nil { + return handleError(err) + } + moveTaskOrderPayload := payloads.MoveTaskOrder(mto) return movetaskorderops.NewMakeMoveTaskOrderAvailableOK().WithPayload(moveTaskOrderPayload), nil diff --git a/pkg/models/errors.go b/pkg/models/errors.go index 2a1cdeeff0e..78011442f48 100644 --- a/pkg/models/errors.go +++ b/pkg/models/errors.go @@ -63,3 +63,6 @@ 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") + +// ErrInvalidFilterFormat is used if the param filter is not in the expected format +var ErrInvalidFilterFormat = errors.New("invalid filter format") diff --git a/pkg/models/move.go b/pkg/models/move.go index c84068b47ef..b740c85c5d0 100644 --- a/pkg/models/move.go +++ b/pkg/models/move.go @@ -217,18 +217,28 @@ func (m Move) GetDestinationGBLOC(db *pop.Connection) (string, error) { var newGBLOC string if *destinationAddress.IsOconus { - err := db.Load(&m.Orders, "ServiceMember") - if err != nil { - if err.Error() == RecordNotFoundErrorString { - return "", errors.WithMessage(err, "No Service Member found in the DB associated with moveID "+m.ID.String()) + if m.OrdersID != uuid.Nil { + err := db.Q().EagerPreload("ServiceMember"). + Find(&m.Orders, m.OrdersID) + if err != nil { + if errors.Cause(err).Error() == RecordNotFoundErrorString { + return "", ErrFetchNotFound + } + return "", err } - return "", err + } else { + return "", errors.WithMessage(ErrInvalidOrderID, "Orders ID must have a value in order to get the destination GBLOC") } - newGBLOCOconus, err := FetchAddressGbloc(db, *destinationAddress, m.Orders.ServiceMember) - if err != nil { - return "", err + + if m.Orders.ServiceMember.Affiliation != nil { + newGBLOCOconus, err := FetchAddressGbloc(db, *destinationAddress, m.Orders.ServiceMember) + if err != nil { + return "", err + } + newGBLOC = *newGBLOCOconus + } else { + return "", errors.Errorf("ServiceMember.Affiliation cannot be NULL for GetDestinationGBLOC") } - newGBLOC = *newGBLOCOconus } else { newGBLOCConus, err := FetchGBLOCForPostalCode(db, destinationAddress.PostalCode) if err != nil { diff --git a/pkg/models/payment_request.go b/pkg/models/payment_request.go index 120f7a1d4fb..8a57e05e0ae 100644 --- a/pkg/models/payment_request.go +++ b/pkg/models/payment_request.go @@ -89,6 +89,11 @@ type PaymentRequest struct { TPPSPaidInvoiceReports TPPSPaidInvoiceReportEntrys `has_many:"tpps_paid_invoice_reports" fk_id:"payment_request_number"` } +type PaymentRequestWithEarliestRequestedDate struct { + ID uuid.UUID `json:"id" db:"id"` + EarliestRequestedDate time.Time `db:"requested_at"` +} + // TableName overrides the table name used by Pop. func (p PaymentRequest) TableName() string { return "payment_requests" diff --git a/pkg/models/port_location.go b/pkg/models/port_location.go index 4d514a2a545..414006d9c82 100644 --- a/pkg/models/port_location.go +++ b/pkg/models/port_location.go @@ -3,7 +3,10 @@ package models import ( "time" + "github.com/gobuffalo/pop/v6" "github.com/gofrs/uuid" + + "github.com/transcom/mymove/pkg/apperror" ) type PortLocation struct { @@ -24,3 +27,12 @@ type PortLocation struct { func (l PortLocation) TableName() string { return "port_locations" } + +func FetchPortLocationByCode(db *pop.Connection, portCode string) (*PortLocation, error) { + portLocation := PortLocation{} + err := db.Eager("Port", "UsPostRegionCity").Where("is_active = TRUE").InnerJoin("ports p", "port_id = p.id").Where("p.port_code = $1", portCode).First(&portLocation) + if err != nil { + return nil, apperror.NewQueryError("PortLocation", err, "") + } + return &portLocation, err +} diff --git a/pkg/models/port_location_test.go b/pkg/models/port_location_test.go index c63a4e34e29..ae50c68880f 100644 --- a/pkg/models/port_location_test.go +++ b/pkg/models/port_location_test.go @@ -2,6 +2,7 @@ package models_test import ( "github.com/transcom/mymove/pkg/factory" + "github.com/transcom/mymove/pkg/models" ) func (suite *ModelSuite) TestPortLocation() { @@ -24,3 +25,28 @@ func (suite *ModelSuite) TestPortLocation() { suite.Equal("port_locations", portLocation.TableName()) }) } + +func (suite *ModelSuite) TestFetchPortLocationByCode() { + suite.Run("Port location can be fetched when it exists", func() { + + portLocation := factory.FetchPortLocation(suite.DB(), []factory.Customization{ + { + Model: models.Port{ + PortCode: "SEA", + }, + }, + }, nil) + suite.NotNil(portLocation) + + result, err := models.FetchPortLocationByCode(suite.AppContextForTest().DB(), "SEA") + suite.NotNil(result) + suite.NoError(err) + suite.Equal(portLocation.ID, result.ID) + }) + + suite.Run("Sends back an error when it does not exist", func() { + result, err := models.FetchPortLocationByCode(suite.AppContextForTest().DB(), "123") + suite.Nil(result) + suite.Error(err) + }) +} diff --git a/pkg/models/ppm_shipment.go b/pkg/models/ppm_shipment.go index 0737417207c..d53bdb5b3ac 100644 --- a/pkg/models/ppm_shipment.go +++ b/pkg/models/ppm_shipment.go @@ -1,6 +1,7 @@ package models import ( + "fmt" "time" "github.com/gobuffalo/pop/v6" @@ -34,11 +35,14 @@ type PPMCloseout struct { RemainingIncentive *unit.Cents HaulPrice *unit.Cents HaulFSC *unit.Cents - HaulType HaulType + HaulType *HaulType DOP *unit.Cents DDP *unit.Cents PackPrice *unit.Cents UnpackPrice *unit.Cents + IntlPackPrice *unit.Cents + IntlUnpackPrice *unit.Cents + IntlLinehaulPrice *unit.Cents SITReimbursement *unit.Cents } @@ -319,3 +323,44 @@ func FetchPPMShipmentByPPMShipmentID(db *pop.Connection, ppmShipmentID uuid.UUID } return &ppmShipment, nil } + +type PPMIncentiveOCONUS struct { + TotalIncentive int `db:"total_incentive"` + PriceISLH int `db:"price_islh"` + PriceIHPK int `db:"price_ihpk"` + PriceIHUPK int `db:"price_ihupk"` + PriceFSC int `db:"price_fsc"` +} + +// a db function that will handle updating the estimated_incentive value +// this simulates pricing of a basic iHHG shipment with ISLH, IHPK, IHUPK, and the CONUS portion for a FSC +func CalculatePPMIncentive(db *pop.Connection, ppmID uuid.UUID, pickupAddressID uuid.UUID, destAddressID uuid.UUID, moveDate time.Time, mileage int, weight int, isEstimated bool, isActual bool, isMax bool) (*PPMIncentiveOCONUS, error) { + var incentive PPMIncentiveOCONUS + + err := db.RawQuery("SELECT * FROM calculate_ppm_incentive($1, $2, $3, $4, $5, $6, $7, $8, $9)", ppmID, pickupAddressID, destAddressID, moveDate, mileage, weight, isEstimated, isActual, isMax). + First(&incentive) + if err != nil { + return nil, fmt.Errorf("error calculating PPM incentive for PPM ID %s: %w", ppmID, err) + } + + return &incentive, nil +} + +type PPMSITCosts struct { + TotalSITCost int `db:"total_cost"` + PriceFirstDaySIT int `db:"price_first_day"` + PriceAddlDaySIT int `db:"price_addl_day"` +} + +// a db function that will handle calculating and returning the SIT costs related to a PPM shipment +func CalculatePPMSITCost(db *pop.Connection, ppmID uuid.UUID, addressID uuid.UUID, isOrigin bool, moveDate time.Time, weight int, sitDays int) (*PPMSITCosts, error) { + var costs PPMSITCosts + + err := db.RawQuery("SELECT * FROM calculate_ppm_SIT_cost($1, $2, $3, $4, $5, $6)", ppmID, addressID, isOrigin, moveDate, weight, sitDays). + First(&costs) + if err != nil { + return nil, fmt.Errorf("error calculating PPM SIT costs for PPM ID %s: %w", ppmID, err) + } + + return &costs, nil +} diff --git a/pkg/models/ppm_shipment_test.go b/pkg/models/ppm_shipment_test.go index 4def2567968..7dbeb27f545 100644 --- a/pkg/models/ppm_shipment_test.go +++ b/pkg/models/ppm_shipment_test.go @@ -7,6 +7,7 @@ import ( "github.com/gofrs/uuid" + "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/testdatagen" "github.com/transcom/mymove/pkg/unit" @@ -125,3 +126,161 @@ func (suite *ModelSuite) TestPPMShipmentValidation() { }) } } + +func (suite *ModelSuite) TestCalculatePPMIncentive() { + suite.Run("success - receive PPM incentive when all values exist", func() { + ppmShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) + pickupUSPRC, err := models.FindByZipCode(suite.AppContextForTest().DB(), "74135") + suite.FatalNoError(err) + pickupAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "Tester Address", + City: "Tulsa", + State: "OK", + PostalCode: "74133", + IsOconus: models.BoolPointer(false), + UsPostRegionCityID: &pickupUSPRC.ID, + }, + }, + }, nil) + + destUSPRC, err := models.FindByZipCode(suite.AppContextForTest().DB(), "99505") + suite.FatalNoError(err) + destAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "JBER", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + UsPostRegionCityID: &destUSPRC.ID, + }, + }, + }, nil) + + testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: time.Now().Add(-24 * time.Hour), + EndDate: time.Now().Add(24 * time.Hour), + }, + }) + moveDate := time.Now() + mileage := 1000 + weight := 2000 + + incentives, err := models.CalculatePPMIncentive(suite.DB(), ppmShipment.ID, pickupAddress.ID, destAddress.ID, moveDate, mileage, weight, true, false, false) + suite.NoError(err) + suite.NotNil(incentives) + suite.NotNil(incentives.PriceFSC) + suite.NotNil(incentives.PriceIHPK) + suite.NotNil(incentives.PriceIHUPK) + suite.NotNil(incentives.PriceISLH) + suite.NotNil(incentives.TotalIncentive) + }) + + suite.Run("failure - contract doesn't exist", func() { + ppmShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) + pickupUSPRC, err := models.FindByZipCode(suite.AppContextForTest().DB(), "74135") + suite.FatalNoError(err) + pickupAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "Tester Address", + City: "Tulsa", + State: "OK", + PostalCode: "74133", + IsOconus: models.BoolPointer(false), + UsPostRegionCityID: &pickupUSPRC.ID, + }, + }, + }, nil) + + destUSPRC, err := models.FindByZipCode(suite.AppContextForTest().DB(), "99505") + suite.FatalNoError(err) + destAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "JBER", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + UsPostRegionCityID: &destUSPRC.ID, + }, + }, + }, nil) + + moveDate := time.Now() + mileage := 1000 + weight := 2000 + + incentives, err := models.CalculatePPMIncentive(suite.DB(), ppmShipment.ID, pickupAddress.ID, destAddress.ID, moveDate, mileage, weight, true, false, false) + suite.Error(err) + suite.Nil(incentives) + }) +} + +func (suite *ModelSuite) TestCalculatePPMSITCost() { + suite.Run("success - receive PPM SIT costs when all values exist", func() { + ppmShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) + destUSPRC, err := models.FindByZipCode(suite.AppContextForTest().DB(), "99505") + suite.FatalNoError(err) + address := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "JBER", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + UsPostRegionCityID: &destUSPRC.ID, + }, + }, + }, nil) + + testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: time.Now().Add(-24 * time.Hour), + EndDate: time.Now().Add(24 * time.Hour), + }, + }) + moveDate := time.Now() + sitDays := 7 + weight := 2000 + + sitCost, err := models.CalculatePPMSITCost(suite.DB(), ppmShipment.ID, address.ID, false, moveDate, weight, sitDays) + suite.NoError(err) + suite.NotNil(sitCost) + suite.NotNil(sitCost.PriceAddlDaySIT) + suite.NotNil(sitCost.PriceFirstDaySIT) + suite.NotNil(sitCost.TotalSITCost) + }) + + suite.Run("failure - contract doesn't exist", func() { + ppmShipment := factory.BuildPPMShipment(suite.DB(), nil, nil) + destUSPRC, err := models.FindByZipCode(suite.AppContextForTest().DB(), "99505") + suite.FatalNoError(err) + address := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "JBER", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + UsPostRegionCityID: &destUSPRC.ID, + }, + }, + }, nil) + + moveDate := time.Now() + sitDays := 7 + weight := 2000 + + sitCost, err := models.CalculatePPMSITCost(suite.DB(), ppmShipment.ID, address.ID, false, moveDate, weight, sitDays) + suite.Error(err) + suite.Nil(sitCost) + }) +} diff --git a/pkg/models/re_intl_other_price.go b/pkg/models/re_intl_other_price.go index b8dce673214..1d95b5fcdd8 100644 --- a/pkg/models/re_intl_other_price.go +++ b/pkg/models/re_intl_other_price.go @@ -1,6 +1,7 @@ package models import ( + "fmt" "time" "github.com/gobuffalo/pop/v6" @@ -45,3 +46,36 @@ func (r *ReIntlOtherPrice) Validate(_ *pop.Connection) (*validate.Errors, error) &validators.IntIsGreaterThan{Field: r.PerUnitCents.Int(), Name: "PerUnitCents", Compared: -1}, ), nil } + +// fetches a row from re_intl_other_prices using passed in parameters +// gets the rate_area_id & is_peak_period based on values provided +func FetchReIntlOtherPrice(db *pop.Connection, addressID uuid.UUID, serviceID uuid.UUID, contractID uuid.UUID, referenceDate *time.Time) (*ReIntlOtherPrice, error) { + if addressID != uuid.Nil && serviceID != uuid.Nil && contractID != uuid.Nil && referenceDate != nil { + // need to get the rate area first + rateAreaID, err := FetchRateAreaID(db, addressID, &serviceID, contractID) + if err != nil { + return nil, fmt.Errorf("error fetching rate area id for shipment ID: %s, service ID %s, and contract ID: %s: %s", addressID, serviceID, contractID, err) + } + + var isPeakPeriod bool + err = db.RawQuery("SELECT is_peak_period($1)", referenceDate).First(&isPeakPeriod) + if err != nil { + return nil, fmt.Errorf("error checking if date is peak period with date: %s: %s", contractID, err) + } + + var reIntlOtherPrice ReIntlOtherPrice + err = db.Q(). + Where("contract_id = ?", contractID). + Where("service_id = ?", serviceID). + Where("is_peak_period = ?", isPeakPeriod). + Where("rate_area_id = ?", rateAreaID). + First(&reIntlOtherPrice) + if err != nil { + return nil, fmt.Errorf("error fetching row from re_int_other_prices using rateAreaID %s, service ID %s, and contract ID: %s: %s", rateAreaID, serviceID, contractID, err) + } + + return &reIntlOtherPrice, nil + } + + return nil, fmt.Errorf("error value from re_intl_other_prices - required parameters not provided") +} diff --git a/pkg/models/re_intl_other_price_test.go b/pkg/models/re_intl_other_price_test.go index 69e9770c47a..8cde04c61d4 100644 --- a/pkg/models/re_intl_other_price_test.go +++ b/pkg/models/re_intl_other_price_test.go @@ -1,9 +1,13 @@ package models_test import ( + "time" + "github.com/gofrs/uuid" + "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/testdatagen" ) func (suite *ModelSuite) TestReIntlOtherPriceValidation() { @@ -43,3 +47,76 @@ func (suite *ModelSuite) TestReIntlOtherPriceValidation() { suite.verifyValidationErrors(&intlOtherPrice, expErrors) }) } + +func (suite *ModelSuite) TestFetchReIntlOtherPrice() { + suite.Run("success - receive ReIntlOtherPrice when all values exist and are found", func() { + address := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "JBER", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + }, + }, + }, nil) + + reService, err := models.FetchReServiceByCode(suite.DB(), models.ReServiceCodeIHPK) + suite.NoError(err) + suite.NotNil(reService) + + contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + moveDate := time.Now() + + reIntlOtherPrice, err := models.FetchReIntlOtherPrice(suite.DB(), address.ID, reService.ID, contract.ID, &moveDate) + suite.NoError(err) + suite.NotNil(reIntlOtherPrice) + suite.NotNil(reIntlOtherPrice.PerUnitCents) + }) + + suite.Run("failure - receive error when values aren't provided", func() { + address := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "JBER", + City: "JBER", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + }, + }, + }, nil) + + reService, err := models.FetchReServiceByCode(suite.DB(), models.ReServiceCodeIHPK) + suite.NoError(err) + suite.NotNil(reService) + + contract := testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{}) + moveDate := time.Now() + + // no address + reIntlOtherPrice, err := models.FetchReIntlOtherPrice(suite.DB(), uuid.Nil, reService.ID, contract.ID, &moveDate) + suite.Error(err) + suite.Nil(reIntlOtherPrice) + suite.Contains(err.Error(), "error value from re_intl_other_prices - required parameters not provided") + + // no service ID + reIntlOtherPrice, err = models.FetchReIntlOtherPrice(suite.DB(), address.ID, uuid.Nil, contract.ID, &moveDate) + suite.Error(err) + suite.Nil(reIntlOtherPrice) + suite.Contains(err.Error(), "error value from re_intl_other_prices - required parameters not provided") + + // no contract ID + reIntlOtherPrice, err = models.FetchReIntlOtherPrice(suite.DB(), address.ID, reService.ID, uuid.Nil, &moveDate) + suite.Error(err) + suite.Nil(reIntlOtherPrice) + suite.Contains(err.Error(), "error value from re_intl_other_prices - required parameters not provided") + + // no move date + reIntlOtherPrice, err = models.FetchReIntlOtherPrice(suite.DB(), address.ID, reService.ID, contract.ID, nil) + suite.Error(err) + suite.Nil(reIntlOtherPrice) + suite.Contains(err.Error(), "error value from re_intl_other_prices - required parameters not provided") + }) +} diff --git a/pkg/models/re_service.go b/pkg/models/re_service.go index bab371ac5d4..85136a4705a 100644 --- a/pkg/models/re_service.go +++ b/pkg/models/re_service.go @@ -1,12 +1,15 @@ package models import ( + "fmt" "time" "github.com/gobuffalo/pop/v6" "github.com/gobuffalo/validate/v3" "github.com/gobuffalo/validate/v3/validators" "github.com/gofrs/uuid" + + "github.com/transcom/mymove/pkg/apperror" ) // ReServiceCode is the code of service @@ -224,3 +227,16 @@ func (r *ReService) Validate(_ *pop.Connection) (*validate.Errors, error) { &validators.StringIsPresent{Field: r.Name, Name: "Name"}, ), nil } + +func FetchReServiceByCode(db *pop.Connection, code ReServiceCode) (*ReService, error) { + var reServiceCode ReServiceCode + if code != reServiceCode { + reService := ReService{} + err := db.Where("code = ?", code).First(&reService) + if err != nil { + return nil, apperror.NewQueryError("ReService", err, "") + } + return &reService, err + } + return nil, fmt.Errorf("error fetching from re_services - required code not provided") +} diff --git a/pkg/models/re_service_test.go b/pkg/models/re_service_test.go index 929677faab7..41e60c55d73 100644 --- a/pkg/models/re_service_test.go +++ b/pkg/models/re_service_test.go @@ -23,3 +23,19 @@ func (suite *ModelSuite) TestReServiceValidation() { suite.verifyValidationErrors(&emptyReService, expErrors) }) } + +func (suite *ModelSuite) TestFetchReServiceBycode() { + suite.Run("success - receive ReService when code is provided", func() { + reService, err := models.FetchReServiceByCode(suite.DB(), models.ReServiceCodeIHPK) + suite.NoError(err) + suite.NotNil(reService) + }) + + suite.Run("failure - receive error when code is not provided", func() { + var blankReServiceCode models.ReServiceCode + reService, err := models.FetchReServiceByCode(suite.DB(), blankReServiceCode) + suite.Error(err) + suite.Nil(reService) + suite.Contains(err.Error(), "error fetching from re_services - required code not provided") + }) +} diff --git a/pkg/models/roles/roles.go b/pkg/models/roles/roles.go index 10fedc99b6e..dd326a3f895 100644 --- a/pkg/models/roles/roles.go +++ b/pkg/models/roles/roles.go @@ -94,3 +94,25 @@ func FetchRolesForUser(db *pop.Connection, userID uuid.UUID) (Roles, error) { All(&roles) return roles, err } + +// Fetch like roles based on the search parameter +func FindRoles(db *pop.Connection, search string) (Roles, error) { + var rolesList Roles + + // The % operator filters out strings that are below this similarity threshold + err := db.Q().RawQuery("SET pg_trgm.similarity_threshold = 0.03").Exec() + if err != nil { + return rolesList, err + } + + sqlQuery := `select * from roles where role_name % $1` + + query := db.Q().RawQuery(sqlQuery, search) + if err := query.All(&rolesList); err != nil { + if err != nil { + return rolesList, err + } + } + + return rolesList, nil +} diff --git a/pkg/models/roles/roles_test.go b/pkg/models/roles/roles_test.go index 9ec5a0e7d86..6bcf0c07704 100644 --- a/pkg/models/roles/roles_test.go +++ b/pkg/models/roles/roles_test.go @@ -3,10 +3,12 @@ package roles_test import ( "testing" + "github.com/gofrs/uuid" "github.com/stretchr/testify/suite" "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/models/roles" m "github.com/transcom/mymove/pkg/models/roles" "github.com/transcom/mymove/pkg/testingsuite" ) @@ -66,3 +68,36 @@ func (suite *RolesSuite) TestFetchRolesForUser() { suite.NoError(err) suite.Equal(1, len(userRoles), userRoles) } + +func (suite *RolesSuite) TestFindRoles() { + id1, _ := uuid.NewV4() + role1 := roles.Role{ + ID: id1, + RoleName: "Task Invoicing Officer", + RoleType: "role1", + } + + id2, _ := uuid.NewV4() + role2 := roles.Role{ + ID: id2, + RoleName: "Task Ordering Officer", + RoleType: "role2", + } + + id3, _ := uuid.NewV4() + role3 := roles.Role{ + ID: id3, + RoleName: "Contracting Officer", + RoleType: "role3", + } + + // Create roles + rs := roles.Roles{role1, role2, role3} + err := suite.DB().Create(rs) + suite.NoError(err) + + userRoles, err := m.FindRoles(suite.DB(), "Ta") + + suite.NoError(err) + suite.GreaterOrEqual(len(userRoles), 2) +} diff --git a/pkg/notifications/move_counseled.go b/pkg/notifications/move_counseled.go index e519fdcc6a7..76b4728bb14 100644 --- a/pkg/notifications/move_counseled.go +++ b/pkg/notifications/move_counseled.go @@ -6,6 +6,7 @@ import ( html "html/template" text "text/template" + "github.com/dustin/go-humanize" "github.com/gofrs/uuid" "go.uber.org/zap" @@ -91,12 +92,21 @@ func (m MoveCounseled) emails(appCtx appcontext.AppContext) ([]emailContent, err return emails, fmt.Errorf("no email found for service member") } + var weightRestriction *int64 + var weightRestrictionFormatted string + if orders.Entitlement != nil && orders.Entitlement.WeightRestriction != nil { + weightRestrictionInt64 := int64(*orders.Entitlement.WeightRestriction) + weightRestriction = &weightRestrictionInt64 + weightRestrictionFormatted = humanize.Comma(int64(*weightRestriction)) + } + htmlBody, textBody, err := m.renderTemplates(appCtx, MoveCounseledEmailData{ OriginDutyLocation: originDutyLocationName, DestinationLocation: destinationAddress, Locator: move.Locator, MyMoveLink: MyMoveLink, ActualExpenseReimbursement: actualExpenseReimbursement, + WeightRestriction: weightRestrictionFormatted, }) if err != nil { @@ -115,8 +125,8 @@ func (m MoveCounseled) emails(appCtx appcontext.AppContext) ([]emailContent, err // TODO: Send email to trusted contacts when that's supported return append(emails, smEmail), nil -} +} func (m MoveCounseled) renderTemplates(appCtx appcontext.AppContext, data MoveCounseledEmailData) (string, string, error) { htmlBody, err := m.RenderHTML(appCtx, data) if err != nil { @@ -135,6 +145,7 @@ type MoveCounseledEmailData struct { Locator string MyMoveLink string ActualExpenseReimbursement bool + WeightRestriction string } // RenderHTML renders the html for the email diff --git a/pkg/notifications/move_counseled_test.go b/pkg/notifications/move_counseled_test.go index a66370c27af..b2a5c736d48 100644 --- a/pkg/notifications/move_counseled_test.go +++ b/pkg/notifications/move_counseled_test.go @@ -273,3 +273,56 @@ func (suite *NotificationSuite) TestMoveCounseledDestinationIsDutyStationForPpmP suite.Contains(email.htmlBody, "from "+move.Orders.OriginDutyLocation.Name+" to "+move.Orders.NewDutyLocation.Name+" in the ") suite.Contains(email.textBody, "from "+move.Orders.OriginDutyLocation.Name+" to "+move.Orders.NewDutyLocation.Name+" in the ") } + +func (suite *NotificationSuite) TestMoveCounseledHTMLTemplateRenderWithWeightRestriction() { + approver := factory.BuildUser(nil, nil, nil) + move := factory.BuildMove(suite.DB(), nil, nil) + notification := NewMoveCounseled(move.ID) + + originDutyLocation := "origDutyLocation" + + s := MoveCounseledEmailData{ + OriginDutyLocation: &originDutyLocation, + DestinationLocation: "destDutyLocation", + Locator: "abc123", + MyMoveLink: MyMoveLink, + ActualExpenseReimbursement: true, + WeightRestriction: "1500", + } + + 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.
Your move has been identified as going to an administratively restricted HHG weight location. Your weight restriction is 1500lbs.
+Be advised, you may be required to pay excess cost if you choose to move more than your weight restriction.
+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: +
Note: To receive allowance for Pro-Gear, you must identify allowable items and provide weight tickets separately for Pro-Gear.
+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{ + UserID: approver.ID, + ApplicationName: auth.OfficeApp, + }), s) + + suite.NoError(err) + suite.Equal(trimExtraSpaces(expectedHTMLContent), trimExtraSpaces(htmlContent)) +} diff --git a/pkg/notifications/move_submitted_test.go b/pkg/notifications/move_submitted_test.go index b636d26a536..2d1693e79c1 100644 --- a/pkg/notifications/move_submitted_test.go +++ b/pkg/notifications/move_submitted_test.go @@ -273,10 +273,11 @@ func (suite *NotificationSuite) TestMoveSubmittedHTMLTemplateRenderWithGovCounse- Your weight allowance: 7,999 pounds. + Your standard weight allowance: 7,999 pounds. That is how much combined weight the government will pay for all movements between authorized locations under your - orders. -
+ orders. +Be advised, if you are moving to an administratively restricted HHG weight location this amount could be less.
+If you move more than 7,999 pounds or ship to/from an other than authorized location, you may owe the @@ -385,10 +386,11 @@ func (suite *NotificationSuite) TestMoveSubmittedHTMLTemplateRenderWithoutGovCou
- Your weight allowance: 7,999 pounds. + Your standard weight allowance: 7,999 pounds. That is how much combined weight the government will pay for all movements between authorized locations under your - orders. -
+ orders. +Be advised, if you are moving to an administratively restricted HHG weight location this amount could be less.
+If you move more than 7,999 pounds or ship to/from an other than authorized location, you may owe the @@ -483,10 +485,11 @@ func (suite *NotificationSuite) TestMoveSubmittedHTMLTemplateRenderNoDutyLocatio
- Your weight allowance: 7,999 pounds. + Your standard weight allowance: 7,999 pounds. That is how much combined weight the government will pay for all movements between authorized locations under your - orders. -
+ orders. +Be advised, if you are moving to an administratively restricted HHG weight location this amount could be less.
+
If you move more than 7,999 pounds or ship to/from an other than authorized location, you may owe the
@@ -578,7 +581,7 @@ We have assigned you a move code: abc123. You can use this code when talking to
To change any information about your move, or to add or cancel shipments, you should contact 555-555-5555 or visit your local transportation office (` + OneSourceTransportationOfficeLink + `) .
-Your weight allowance: 7,999 pounds. That is how much combined weight the government will pay for all movements between authorized locations under your orders.
+Your standard weight allowance: 7,999 pounds. That is how much combined weight the government will pay for all movements between authorized locations under your orders. Be advised, if you are moving to an administratively restricted HHG weight location this amount could be less.
If you move more than 7,999 pounds or ship to/from an other than authorized location, you may owe the government the difference in cost between what you are authorized and what you decide to move.
diff --git a/pkg/payment_request/service_param_value_lookups/distance_zip_lookup.go b/pkg/payment_request/service_param_value_lookups/distance_zip_lookup.go
index 0317726d032..acc87eb2715 100644
--- a/pkg/payment_request/service_param_value_lookups/distance_zip_lookup.go
+++ b/pkg/payment_request/service_param_value_lookups/distance_zip_lookup.go
@@ -48,6 +48,8 @@ func (r DistanceZipLookup) lookup(appCtx appcontext.AppContext, keyData *Service
"Distance",
"PickupAddress",
"DestinationAddress",
+ "PPMShipment.PickupAddress",
+ "PPMShipment.DestinationAddress",
).Find(&mtoShipment, mtoShipment.ID)
if err != nil {
return "", err
@@ -61,20 +63,47 @@ func (r DistanceZipLookup) lookup(appCtx appcontext.AppContext, keyData *Service
// if the shipment is international, we need to change the respective ZIP to use the port ZIP and not the address ZIP
if isInternationalShipment {
- portZip, portType, err := models.GetPortLocationInfoForShipment(appCtx.DB(), *mtoShipmentID)
- if err != nil {
- return "", err
- }
- if portZip != nil && portType != nil {
- // if the port type is POEFSC this means the shipment is CONUS -> OCONUS (pickup -> port)
- // if the port type is PODFSC this means the shipment is OCONUS -> CONUS (port -> destination)
- if *portType == models.ReServiceCodePOEFSC.String() {
- destinationZip = *portZip
- } else if *portType == models.ReServiceCodePODFSC.String() {
- pickupZip = *portZip
+ if mtoShipment.ShipmentType != models.MTOShipmentTypePPM {
+ portZip, portType, err := models.GetPortLocationInfoForShipment(appCtx.DB(), *mtoShipmentID)
+ if err != nil {
+ return "", err
+ }
+ if portZip != nil && portType != nil {
+ // if the port type is POEFSC this means the shipment is CONUS -> OCONUS (pickup -> port)
+ // if the port type is PODFSC this means the shipment is OCONUS -> CONUS (port -> destination)
+ if *portType == models.ReServiceCodePOEFSC.String() {
+ destinationZip = *portZip
+ } else if *portType == models.ReServiceCodePODFSC.String() {
+ pickupZip = *portZip
+ }
+ } else {
+ return "", apperror.NewNotFoundError(*mtoShipmentID, "looking for port ZIP for shipment")
}
} else {
- return "", apperror.NewNotFoundError(*mtoShipmentID, "looking for port ZIP for shipment")
+ // PPMs get reimbursed for their travel from CONUS <-> Port ZIPs, but only for the Tacoma Port
+ portLocation, err := models.FetchPortLocationByCode(appCtx.DB(), "4E1") // Tacoma port code
+ if err != nil {
+ return "", fmt.Errorf("unable to find port zip with code %s", "4E1")
+ }
+ if mtoShipment.PPMShipment != nil && mtoShipment.PPMShipment.PickupAddress != nil && mtoShipment.PPMShipment.DestinationAddress != nil {
+ // need to figure out if we are going to go Port -> CONUS or CONUS -> Port
+ pickupOconus := *mtoShipment.PPMShipment.PickupAddress.IsOconus
+ destOconus := *mtoShipment.PPMShipment.DestinationAddress.IsOconus
+ if pickupOconus && !destOconus {
+ // Port ZIP -> CONUS ZIP
+ pickupZip = portLocation.UsPostRegionCity.UsprZipID
+ destinationZip = mtoShipment.PPMShipment.DestinationAddress.PostalCode
+ } else if !pickupOconus && destOconus {
+ // CONUS ZIP -> Port ZIP
+ pickupZip = mtoShipment.PPMShipment.PickupAddress.PostalCode
+ destinationZip = portLocation.UsPostRegionCity.UsprZipID
+ } else {
+ // OCONUS -> OCONUS mileage they don't get reimbursed for this
+ return strconv.Itoa(0), nil
+ }
+ } else {
+ return "", fmt.Errorf("missing required PPM & address information for shipment with id %s", mtoShipmentID)
+ }
}
}
errorMsgForPickupZip := fmt.Sprintf("Shipment must have valid pickup zipcode. Received: %s", pickupZip)
diff --git a/pkg/payment_request/service_param_value_lookups/distance_zip_lookup_test.go b/pkg/payment_request/service_param_value_lookups/distance_zip_lookup_test.go
index 48c920919d0..8b8d866b313 100644
--- a/pkg/payment_request/service_param_value_lookups/distance_zip_lookup_test.go
+++ b/pkg/payment_request/service_param_value_lookups/distance_zip_lookup_test.go
@@ -125,6 +125,56 @@ func (suite *ServiceParamValueLookupsSuite) TestDistanceLookup() {
suite.Equal(unit.Miles(defaultInternationalZipDistance), *mtoShipment.Distance)
})
+ suite.Run("Call ZipTransitDistance on international PPMs with CONUS -> Tacoma Port ZIP", func() {
+ miles := unit.Miles(defaultZipDistance)
+
+ ppmShipment := factory.BuildPPMShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.MTOShipment{
+ Distance: &miles,
+ ShipmentType: models.MTOShipmentTypePPM,
+ MarketCode: models.MarketCodeInternational,
+ },
+ },
+ {
+ Model: models.Address{
+ StreetAddress1: "Tester Address",
+ City: "Tulsa",
+ State: "OK",
+ PostalCode: "74133",
+ },
+ Type: &factory.Addresses.PickupAddress,
+ },
+ {
+ Model: models.Address{
+ StreetAddress1: "JBER",
+ City: "JBER",
+ State: "AK",
+ PostalCode: "99505",
+ IsOconus: models.BoolPointer(true),
+ },
+ Type: &factory.Addresses.DeliveryAddress,
+ },
+ }, nil)
+
+ distanceZipLookup := DistanceZipLookup{
+ PickupAddress: models.Address{PostalCode: ppmShipment.PickupAddress.PostalCode},
+ DestinationAddress: models.Address{PostalCode: ppmShipment.DestinationAddress.PostalCode},
+ }
+
+ appContext := suite.AppContextForTest()
+ distance, err := distanceZipLookup.lookup(appContext, &ServiceItemParamKeyData{
+ planner: suite.planner,
+ mtoShipmentID: &ppmShipment.ShipmentID,
+ })
+ suite.NoError(err)
+ suite.NotNil(distance)
+
+ planner := suite.planner.(*mocks.Planner)
+ // should be called with the 98421 ZIP of the Tacoma port and NOT 99505
+ planner.AssertCalled(suite.T(), "ZipTransitDistance", appContext, ppmShipment.PickupAddress.PostalCode, "98421", true)
+ })
+
suite.Run("Calculate transit zip distance with an approved Destination SIT service item", func() {
testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{
ReContractYear: models.ReContractYear{
diff --git a/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go b/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go
index f8efda9d451..37032a5ccf9 100644
--- a/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go
+++ b/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup.go
@@ -2,6 +2,9 @@ package serviceparamvaluelookups
import (
"fmt"
+ "time"
+
+ "github.com/gofrs/uuid"
"github.com/transcom/mymove/pkg/appcontext"
"github.com/transcom/mymove/pkg/models"
@@ -16,19 +19,67 @@ type PerUnitCentsLookup struct {
func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemParamKeyData) (string, error) {
serviceID := p.ServiceItem.ReServiceID
+ if serviceID == uuid.Nil {
+ reService, err := models.FetchReServiceByCode(appCtx.DB(), p.ServiceItem.ReService.Code)
+ if err != nil {
+ return "", fmt.Errorf("error fetching ReService Code %s: %w", p.ServiceItem.ReService.Code, err)
+ }
+ serviceID = reService.ID
+ }
contractID := s.ContractID
- if p.MTOShipment.RequestedPickupDate == nil {
- return "", fmt.Errorf("requested pickup date is required for shipment with id: %s", p.MTOShipment.ID)
+ var shipmentID uuid.UUID
+ var pickupAddressID uuid.UUID
+ var destinationAddressID uuid.UUID
+ var moveDate time.Time
+ // HHG shipment
+ if p.MTOShipment.ShipmentType != models.MTOShipmentTypePPM {
+ shipmentID = p.MTOShipment.ID
+ if p.MTOShipment.RequestedPickupDate != nil {
+ moveDate = *p.MTOShipment.RequestedPickupDate
+ } else {
+ return "", fmt.Errorf("requested pickup date is required for shipment with id: %s", shipmentID)
+ }
+ if p.MTOShipment.PickupAddressID != nil {
+ pickupAddressID = *p.MTOShipment.PickupAddressID
+ } else {
+ return "", fmt.Errorf("pickup address is required for shipment with id: %s", shipmentID)
+ }
+ if p.MTOShipment.DestinationAddressID != nil {
+ destinationAddressID = *p.MTOShipment.DestinationAddressID
+ } else {
+ return "", fmt.Errorf("destination address is required for shipment with id: %s", shipmentID)
+ }
+ } else { // PPM shipment
+ shipmentID = p.MTOShipment.PPMShipment.ID
+ if p.MTOShipment.ActualPickupDate != nil {
+ moveDate = *p.MTOShipment.ActualPickupDate
+ } else if p.MTOShipment.RequestedPickupDate != nil {
+ moveDate = *p.MTOShipment.RequestedPickupDate
+ } else {
+ return "", fmt.Errorf("actual move date is required for PPM shipment with id: %s", shipmentID)
+ }
+
+ if p.MTOShipment.PPMShipment.PickupAddressID != nil {
+ pickupAddressID = *p.MTOShipment.PPMShipment.PickupAddressID
+ } else {
+ return "", fmt.Errorf("pickup address is required for PPM shipment with id: %s", shipmentID)
+ }
+
+ if p.MTOShipment.PPMShipment.DestinationAddressID != nil {
+ destinationAddressID = *p.MTOShipment.PPMShipment.DestinationAddressID
+ } else {
+ return "", fmt.Errorf("destination address is required for PPM shipment with id: %s", shipmentID)
+ }
}
switch p.ServiceItem.ReService.Code {
case models.ReServiceCodeIHPK:
// IHPK: Need rate area ID for the pickup address
- rateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, &serviceID, contractID)
+ rateAreaID, err := models.FetchRateAreaID(appCtx.DB(), pickupAddressID, &serviceID, contractID)
if err != nil {
- return "", fmt.Errorf("error fetching rate area id for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, serviceID, err)
+ return "", fmt.Errorf("error fetching rate area id for shipment ID: %s and service ID %s: %s", shipmentID, serviceID, err)
}
- isPeakPeriod := ghcrateengine.IsPeakPeriod(*p.MTOShipment.RequestedPickupDate)
+ isPeakPeriod := ghcrateengine.IsPeakPeriod(moveDate)
var reIntlOtherPrice models.ReIntlOtherPrice
err = appCtx.DB().Q().
Where("contract_id = ?", contractID).
@@ -43,11 +94,11 @@ func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemP
case models.ReServiceCodeIHUPK:
// IHUPK: Need rate area ID for the destination address
- rateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.DestinationAddressID, &serviceID, contractID)
+ rateAreaID, err := models.FetchRateAreaID(appCtx.DB(), destinationAddressID, &serviceID, contractID)
if err != nil {
- return "", fmt.Errorf("error fetching rate area id for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, serviceID, err)
+ return "", fmt.Errorf("error fetching rate area id for shipment ID: %s and service ID %s: %s", shipmentID, serviceID, err)
}
- isPeakPeriod := ghcrateengine.IsPeakPeriod(*p.MTOShipment.RequestedPickupDate)
+ isPeakPeriod := ghcrateengine.IsPeakPeriod(moveDate)
var reIntlOtherPrice models.ReIntlOtherPrice
err = appCtx.DB().Q().
Where("contract_id = ?", contractID).
@@ -62,15 +113,15 @@ func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemP
case models.ReServiceCodeISLH:
// ISLH: Need rate area IDs for origin and destination
- originRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.PickupAddressID, &serviceID, contractID)
+ originRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), pickupAddressID, &serviceID, contractID)
if err != nil {
- return "", fmt.Errorf("error fetching rate area id for origin address for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, serviceID, err)
+ return "", fmt.Errorf("error fetching rate area id for origin address for shipment ID: %s and service ID %s: %s", shipmentID, serviceID, err)
}
- destRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), *p.MTOShipment.DestinationAddressID, &serviceID, contractID)
+ destRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), destinationAddressID, &serviceID, contractID)
if err != nil {
- return "", fmt.Errorf("error fetching rate area id for destination address for shipment ID: %s and service ID %s: %s", p.MTOShipment.ID, serviceID, err)
+ return "", fmt.Errorf("error fetching rate area id for destination address for shipment ID: %s and service ID %s: %s", shipmentID, serviceID, err)
}
- isPeakPeriod := ghcrateengine.IsPeakPeriod(*p.MTOShipment.RequestedPickupDate)
+ isPeakPeriod := ghcrateengine.IsPeakPeriod(moveDate)
var reIntlPrice models.ReIntlPrice
err = appCtx.DB().Q().
Where("contract_id = ?", contractID).
@@ -84,6 +135,82 @@ func (p PerUnitCentsLookup) lookup(appCtx appcontext.AppContext, s *ServiceItemP
}
return reIntlPrice.PerUnitCents.ToMillicents().ToCents().String(), nil
+ case models.ReServiceCodeIOFSIT:
+ // IOFSIT: Need rate area ID for origin
+ originRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), pickupAddressID, &serviceID, contractID)
+ if err != nil {
+ return "", fmt.Errorf("error fetching rate area id for origin address for shipment ID: %s and service ID %s: %s", shipmentID, serviceID, err)
+ }
+ isPeakPeriod := ghcrateengine.IsPeakPeriod(moveDate)
+ var reIntlOtherPrice models.ReIntlOtherPrice
+ err = appCtx.DB().Q().
+ Where("contract_id = ?", contractID).
+ Where("service_id = ?", serviceID).
+ Where("is_peak_period = ?", isPeakPeriod).
+ Where("rate_area_id = ?", originRateAreaID).
+ First(&reIntlOtherPrice)
+ if err != nil {
+ return "", fmt.Errorf("error fetching IOFSIT per unit cents for contractID: %s, serviceID %s, isPeakPeriod: %t, originRateAreaID: %s: %s", contractID, serviceID, isPeakPeriod, originRateAreaID, err)
+ }
+ return reIntlOtherPrice.PerUnitCents.ToMillicents().ToCents().String(), nil
+
+ case models.ReServiceCodeIOASIT:
+ // IOASIT: Need rate area ID for origin
+ originRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), pickupAddressID, &serviceID, contractID)
+ if err != nil {
+ return "", fmt.Errorf("error fetching rate area id for origin address for shipment ID: %s, service ID %s: %s", shipmentID, serviceID, err)
+ }
+ isPeakPeriod := ghcrateengine.IsPeakPeriod(moveDate)
+ var reIntlOtherPrice models.ReIntlOtherPrice
+ err = appCtx.DB().Q().
+ Where("contract_id = ?", contractID).
+ Where("service_id = ?", serviceID).
+ Where("is_peak_period = ?", isPeakPeriod).
+ Where("rate_area_id = ?", originRateAreaID).
+ First(&reIntlOtherPrice)
+ if err != nil {
+ return "", fmt.Errorf("error fetching IOASIT per unit cents for contractID: %s, serviceID %s, isPeakPeriod: %t, originRateAreaID: %s: %s", contractID, serviceID, isPeakPeriod, originRateAreaID, err)
+ }
+ return reIntlOtherPrice.PerUnitCents.ToMillicents().ToCents().String(), nil
+
+ case models.ReServiceCodeIDFSIT:
+ // IDFSIT: Need rate area ID for destination
+ destRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), destinationAddressID, &serviceID, contractID)
+ if err != nil {
+ return "", fmt.Errorf("error fetching rate area id for destination address for shipment ID: %s, service ID %s: %s", shipmentID, serviceID, err)
+ }
+ isPeakPeriod := ghcrateengine.IsPeakPeriod(moveDate)
+ var reIntlOtherPrice models.ReIntlOtherPrice
+ err = appCtx.DB().Q().
+ Where("contract_id = ?", contractID).
+ Where("service_id = ?", serviceID).
+ Where("is_peak_period = ?", isPeakPeriod).
+ Where("rate_area_id = ?", destRateAreaID).
+ First(&reIntlOtherPrice)
+ if err != nil {
+ return "", fmt.Errorf("error fetching IDFSIT per unit cents for contractID: %s, serviceID %s, isPeakPeriod: %t, destRateAreaID: %s: %s", contractID, serviceID, isPeakPeriod, destRateAreaID, err)
+ }
+ return reIntlOtherPrice.PerUnitCents.ToMillicents().ToCents().String(), nil
+
+ case models.ReServiceCodeIDASIT:
+ // IDASIT: Need rate area ID for destination
+ destRateAreaID, err := models.FetchRateAreaID(appCtx.DB(), destinationAddressID, &serviceID, contractID)
+ if err != nil {
+ return "", fmt.Errorf("error fetching rate area id for destination address for shipment ID: %s and service ID %s: %s", shipmentID, serviceID, err)
+ }
+ isPeakPeriod := ghcrateengine.IsPeakPeriod(moveDate)
+ var reIntlOtherPrice models.ReIntlOtherPrice
+ err = appCtx.DB().Q().
+ Where("contract_id = ?", contractID).
+ Where("service_id = ?", serviceID).
+ Where("is_peak_period = ?", isPeakPeriod).
+ Where("rate_area_id = ?", destRateAreaID).
+ First(&reIntlOtherPrice)
+ if err != nil {
+ return "", fmt.Errorf("error fetching IDASIT per unit cents for contractID: %s, serviceID %s, isPeakPeriod: %t, destRateAreaID: %s: %s", contractID, serviceID, isPeakPeriod, destRateAreaID, err)
+ }
+ return reIntlOtherPrice.PerUnitCents.ToMillicents().ToCents().String(), nil
+
default:
return "", fmt.Errorf("unsupported service code to retrieve service item param PerUnitCents")
}
diff --git a/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup_test.go b/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup_test.go
index 9937f86217b..dc69b69f888 100644
--- a/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup_test.go
+++ b/pkg/payment_request/service_param_value_lookups/per_unit_cents_lookup_test.go
@@ -30,6 +30,105 @@ func (suite *ServiceParamValueLookupsSuite) TestPerUnitCentsLookup() {
}
+ setupTestDataPickupOCONUS := func(serviceCode models.ReServiceCode) models.Move {
+ testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{
+ ReContractYear: models.ReContractYear{
+ StartDate: time.Now().Add(-24 * time.Hour),
+ EndDate: time.Now().Add(24 * time.Hour),
+ },
+ })
+ move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil)
+ address := factory.BuildAddress(suite.DB(), []factory.Customization{
+ {
+ Model: models.Address{
+ StreetAddress1: "JBER",
+ City: "Anchorage",
+ State: "AK",
+ PostalCode: "99505",
+ IsOconus: models.BoolPointer(true),
+ },
+ },
+ }, nil)
+ shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.MTOShipment{
+ PickupAddressID: &address.ID,
+ MarketCode: models.MarketCodeInternational,
+ },
+ },
+ {
+ Model: move,
+ LinkOnly: true,
+ },
+ }, nil)
+ mtoServiceItem = factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{
+ {
+ Model: move,
+ LinkOnly: true,
+ },
+ {
+ Model: shipment,
+ LinkOnly: true,
+ },
+ {
+ Model: models.ReService{
+ Code: serviceCode,
+ },
+ },
+ }, nil)
+
+ return move
+ }
+
+ setupTestDataDestOCONUS := func(serviceCode models.ReServiceCode) models.Move {
+ testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{
+ ReContractYear: models.ReContractYear{
+ StartDate: time.Now().Add(-24 * time.Hour),
+ EndDate: time.Now().Add(24 * time.Hour),
+ },
+ })
+ move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil)
+ address := factory.BuildAddress(suite.DB(), []factory.Customization{
+ {
+ Model: models.Address{
+ StreetAddress1: "JBER",
+ City: "Anchorage",
+ State: "AK",
+ PostalCode: "99505",
+ IsOconus: models.BoolPointer(true),
+ },
+ },
+ }, nil)
+ shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.MTOShipment{
+ DestinationAddressID: &address.ID,
+ MarketCode: models.MarketCodeInternational,
+ },
+ },
+ {
+ Model: move,
+ LinkOnly: true,
+ },
+ }, nil)
+ mtoServiceItem = factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{
+ {
+ Model: move,
+ LinkOnly: true,
+ },
+ {
+ Model: shipment,
+ LinkOnly: true,
+ },
+ {
+ Model: models.ReService{
+ Code: serviceCode,
+ },
+ },
+ }, nil)
+ return move
+ }
+
suite.Run("success - returns perUnitCent value for IHPK", func() {
setupTestData(models.ReServiceCodeIHPK)
@@ -119,6 +218,115 @@ func (suite *ServiceParamValueLookupsSuite) TestPerUnitCentsLookup() {
suite.Equal(perUnitCents, "1605")
})
+ suite.Run("success - returns perUnitCent value for IOFSIT", func() {
+ move := setupTestDataPickupOCONUS(models.ReServiceCodeIOFSIT)
+
+ paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), move.ID, nil)
+ suite.FatalNoError(err)
+
+ perUnitCents, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key)
+ suite.FatalNoError(err)
+ suite.Equal(perUnitCents, "607")
+ })
+
+ suite.Run("success - returns perUnitCent value for IOASIT", func() {
+ move := setupTestDataPickupOCONUS(models.ReServiceCodeIOASIT)
+
+ paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), move.ID, nil)
+ suite.FatalNoError(err)
+
+ perUnitCents, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key)
+ suite.FatalNoError(err)
+ suite.Equal(perUnitCents, "14")
+ })
+
+ suite.Run("success - returns perUnitCent value for IDFSIT", func() {
+ move := setupTestDataDestOCONUS(models.ReServiceCodeIDFSIT)
+
+ paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), move.ID, nil)
+ suite.FatalNoError(err)
+
+ perUnitCents, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key)
+ suite.FatalNoError(err)
+ suite.Equal(perUnitCents, "607")
+ })
+
+ suite.Run("success - returns perUnitCent value for IDASIT", func() {
+ move := setupTestDataDestOCONUS(models.ReServiceCodeIDASIT)
+
+ paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), move.ID, nil)
+ suite.FatalNoError(err)
+
+ perUnitCents, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key)
+ suite.FatalNoError(err)
+ suite.Equal(perUnitCents, "14")
+ })
+
+ suite.Run("success - returns perUnitCent value for IDASIT for a PPM", func() {
+ contractYear := testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{
+ ReContractYear: models.ReContractYear{
+ StartDate: time.Now().Add(-24 * time.Hour),
+ EndDate: time.Now().Add(24 * time.Hour),
+ },
+ })
+ move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil)
+ ppm := factory.BuildPPMShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.MTOShipment{
+ ActualPickupDate: models.TimePointer(time.Now()),
+ },
+ },
+ {
+ Model: models.Address{
+ StreetAddress1: "Tester Address",
+ City: "Tulsa",
+ State: "OK",
+ PostalCode: "74133",
+ },
+ Type: &factory.Addresses.PickupAddress,
+ },
+ {
+ Model: models.Address{
+ StreetAddress1: "JBER",
+ City: "JBER",
+ State: "AK",
+ PostalCode: "99505",
+ IsOconus: models.BoolPointer(true),
+ },
+ Type: &factory.Addresses.DeliveryAddress,
+ },
+ }, nil)
+
+ mtoServiceItem = factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{
+ {
+ Model: models.ReService{
+ Code: models.ReServiceCodeIDASIT,
+ },
+ },
+ {
+ Model: move,
+ LinkOnly: true,
+ },
+ }, nil)
+
+ _, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), mtoServiceItem.MoveTaskOrderID, nil)
+ suite.FatalNoError(err)
+
+ perUnitCentsLookup := PerUnitCentsLookup{
+ ServiceItem: mtoServiceItem,
+ MTOShipment: ppm.Shipment,
+ }
+
+ appContext := suite.AppContextForTest()
+ perUnitCents, err := perUnitCentsLookup.lookup(appContext, &ServiceItemParamKeyData{
+ planner: suite.planner,
+ mtoShipmentID: &ppm.ShipmentID,
+ ContractID: contractYear.ContractID,
+ })
+ suite.NoError(err)
+ suite.Equal(perUnitCents, "14")
+ })
+
suite.Run("failure - unauthorized service code", func() {
setupTestData(models.ReServiceCodeDUPK)
diff --git a/pkg/payment_request/service_param_value_lookups/port_zip_lookup.go b/pkg/payment_request/service_param_value_lookups/port_zip_lookup.go
index 3ea8be94315..9621c56cd8a 100644
--- a/pkg/payment_request/service_param_value_lookups/port_zip_lookup.go
+++ b/pkg/payment_request/service_param_value_lookups/port_zip_lookup.go
@@ -15,14 +15,27 @@ type PortZipLookup struct {
ServiceItem models.MTOServiceItem
}
-func (p PortZipLookup) lookup(appCtx appcontext.AppContext, _ *ServiceItemParamKeyData) (string, error) {
+func (p PortZipLookup) lookup(appCtx appcontext.AppContext, keyData *ServiceItemParamKeyData) (string, error) {
var portLocationID *uuid.UUID
if p.ServiceItem.PODLocationID != nil {
portLocationID = p.ServiceItem.PODLocationID
} else if p.ServiceItem.POELocationID != nil {
portLocationID = p.ServiceItem.POELocationID
} else {
- return "", fmt.Errorf("unable to find port zip for service item id: %s", p.ServiceItem.ID)
+ // for PPMs we need to send back the ZIP for the Tacoma Port, they are reimbursed for their CONUS <-> Port travel
+ shipment, err := models.FetchShipmentByID(appCtx.DB(), *keyData.mtoShipmentID)
+ if err != nil {
+ return "", fmt.Errorf("unable to find shipment with id %s", keyData.mtoShipmentID)
+ }
+ if shipment.ShipmentType == models.MTOShipmentTypePPM && shipment.MarketCode == models.MarketCodeInternational {
+ portLocation, err := models.FetchPortLocationByCode(appCtx.DB(), "4E1")
+ if err != nil {
+ return "", fmt.Errorf("unable to find port zip with code %s", "4E1")
+ }
+ return portLocation.UsPostRegionCity.UsprZipID, nil
+ } else {
+ return "", nil
+ }
}
var portLocation models.PortLocation
err := appCtx.DB().Q().
diff --git a/pkg/payment_request/service_param_value_lookups/port_zip_lookup_test.go b/pkg/payment_request/service_param_value_lookups/port_zip_lookup_test.go
index 4410ba8e198..b06533a4e1f 100644
--- a/pkg/payment_request/service_param_value_lookups/port_zip_lookup_test.go
+++ b/pkg/payment_request/service_param_value_lookups/port_zip_lookup_test.go
@@ -85,7 +85,76 @@ func (suite *ServiceParamValueLookupsSuite) TestPortZipLookup() {
suite.Equal(portZip, port.UsPostRegionCity.UsprZipID)
})
- suite.Run("failure - no port zip on service item", func() {
+ suite.Run("success - returns PortZip value for Port Code 4E1 for PPMs", func() {
+ port := factory.FetchPortLocation(suite.DB(), []factory.Customization{
+ {
+ Model: models.Port{
+ PortCode: "4E1",
+ },
+ },
+ }, nil)
+
+ contractYear := testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{
+ ReContractYear: models.ReContractYear{
+ StartDate: time.Now().Add(-24 * time.Hour),
+ EndDate: time.Now().Add(24 * time.Hour),
+ },
+ })
+
+ move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil)
+ ppm := factory.BuildPPMShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.MTOShipment{
+ ActualPickupDate: models.TimePointer(time.Now()),
+ MarketCode: models.MarketCodeInternational,
+ },
+ },
+ {
+ Model: models.Address{
+ StreetAddress1: "Tester Address",
+ City: "Tulsa",
+ State: "OK",
+ PostalCode: "74133",
+ },
+ Type: &factory.Addresses.PickupAddress,
+ },
+ {
+ Model: models.Address{
+ StreetAddress1: "JBER",
+ City: "JBER",
+ State: "AK",
+ PostalCode: "99505",
+ IsOconus: models.BoolPointer(true),
+ },
+ Type: &factory.Addresses.DeliveryAddress,
+ },
+ }, nil)
+
+ mtoServiceItem = factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{
+ {
+ Model: move,
+ LinkOnly: true,
+ },
+ }, nil)
+
+ _, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), mtoServiceItem.MoveTaskOrderID, nil)
+ suite.FatalNoError(err)
+
+ portZipLookup := PortZipLookup{
+ ServiceItem: mtoServiceItem,
+ }
+
+ appContext := suite.AppContextForTest()
+ portZip, err := portZipLookup.lookup(appContext, &ServiceItemParamKeyData{
+ planner: suite.planner,
+ mtoShipmentID: &ppm.ShipmentID,
+ ContractID: contractYear.ContractID,
+ })
+ suite.NoError(err)
+ suite.Equal(portZip, port.UsPostRegionCity.UsprZipID)
+ })
+
+ suite.Run("returns nothing if shipment is HHG and service item does not have port info", func() {
testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{
ReContractYear: models.ReContractYear{
StartDate: time.Now().Add(-24 * time.Hour),
@@ -108,7 +177,8 @@ func (suite *ServiceParamValueLookupsSuite) TestPortZipLookup() {
paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), mtoServiceItem.MoveTaskOrderID, nil)
suite.FatalNoError(err)
- _, err = paramLookup.ServiceParamValue(suite.AppContextForTest(), key)
- suite.Error(err)
+ portZip, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key)
+ suite.NoError(err)
+ suite.Equal(portZip, "")
})
}
diff --git a/pkg/payment_request/service_param_value_lookups/service_param_value_lookups.go b/pkg/payment_request/service_param_value_lookups/service_param_value_lookups.go
index 580ec02bc19..6815d250f28 100644
--- a/pkg/payment_request/service_param_value_lookups/service_param_value_lookups.go
+++ b/pkg/payment_request/service_param_value_lookups/service_param_value_lookups.go
@@ -27,7 +27,7 @@ type ServiceItemParamKeyData struct {
paramCache *ServiceParamsCache
}
-func NewServiceItemParamKeyData(planner route.Planner, lookups map[models.ServiceItemParamName]ServiceItemParamKeyLookup, mtoServiceItem models.MTOServiceItem, mtoShipment models.MTOShipment, contractCode string) ServiceItemParamKeyData {
+func NewServiceItemParamKeyData(planner route.Planner, lookups map[models.ServiceItemParamName]ServiceItemParamKeyLookup, mtoServiceItem models.MTOServiceItem, mtoShipment models.MTOShipment, contractCode string, contractID uuid.UUID) ServiceItemParamKeyData {
return ServiceItemParamKeyData{
planner: planner,
lookups: lookups,
@@ -36,6 +36,7 @@ func NewServiceItemParamKeyData(planner route.Planner, lookups map[models.Servic
mtoShipmentID: &mtoShipment.ID,
MoveTaskOrderID: mtoShipment.MoveTaskOrderID,
ContractCode: contractCode,
+ ContractID: contractID,
}
}
@@ -213,8 +214,11 @@ func ServiceParamLookupInitialize(
mtoShipment.DestinationAddress = &destinationAddress
switch mtoServiceItem.ReService.Code {
- case models.ReServiceCodeDDASIT, models.ReServiceCodeDDDSIT, models.ReServiceCodeDDFSIT, models.ReServiceCodeDDSFSC, models.ReServiceCodeDOASIT, models.ReServiceCodeDOPSIT, models.ReServiceCodeDOFSIT, models.ReServiceCodeDOSFSC:
- err := appCtx.DB().Load(&mtoShipment, "SITDurationUpdates")
+ case models.ReServiceCodeDDASIT, models.ReServiceCodeDDDSIT, models.ReServiceCodeDDFSIT,
+ models.ReServiceCodeDDSFSC, models.ReServiceCodeDOASIT, models.ReServiceCodeDOPSIT,
+ models.ReServiceCodeDOFSIT, models.ReServiceCodeDOSFSC, models.ReServiceCodeIOFSIT,
+ models.ReServiceCodeIOASIT, models.ReServiceCodeIDFSIT, models.ReServiceCodeIDASIT:
+ err := appCtx.DB().Load(&mtoShipment, "SITDurationUpdates", "PPMShipment.PickupAddress", "PPMShipment.DestinationAddress")
if err != nil {
return nil, err
}
diff --git a/pkg/payment_request/service_param_value_lookups/service_param_value_lookups_test.go b/pkg/payment_request/service_param_value_lookups/service_param_value_lookups_test.go
index d9b197d44d5..34d4a025d2b 100644
--- a/pkg/payment_request/service_param_value_lookups/service_param_value_lookups_test.go
+++ b/pkg/payment_request/service_param_value_lookups/service_param_value_lookups_test.go
@@ -172,7 +172,7 @@ func (suite *ServiceParamValueLookupsSuite) setupTestMTOServiceItemWithEstimated
// i don't think this function gets called for PPMs, but need to verify
//paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, paymentRequest.ID, paymentRequest.MoveTaskOrderID, nil)
//suite.FatalNoError(err)
- paramLookup := NewServiceItemParamKeyData(suite.planner, serviceItemLookups, mtoServiceItem, mtoShipment, testdatagen.DefaultContractCode)
+ paramLookup := NewServiceItemParamKeyData(suite.planner, serviceItemLookups, mtoServiceItem, mtoShipment, testdatagen.DefaultContractCode, uuid.Nil)
return mtoServiceItem, paymentRequest, ¶mLookup
}
diff --git a/pkg/payment_request/service_param_value_lookups/service_params_cache_test.go b/pkg/payment_request/service_param_value_lookups/service_params_cache_test.go
index f55832cc189..999db3de94b 100644
--- a/pkg/payment_request/service_param_value_lookups/service_params_cache_test.go
+++ b/pkg/payment_request/service_param_value_lookups/service_params_cache_test.go
@@ -20,7 +20,7 @@ type paramsCacheSubtestData struct {
mtoServiceItemMS models.MTOServiceItem
mtoServiceItemCrate1 models.MTOServiceItem
mtoServiceItemCrate2 models.MTOServiceItem
- mtoServiceItemShuttle models.MTOServiceItem
+ mtoServiceItemDomesticShuttle models.MTOServiceItem
paramKeyWeightEstimated models.ServiceItemParamKey
paramKeyRequestedPickupDate models.ServiceItemParamKey
paramKeyMTOAvailableToPrimeAt models.ServiceItemParamKey
@@ -224,7 +224,7 @@ func (suite *ServiceParamValueLookupsSuite) makeSubtestData() (subtestData *para
subtestData.shuttleEstimatedWeight = unit.Pound(400)
subtestData.shuttleActualWeight = unit.Pound(450)
- subtestData.mtoServiceItemShuttle = factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{
+ subtestData.mtoServiceItemDomesticShuttle = factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{
{
Model: subtestData.move,
LinkOnly: true,
@@ -248,7 +248,7 @@ func (suite *ServiceParamValueLookupsSuite) makeSubtestData() (subtestData *para
// DOSHUT estimated weight
factory.BuildServiceParam(suite.DB(), []factory.Customization{
{
- Model: subtestData.mtoServiceItemShuttle.ReService,
+ Model: subtestData.mtoServiceItemDomesticShuttle.ReService,
LinkOnly: true,
},
{
@@ -462,7 +462,7 @@ func (suite *ServiceParamValueLookupsSuite) TestServiceParamCache() {
expected := strconv.Itoa(subtestData.estimatedWeight.Int())
suite.Equal(expected, estimatedWeightStr)
- paramLookupService2, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, subtestData.mtoServiceItemShuttle, subtestData.paymentRequest.ID, subtestData.paymentRequest.MoveTaskOrderID, ¶mCache)
+ paramLookupService2, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, subtestData.mtoServiceItemDomesticShuttle, subtestData.paymentRequest.ID, subtestData.paymentRequest.MoveTaskOrderID, ¶mCache)
suite.NoError(err)
var shuttleEstimatedWeightStr string
diff --git a/pkg/payment_request/service_param_value_lookups/weight_billed_lookup.go b/pkg/payment_request/service_param_value_lookups/weight_billed_lookup.go
index bca7555ea28..2a3e2f8bb62 100644
--- a/pkg/payment_request/service_param_value_lookups/weight_billed_lookup.go
+++ b/pkg/payment_request/service_param_value_lookups/weight_billed_lookup.go
@@ -200,8 +200,13 @@ func applyMinimum(code models.ReServiceCode, shipmentType models.MTOShipmentType
switch shipmentType {
case models.MTOShipmentTypeUnaccompaniedBaggage:
switch code {
- case models.ReServiceCodeIOSHUT,
- models.ReServiceCodeIDSHUT:
+ case models.ReServiceCodeUBP,
+ models.ReServiceCodeIUBPK,
+ models.ReServiceCodeIUBUPK,
+ models.ReServiceCodeIOSHUT,
+ models.ReServiceCodeIDSHUT,
+ models.ReServiceCodePODFSC,
+ models.ReServiceCodePOEFSC:
if weight < 300 {
result = 300
}
@@ -238,16 +243,12 @@ func applyMinimum(code models.ReServiceCode, shipmentType models.MTOShipmentType
models.ReServiceCodeIDDSIT,
models.ReServiceCodeIOSHUT,
models.ReServiceCodeIDSHUT,
- models.ReServiceCodeFSC:
+ models.ReServiceCodeFSC,
+ models.ReServiceCodePODFSC,
+ models.ReServiceCodePOEFSC:
if weight < 500 {
result = 500
}
- case models.ReServiceCodeUBP,
- models.ReServiceCodeIUBPK,
- models.ReServiceCodeIUBUPK:
- if weight < 300 {
- result = 300
- }
}
}
return fmt.Sprintf("%d", result)
diff --git a/pkg/payment_request/service_param_value_lookups/weight_billed_lookup_test.go b/pkg/payment_request/service_param_value_lookups/weight_billed_lookup_test.go
index 78072940ae1..74ffcd88754 100644
--- a/pkg/payment_request/service_param_value_lookups/weight_billed_lookup_test.go
+++ b/pkg/payment_request/service_param_value_lookups/weight_billed_lookup_test.go
@@ -95,15 +95,15 @@ func (suite *ServiceParamValueLookupsSuite) TestWeightBilledLookup() {
{models.ReServiceCodeDDDSIT, unit.Pound(450), "500", models.MTOShipmentTypeHHG},
// International
{models.ReServiceCodeISLH, unit.Pound(450), "500", models.MTOShipmentTypeHHG},
- {models.ReServiceCodeUBP, unit.Pound(250), "300", models.MTOShipmentTypeHHG},
- {models.ReServiceCodeISLH, unit.Pound(450), "500", models.MTOShipmentTypeHHG},
- {models.ReServiceCodeUBP, unit.Pound(250), "300", models.MTOShipmentTypeHHG},
- {models.ReServiceCodeISLH, unit.Pound(450), "500", models.MTOShipmentTypeHHG},
- {models.ReServiceCodeUBP, unit.Pound(250), "300", models.MTOShipmentTypeHHG},
{models.ReServiceCodeIHPK, unit.Pound(450), "500", models.MTOShipmentTypeHHG},
{models.ReServiceCodeIHUPK, unit.Pound(450), "500", models.MTOShipmentTypeHHG},
- {models.ReServiceCodeIUBPK, unit.Pound(250), "300", models.MTOShipmentTypeHHG},
- {models.ReServiceCodeIUBUPK, unit.Pound(250), "300", models.MTOShipmentTypeHHG},
+ {models.ReServiceCodePOEFSC, unit.Pound(450), "500", models.MTOShipmentTypeHHG},
+ {models.ReServiceCodePODFSC, unit.Pound(450), "500", models.MTOShipmentTypeHHG},
+ {models.ReServiceCodeUBP, unit.Pound(250), "300", models.MTOShipmentTypeUnaccompaniedBaggage},
+ {models.ReServiceCodeIUBPK, unit.Pound(250), "300", models.MTOShipmentTypeUnaccompaniedBaggage},
+ {models.ReServiceCodeIUBUPK, unit.Pound(250), "300", models.MTOShipmentTypeUnaccompaniedBaggage},
+ {models.ReServiceCodePOEFSC, unit.Pound(250), "300", models.MTOShipmentTypeUnaccompaniedBaggage},
+ {models.ReServiceCodePODFSC, unit.Pound(250), "300", models.MTOShipmentTypeUnaccompaniedBaggage},
// International SIT
{models.ReServiceCodeIOFSIT, unit.Pound(450), "500", models.MTOShipmentTypeHHG},
{models.ReServiceCodeIDFSIT, unit.Pound(450), "500", models.MTOShipmentTypeHHG},
diff --git a/pkg/services/event/ghc_endpoint.go b/pkg/services/event/ghc_endpoint.go
index d24e480a02e..b06fa47d5d7 100644
--- a/pkg/services/event/ghc_endpoint.go
+++ b/pkg/services/event/ghc_endpoint.go
@@ -43,6 +43,9 @@ const GhcDeleteShipmentEndpointKey = "Ghc.DeleteShipment"
// GhcApproveShipmentEndpointKey is the key for the approveShipment endpoint in ghc
const GhcApproveShipmentEndpointKey = "Ghc.ApproveShipment"
+// GhcApproveShipmentEndpointKey is the key for the approveShipment endpoint in ghc
+const GhcApproveShipmentsEndpointKey = "Ghc.ApproveShipments"
+
// GhcRequestShipmentDiversionEndpointKey is the key for the requestShipmentDiversion endpoint in ghc
const GhcRequestShipmentDiversionEndpointKey = "Ghc.RequestShipmentDiversion"
@@ -180,6 +183,10 @@ var ghcEndpoints = EndpointMapType{
APIName: GhcAPIName,
OperationID: "approveShipment",
},
+ GhcApproveShipmentsEndpointKey: {
+ APIName: GhcAPIName,
+ OperationID: "approveShipments",
+ },
GhcRequestShipmentDiversionEndpointKey: {
APIName: GhcAPIName,
OperationID: "requestShipmentDiversion",
diff --git a/pkg/services/event/notification_test.go b/pkg/services/event/notification_test.go
index eea593eba9a..ec669b81daf 100644
--- a/pkg/services/event/notification_test.go
+++ b/pkg/services/event/notification_test.go
@@ -200,7 +200,7 @@ func (suite *EventServiceSuite) Test_MTOServiceItemPayload() {
},
},
}, nil)
- data := &primemessages.MTOServiceItemShuttle{}
+ data := &primemessages.MTOServiceItemDomesticShuttle{}
payload, assemblePayloadErr := assembleMTOServiceItemPayload(suite.AppContextForTest(), mtoServiceItemDOSHUT.ID)
diff --git a/pkg/services/ghc_rate_engine.go b/pkg/services/ghc_rate_engine.go
index 2aa23954ce6..d1c7b923118 100644
--- a/pkg/services/ghc_rate_engine.go
+++ b/pkg/services/ghc_rate_engine.go
@@ -280,3 +280,35 @@ type IntlPortFuelSurchargePricer interface {
Price(appCtx appcontext.AppContext, actualPickupDate time.Time, distance unit.Miles, weight unit.Pound, fscWeightBasedDistanceMultiplier float64, eiaFuelPrice unit.Millicents) (unit.Cents, PricingDisplayParams, error)
ParamsPricer
}
+
+// IntlOriginFirstDaySITPricer prices international origin first day SIT
+//
+//go:generate mockery --name IntlOriginFirstDaySITPricer
+type IntlOriginFirstDaySITPricer interface {
+ Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, weight unit.Pound, perUnitCents int) (unit.Cents, PricingDisplayParams, error)
+ ParamsPricer
+}
+
+// IntlOriginAdditionalDaySITPricer prices international origin additional days of SIT
+//
+//go:generate mockery --name IntlOriginAdditionalDaySITPricer
+type IntlOriginAdditionalDaySITPricer interface {
+ Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, numberOfDaysInSIT int, weight unit.Pound, perUnitCents int) (unit.Cents, PricingDisplayParams, error)
+ ParamsPricer
+}
+
+// IntlDestinationFirstDaySITPricer prices international destination first day SIT
+//
+//go:generate mockery --name IntlDestinationFirstDaySITPricer
+type IntlDestinationFirstDaySITPricer interface {
+ Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, weight unit.Pound, perUnitCents int) (unit.Cents, PricingDisplayParams, error)
+ ParamsPricer
+}
+
+// IntlDestinationAdditionalDaySITPricer prices international destination additional days of SIT
+//
+//go:generate mockery --name IntlDestinationAdditionalDaySITPricer
+type IntlDestinationAdditionalDaySITPricer interface {
+ Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, numberOfDaysInSIT int, weight unit.Pound, perUnitCents int) (unit.Cents, PricingDisplayParams, error)
+ ParamsPricer
+}
diff --git a/pkg/services/ghcrateengine/intl_destination_additional_days_sit_pricer.go b/pkg/services/ghcrateengine/intl_destination_additional_days_sit_pricer.go
new file mode 100644
index 00000000000..ed57a28b57c
--- /dev/null
+++ b/pkg/services/ghcrateengine/intl_destination_additional_days_sit_pricer.go
@@ -0,0 +1,50 @@
+package ghcrateengine
+
+import (
+ "time"
+
+ "github.com/transcom/mymove/pkg/appcontext"
+ "github.com/transcom/mymove/pkg/models"
+ "github.com/transcom/mymove/pkg/services"
+ "github.com/transcom/mymove/pkg/unit"
+)
+
+type intlDestinationAdditionalDaySITPricer struct {
+}
+
+func NewIntlDestinationAdditionalDaySITPricer() services.IntlDestinationAdditionalDaySITPricer {
+ return &intlDestinationAdditionalDaySITPricer{}
+}
+
+func (p intlDestinationAdditionalDaySITPricer) Price(appCtx appcontext.AppContext, contractCode string, referenceDate time.Time, numberOfDaysInSIT int, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) {
+ return priceIntlAdditionalDaySIT(appCtx, models.ReServiceCodeIDASIT, contractCode, referenceDate, numberOfDaysInSIT, weight, perUnitCents)
+}
+
+func (p intlDestinationAdditionalDaySITPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) {
+ contractCode, err := getParamString(params, models.ServiceItemParamNameContractCode)
+ if err != nil {
+ return unit.Cents(0), nil, err
+ }
+
+ referenceDate, err := getParamTime(params, models.ServiceItemParamNameReferenceDate)
+ if err != nil {
+ return unit.Cents(0), nil, err
+ }
+
+ numberOfDaysInSIT, err := getParamInt(params, models.ServiceItemParamNameNumberDaysSIT)
+ if err != nil {
+ return unit.Cents(0), nil, err
+ }
+
+ perUnitCents, err := getParamInt(params, models.ServiceItemParamNamePerUnitCents)
+ if err != nil {
+ return unit.Cents(0), nil, err
+ }
+
+ weightBilled, err := getParamInt(params, models.ServiceItemParamNameWeightBilled)
+ if err != nil {
+ return unit.Cents(0), nil, err
+ }
+
+ return p.Price(appCtx, contractCode, referenceDate, numberOfDaysInSIT, unit.Pound(weightBilled), perUnitCents)
+}
diff --git a/pkg/services/ghcrateengine/intl_destination_additional_days_sit_pricer_test.go b/pkg/services/ghcrateengine/intl_destination_additional_days_sit_pricer_test.go
new file mode 100644
index 00000000000..76a40753891
--- /dev/null
+++ b/pkg/services/ghcrateengine/intl_destination_additional_days_sit_pricer_test.go
@@ -0,0 +1,127 @@
+package ghcrateengine
+
+import (
+ "fmt"
+ "strconv"
+ "time"
+
+ "github.com/transcom/mymove/pkg/factory"
+ "github.com/transcom/mymove/pkg/models"
+ "github.com/transcom/mymove/pkg/services"
+ "github.com/transcom/mymove/pkg/testdatagen"
+ "github.com/transcom/mymove/pkg/unit"
+)
+
+const (
+ idasitTestContractYearName = "Base Period Year 1"
+ idasitTestPerUnitCents = unit.Cents(15000)
+ idasitTestTotalCost = unit.Cents(1575000)
+ idasitTestIsPeakPeriod = true
+ idasitTestEscalationCompounded = 1.0000
+ idasitTestWeight = unit.Pound(2100)
+ idasitTestPriceCents = unit.Cents(500)
+ idasitNumerDaysInSIT = 5
+)
+
+var idasitTestRequestedPickupDate = time.Date(testdatagen.TestYear, peakStart.month, peakStart.day, 0, 0, 0, 0, time.UTC)
+
+func (suite *GHCRateEngineServiceSuite) TestIntlDestinationAdditionalDaySITPricer() {
+ pricer := NewIntlDestinationAdditionalDaySITPricer()
+
+ suite.Run("success using PaymentServiceItemParams", func() {
+ paymentServiceItem := suite.setupIntlDestinationAdditionalDayServiceItem()
+
+ totalCost, displayParams, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams)
+ suite.NoError(err)
+ suite.Equal(idasitTestTotalCost, totalCost)
+
+ expectedParams := services.PricingDisplayParams{
+ {Key: models.ServiceItemParamNameContractYearName, Value: idasitTestContractYearName},
+ {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(idasitTestPerUnitCents)},
+ {Key: models.ServiceItemParamNameIsPeak, Value: FormatBool(idasitTestIsPeakPeriod)},
+ {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(idasitTestEscalationCompounded)},
+ }
+ suite.validatePricerCreatedParams(expectedParams, displayParams)
+ })
+
+ suite.Run("invalid parameters to PriceUsingParams", func() {
+ paymentServiceItem := suite.setupIntlDestinationAdditionalDayServiceItem()
+
+ // WeightBilled
+ paymentServiceItem.PaymentServiceItemParams[4].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean
+ _, _, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams)
+ suite.Error(err)
+ suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to an int", models.ServiceItemParamNameWeightBilled))
+
+ // PerUnitCents
+ paymentServiceItem.PaymentServiceItemParams[3].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean
+ _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams)
+ suite.Error(err)
+ suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to an int", models.ServiceItemParamNamePerUnitCents))
+
+ // NumberDaysSIT
+ paymentServiceItem.PaymentServiceItemParams[2].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean
+ _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams)
+ suite.Error(err)
+ suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to an int", models.ServiceItemParamNameNumberDaysSIT))
+
+ // ReferenceDate
+ paymentServiceItem.PaymentServiceItemParams[1].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean
+ _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams)
+ suite.Error(err)
+ suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to a time", models.ServiceItemParamNameReferenceDate))
+
+ // ContractCode
+ paymentServiceItem.PaymentServiceItemParams[0].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean
+ _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams)
+ suite.Error(err)
+ suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to a string", models.ServiceItemParamNameContractCode))
+ })
+}
+
+func (suite *GHCRateEngineServiceSuite) setupIntlDestinationAdditionalDayServiceItem() models.PaymentServiceItem {
+ contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{})
+ startDate := time.Date(2018, time.January, 1, 12, 0, 0, 0, time.UTC)
+ endDate := time.Date(2018, time.December, 31, 12, 0, 0, 0, time.UTC)
+ testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{
+ ReContractYear: models.ReContractYear{
+ Contract: contract,
+ ContractID: contract.ID,
+ StartDate: startDate,
+ EndDate: endDate,
+ Escalation: 1.0,
+ EscalationCompounded: 1.0,
+ },
+ })
+ return factory.BuildPaymentServiceItemWithParams(
+ suite.DB(),
+ models.ReServiceCodeIDASIT,
+ []factory.CreatePaymentServiceItemParams{
+ {
+ Key: models.ServiceItemParamNameContractCode,
+ KeyType: models.ServiceItemParamTypeString,
+ Value: contract.Code,
+ },
+ {
+ Key: models.ServiceItemParamNameReferenceDate,
+ KeyType: models.ServiceItemParamTypeDate,
+ Value: idasitTestRequestedPickupDate.Format(DateParamFormat),
+ },
+ {
+ Key: models.ServiceItemParamNameNumberDaysSIT,
+ KeyType: models.ServiceItemParamTypeInteger,
+ Value: fmt.Sprintf("%d", int(idasitNumerDaysInSIT)),
+ },
+ {
+ Key: models.ServiceItemParamNamePerUnitCents,
+ KeyType: models.ServiceItemParamTypeInteger,
+ Value: fmt.Sprintf("%d", int(idasitTestPerUnitCents)),
+ },
+ {
+ Key: models.ServiceItemParamNameWeightBilled,
+ KeyType: models.ServiceItemParamTypeInteger,
+ Value: strconv.Itoa(idasitTestWeight.Int()),
+ },
+ }, nil, nil,
+ )
+}
diff --git a/pkg/services/ghcrateengine/intl_destination_first_day_sit_pricer.go b/pkg/services/ghcrateengine/intl_destination_first_day_sit_pricer.go
new file mode 100644
index 00000000000..eb1354ebbd0
--- /dev/null
+++ b/pkg/services/ghcrateengine/intl_destination_first_day_sit_pricer.go
@@ -0,0 +1,45 @@
+package ghcrateengine
+
+import (
+ "time"
+
+ "github.com/transcom/mymove/pkg/appcontext"
+ "github.com/transcom/mymove/pkg/models"
+ "github.com/transcom/mymove/pkg/services"
+ "github.com/transcom/mymove/pkg/unit"
+)
+
+type intlDestinationFirstDaySITPricer struct {
+}
+
+func NewIntlDestinationFirstDaySITPricer() services.IntlDestinationFirstDaySITPricer {
+ return &intlDestinationFirstDaySITPricer{}
+}
+
+func (p intlDestinationFirstDaySITPricer) Price(appCtx appcontext.AppContext, contractCode string, referenceDate time.Time, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) {
+ return priceIntlFirstDaySIT(appCtx, models.ReServiceCodeIDFSIT, contractCode, referenceDate, weight, perUnitCents)
+}
+
+func (p intlDestinationFirstDaySITPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) {
+ contractCode, err := getParamString(params, models.ServiceItemParamNameContractCode)
+ if err != nil {
+ return unit.Cents(0), nil, err
+ }
+
+ referenceDate, err := getParamTime(params, models.ServiceItemParamNameReferenceDate)
+ if err != nil {
+ return unit.Cents(0), nil, err
+ }
+
+ perUnitCents, err := getParamInt(params, models.ServiceItemParamNamePerUnitCents)
+ if err != nil {
+ return unit.Cents(0), nil, err
+ }
+
+ weightBilled, err := getParamInt(params, models.ServiceItemParamNameWeightBilled)
+ if err != nil {
+ return unit.Cents(0), nil, err
+ }
+
+ return p.Price(appCtx, contractCode, referenceDate, unit.Pound(weightBilled), perUnitCents)
+}
diff --git a/pkg/services/ghcrateengine/intl_destination_first_day_sit_pricer_test.go b/pkg/services/ghcrateengine/intl_destination_first_day_sit_pricer_test.go
new file mode 100644
index 00000000000..fbaebec2df0
--- /dev/null
+++ b/pkg/services/ghcrateengine/intl_destination_first_day_sit_pricer_test.go
@@ -0,0 +1,116 @@
+package ghcrateengine
+
+import (
+ "fmt"
+ "strconv"
+ "time"
+
+ "github.com/transcom/mymove/pkg/factory"
+ "github.com/transcom/mymove/pkg/models"
+ "github.com/transcom/mymove/pkg/services"
+ "github.com/transcom/mymove/pkg/testdatagen"
+ "github.com/transcom/mymove/pkg/unit"
+)
+
+const (
+ idfsitTestContractYearName = "Base Period Year 1"
+ idfsitTestPerUnitCents = unit.Cents(15000)
+ idfsitTestTotalCost = unit.Cents(315000)
+ idfsitTestIsPeakPeriod = true
+ idfsitTestEscalationCompounded = 1.0000
+ idfsitTestWeight = unit.Pound(2100)
+ idfsitTestPriceCents = unit.Cents(500)
+ idfsitNumerDaysInSIT = 5
+)
+
+var idfsitTestRequestedPickupDate = time.Date(testdatagen.TestYear, peakStart.month, peakStart.day, 0, 0, 0, 0, time.UTC)
+
+func (suite *GHCRateEngineServiceSuite) TestIntlDestinationFirstDaySITPricer() {
+ pricer := NewIntlDestinationFirstDaySITPricer()
+
+ suite.Run("success using PaymentServiceItemParams", func() {
+ paymentServiceItem := suite.setupIntlDestinationFirstDayServiceItem()
+
+ totalCost, displayParams, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams)
+ suite.NoError(err)
+ suite.Equal(idfsitTestTotalCost, totalCost)
+
+ expectedParams := services.PricingDisplayParams{
+ {Key: models.ServiceItemParamNameContractYearName, Value: idfsitTestContractYearName},
+ {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(idfsitTestPerUnitCents)},
+ {Key: models.ServiceItemParamNameIsPeak, Value: FormatBool(idfsitTestIsPeakPeriod)},
+ {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(idfsitTestEscalationCompounded)},
+ }
+ suite.validatePricerCreatedParams(expectedParams, displayParams)
+ })
+
+ suite.Run("invalid parameters to PriceUsingParams", func() {
+ paymentServiceItem := suite.setupIntlDestinationFirstDayServiceItem()
+
+ // WeightBilled
+ paymentServiceItem.PaymentServiceItemParams[3].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean
+ _, _, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams)
+ suite.Error(err)
+ suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to an int", models.ServiceItemParamNameWeightBilled))
+
+ // PerUnitCents
+ paymentServiceItem.PaymentServiceItemParams[2].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean
+ _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams)
+ suite.Error(err)
+ suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to an int", models.ServiceItemParamNamePerUnitCents))
+
+ // ReferenceDate
+ paymentServiceItem.PaymentServiceItemParams[1].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean
+ _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams)
+ suite.Error(err)
+ suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to a time", models.ServiceItemParamNameReferenceDate))
+
+ // ContractCode
+ paymentServiceItem.PaymentServiceItemParams[0].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean
+ _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams)
+ suite.Error(err)
+ suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to a string", models.ServiceItemParamNameContractCode))
+ })
+}
+
+func (suite *GHCRateEngineServiceSuite) setupIntlDestinationFirstDayServiceItem() models.PaymentServiceItem {
+ contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{})
+ startDate := time.Date(2018, time.January, 1, 12, 0, 0, 0, time.UTC)
+ endDate := time.Date(2018, time.December, 31, 12, 0, 0, 0, time.UTC)
+ testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{
+ ReContractYear: models.ReContractYear{
+ Contract: contract,
+ ContractID: contract.ID,
+ StartDate: startDate,
+ EndDate: endDate,
+ Escalation: 1.0,
+ EscalationCompounded: 1.0,
+ },
+ })
+ return factory.BuildPaymentServiceItemWithParams(
+ suite.DB(),
+ models.ReServiceCodeIDFSIT,
+ []factory.CreatePaymentServiceItemParams{
+ {
+ Key: models.ServiceItemParamNameContractCode,
+ KeyType: models.ServiceItemParamTypeString,
+ Value: contract.Code,
+ },
+ {
+ Key: models.ServiceItemParamNameReferenceDate,
+ KeyType: models.ServiceItemParamTypeDate,
+ Value: idfsitTestRequestedPickupDate.Format(DateParamFormat),
+ },
+ {
+ Key: models.ServiceItemParamNamePerUnitCents,
+ KeyType: models.ServiceItemParamTypeInteger,
+ Value: fmt.Sprintf("%d", int(idfsitTestPerUnitCents)),
+ },
+ {
+ Key: models.ServiceItemParamNameWeightBilled,
+ KeyType: models.ServiceItemParamTypeInteger,
+ Value: strconv.Itoa(idfsitTestWeight.Int()),
+ },
+ }, nil, nil,
+ )
+}
diff --git a/pkg/services/ghcrateengine/intl_origin_additional_days_sit_pricer.go b/pkg/services/ghcrateengine/intl_origin_additional_days_sit_pricer.go
new file mode 100644
index 00000000000..e9b4dc22478
--- /dev/null
+++ b/pkg/services/ghcrateengine/intl_origin_additional_days_sit_pricer.go
@@ -0,0 +1,50 @@
+package ghcrateengine
+
+import (
+ "time"
+
+ "github.com/transcom/mymove/pkg/appcontext"
+ "github.com/transcom/mymove/pkg/models"
+ "github.com/transcom/mymove/pkg/services"
+ "github.com/transcom/mymove/pkg/unit"
+)
+
+type intlOriginAdditionalDaySITPricer struct {
+}
+
+func NewIntlOriginAdditionalDaySITPricer() services.IntlOriginAdditionalDaySITPricer {
+ return &intlOriginAdditionalDaySITPricer{}
+}
+
+func (p intlOriginAdditionalDaySITPricer) Price(appCtx appcontext.AppContext, contractCode string, referenceDate time.Time, numberOfDaysInSIT int, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) {
+ return priceIntlAdditionalDaySIT(appCtx, models.ReServiceCodeIOASIT, contractCode, referenceDate, numberOfDaysInSIT, weight, perUnitCents)
+}
+
+func (p intlOriginAdditionalDaySITPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) {
+ contractCode, err := getParamString(params, models.ServiceItemParamNameContractCode)
+ if err != nil {
+ return unit.Cents(0), nil, err
+ }
+
+ referenceDate, err := getParamTime(params, models.ServiceItemParamNameReferenceDate)
+ if err != nil {
+ return unit.Cents(0), nil, err
+ }
+
+ numberOfDaysInSIT, err := getParamInt(params, models.ServiceItemParamNameNumberDaysSIT)
+ if err != nil {
+ return unit.Cents(0), nil, err
+ }
+
+ perUnitCents, err := getParamInt(params, models.ServiceItemParamNamePerUnitCents)
+ if err != nil {
+ return unit.Cents(0), nil, err
+ }
+
+ weightBilled, err := getParamInt(params, models.ServiceItemParamNameWeightBilled)
+ if err != nil {
+ return unit.Cents(0), nil, err
+ }
+
+ return p.Price(appCtx, contractCode, referenceDate, numberOfDaysInSIT, unit.Pound(weightBilled), perUnitCents)
+}
diff --git a/pkg/services/ghcrateengine/intl_origin_additional_days_sit_pricer_test.go b/pkg/services/ghcrateengine/intl_origin_additional_days_sit_pricer_test.go
new file mode 100644
index 00000000000..87686d5110a
--- /dev/null
+++ b/pkg/services/ghcrateengine/intl_origin_additional_days_sit_pricer_test.go
@@ -0,0 +1,127 @@
+package ghcrateengine
+
+import (
+ "fmt"
+ "strconv"
+ "time"
+
+ "github.com/transcom/mymove/pkg/factory"
+ "github.com/transcom/mymove/pkg/models"
+ "github.com/transcom/mymove/pkg/services"
+ "github.com/transcom/mymove/pkg/testdatagen"
+ "github.com/transcom/mymove/pkg/unit"
+)
+
+const (
+ ioasitTestContractYearName = "Base Period Year 1"
+ ioasitTestPerUnitCents = unit.Cents(15000)
+ ioasitTestTotalCost = unit.Cents(1575000)
+ ioasitTestIsPeakPeriod = true
+ ioasitTestEscalationCompounded = 1.0000
+ ioasitTestWeight = unit.Pound(2100)
+ ioasitTestPriceCents = unit.Cents(500)
+ ioasitNumerDaysInSIT = 5
+)
+
+var ioasitTestRequestedPickupDate = time.Date(testdatagen.TestYear, peakStart.month, peakStart.day, 0, 0, 0, 0, time.UTC)
+
+func (suite *GHCRateEngineServiceSuite) TestIntlOriginAdditionalDaySITPricer() {
+ pricer := NewIntlOriginAdditionalDaySITPricer()
+
+ suite.Run("success using PaymentServiceItemParams", func() {
+ paymentServiceItem := suite.setupIntlOriginAdditionalDayServiceItem()
+
+ totalCost, displayParams, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams)
+ suite.NoError(err)
+ suite.Equal(ioasitTestTotalCost, totalCost)
+
+ expectedParams := services.PricingDisplayParams{
+ {Key: models.ServiceItemParamNameContractYearName, Value: ioasitTestContractYearName},
+ {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(ioasitTestPerUnitCents)},
+ {Key: models.ServiceItemParamNameIsPeak, Value: FormatBool(ioasitTestIsPeakPeriod)},
+ {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(ioasitTestEscalationCompounded)},
+ }
+ suite.validatePricerCreatedParams(expectedParams, displayParams)
+ })
+
+ suite.Run("invalid parameters to PriceUsingParams", func() {
+ paymentServiceItem := suite.setupIntlOriginAdditionalDayServiceItem()
+
+ // WeightBilled
+ paymentServiceItem.PaymentServiceItemParams[4].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean
+ _, _, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams)
+ suite.Error(err)
+ suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to an int", models.ServiceItemParamNameWeightBilled))
+
+ // PerUnitCents
+ paymentServiceItem.PaymentServiceItemParams[3].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean
+ _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams)
+ suite.Error(err)
+ suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to an int", models.ServiceItemParamNamePerUnitCents))
+
+ // NumberDaysSIT
+ paymentServiceItem.PaymentServiceItemParams[2].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean
+ _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams)
+ suite.Error(err)
+ suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to an int", models.ServiceItemParamNameNumberDaysSIT))
+
+ // ReferenceDate
+ paymentServiceItem.PaymentServiceItemParams[1].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean
+ _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams)
+ suite.Error(err)
+ suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to a time", models.ServiceItemParamNameReferenceDate))
+
+ // ContractCode
+ paymentServiceItem.PaymentServiceItemParams[0].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean
+ _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams)
+ suite.Error(err)
+ suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to a string", models.ServiceItemParamNameContractCode))
+ })
+}
+
+func (suite *GHCRateEngineServiceSuite) setupIntlOriginAdditionalDayServiceItem() models.PaymentServiceItem {
+ contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{})
+ startDate := time.Date(2018, time.January, 1, 12, 0, 0, 0, time.UTC)
+ endDate := time.Date(2018, time.December, 31, 12, 0, 0, 0, time.UTC)
+ testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{
+ ReContractYear: models.ReContractYear{
+ Contract: contract,
+ ContractID: contract.ID,
+ StartDate: startDate,
+ EndDate: endDate,
+ Escalation: 1.0,
+ EscalationCompounded: 1.0,
+ },
+ })
+ return factory.BuildPaymentServiceItemWithParams(
+ suite.DB(),
+ models.ReServiceCodeIOASIT,
+ []factory.CreatePaymentServiceItemParams{
+ {
+ Key: models.ServiceItemParamNameContractCode,
+ KeyType: models.ServiceItemParamTypeString,
+ Value: contract.Code,
+ },
+ {
+ Key: models.ServiceItemParamNameReferenceDate,
+ KeyType: models.ServiceItemParamTypeDate,
+ Value: ioasitTestRequestedPickupDate.Format(DateParamFormat),
+ },
+ {
+ Key: models.ServiceItemParamNameNumberDaysSIT,
+ KeyType: models.ServiceItemParamTypeInteger,
+ Value: fmt.Sprintf("%d", int(ioasitNumerDaysInSIT)),
+ },
+ {
+ Key: models.ServiceItemParamNamePerUnitCents,
+ KeyType: models.ServiceItemParamTypeInteger,
+ Value: fmt.Sprintf("%d", int(ioasitTestPerUnitCents)),
+ },
+ {
+ Key: models.ServiceItemParamNameWeightBilled,
+ KeyType: models.ServiceItemParamTypeInteger,
+ Value: strconv.Itoa(ioasitTestWeight.Int()),
+ },
+ }, nil, nil,
+ )
+}
diff --git a/pkg/services/ghcrateengine/intl_origin_first_day_sit_pricer.go b/pkg/services/ghcrateengine/intl_origin_first_day_sit_pricer.go
new file mode 100644
index 00000000000..2070d13835b
--- /dev/null
+++ b/pkg/services/ghcrateengine/intl_origin_first_day_sit_pricer.go
@@ -0,0 +1,45 @@
+package ghcrateengine
+
+import (
+ "time"
+
+ "github.com/transcom/mymove/pkg/appcontext"
+ "github.com/transcom/mymove/pkg/models"
+ "github.com/transcom/mymove/pkg/services"
+ "github.com/transcom/mymove/pkg/unit"
+)
+
+type intlOriginFirstDaySITPricer struct {
+}
+
+func NewIntlOriginFirstDaySITPricer() services.IntlOriginFirstDaySITPricer {
+ return &intlOriginFirstDaySITPricer{}
+}
+
+func (p intlOriginFirstDaySITPricer) Price(appCtx appcontext.AppContext, contractCode string, referenceDate time.Time, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) {
+ return priceIntlFirstDaySIT(appCtx, models.ReServiceCodeIOFSIT, contractCode, referenceDate, weight, perUnitCents)
+}
+
+func (p intlOriginFirstDaySITPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) {
+ contractCode, err := getParamString(params, models.ServiceItemParamNameContractCode)
+ if err != nil {
+ return unit.Cents(0), nil, err
+ }
+
+ referenceDate, err := getParamTime(params, models.ServiceItemParamNameReferenceDate)
+ if err != nil {
+ return unit.Cents(0), nil, err
+ }
+
+ perUnitCents, err := getParamInt(params, models.ServiceItemParamNamePerUnitCents)
+ if err != nil {
+ return unit.Cents(0), nil, err
+ }
+
+ weightBilled, err := getParamInt(params, models.ServiceItemParamNameWeightBilled)
+ if err != nil {
+ return unit.Cents(0), nil, err
+ }
+
+ return p.Price(appCtx, contractCode, referenceDate, unit.Pound(weightBilled), perUnitCents)
+}
diff --git a/pkg/services/ghcrateengine/intl_origin_first_day_sit_pricer_test.go b/pkg/services/ghcrateengine/intl_origin_first_day_sit_pricer_test.go
new file mode 100644
index 00000000000..ae6d9069fc4
--- /dev/null
+++ b/pkg/services/ghcrateengine/intl_origin_first_day_sit_pricer_test.go
@@ -0,0 +1,116 @@
+package ghcrateengine
+
+import (
+ "fmt"
+ "strconv"
+ "time"
+
+ "github.com/transcom/mymove/pkg/factory"
+ "github.com/transcom/mymove/pkg/models"
+ "github.com/transcom/mymove/pkg/services"
+ "github.com/transcom/mymove/pkg/testdatagen"
+ "github.com/transcom/mymove/pkg/unit"
+)
+
+const (
+ iofsitTestContractYearName = "Base Period Year 1"
+ iofsitTestPerUnitCents = unit.Cents(15000)
+ iofsitTestTotalCost = unit.Cents(315000)
+ iofsitTestIsPeakPeriod = true
+ iofsitTestEscalationCompounded = 1.0000
+ iofsitTestWeight = unit.Pound(2100)
+ iofsitTestPriceCents = unit.Cents(500)
+ iofsitNumerDaysInSIT = 5
+)
+
+var iofsitTestRequestedPickupDate = time.Date(testdatagen.TestYear, peakStart.month, peakStart.day, 0, 0, 0, 0, time.UTC)
+
+func (suite *GHCRateEngineServiceSuite) TestIntlOriginFirstDaySITPricer() {
+ pricer := NewIntlOriginFirstDaySITPricer()
+
+ suite.Run("success using PaymentServiceItemParams", func() {
+ paymentServiceItem := suite.setupIntlOriginFirstDayServiceItem()
+
+ totalCost, displayParams, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams)
+ suite.NoError(err)
+ suite.Equal(iofsitTestTotalCost, totalCost)
+
+ expectedParams := services.PricingDisplayParams{
+ {Key: models.ServiceItemParamNameContractYearName, Value: iofsitTestContractYearName},
+ {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(iofsitTestPerUnitCents)},
+ {Key: models.ServiceItemParamNameIsPeak, Value: FormatBool(iofsitTestIsPeakPeriod)},
+ {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(iofsitTestEscalationCompounded)},
+ }
+ suite.validatePricerCreatedParams(expectedParams, displayParams)
+ })
+
+ suite.Run("invalid parameters to PriceUsingParams", func() {
+ paymentServiceItem := suite.setupIntlOriginFirstDayServiceItem()
+
+ // WeightBilled
+ paymentServiceItem.PaymentServiceItemParams[3].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean
+ _, _, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams)
+ suite.Error(err)
+ suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to an int", models.ServiceItemParamNameWeightBilled))
+
+ // PerUnitCents
+ paymentServiceItem.PaymentServiceItemParams[2].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean
+ _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams)
+ suite.Error(err)
+ suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to an int", models.ServiceItemParamNamePerUnitCents))
+
+ // ReferenceDate
+ paymentServiceItem.PaymentServiceItemParams[1].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean
+ _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams)
+ suite.Error(err)
+ suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to a time", models.ServiceItemParamNameReferenceDate))
+
+ // ContractCode
+ paymentServiceItem.PaymentServiceItemParams[0].ServiceItemParamKey.Type = models.ServiceItemParamTypeBoolean
+ _, _, err = pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams)
+ suite.Error(err)
+ suite.Contains(err.Error(), fmt.Sprintf("trying to convert %s to a string", models.ServiceItemParamNameContractCode))
+ })
+}
+
+func (suite *GHCRateEngineServiceSuite) setupIntlOriginFirstDayServiceItem() models.PaymentServiceItem {
+ contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{})
+ startDate := time.Date(2018, time.January, 1, 12, 0, 0, 0, time.UTC)
+ endDate := time.Date(2018, time.December, 31, 12, 0, 0, 0, time.UTC)
+ testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{
+ ReContractYear: models.ReContractYear{
+ Contract: contract,
+ ContractID: contract.ID,
+ StartDate: startDate,
+ EndDate: endDate,
+ Escalation: 1.0,
+ EscalationCompounded: 1.0,
+ },
+ })
+ return factory.BuildPaymentServiceItemWithParams(
+ suite.DB(),
+ models.ReServiceCodeIOFSIT,
+ []factory.CreatePaymentServiceItemParams{
+ {
+ Key: models.ServiceItemParamNameContractCode,
+ KeyType: models.ServiceItemParamTypeString,
+ Value: contract.Code,
+ },
+ {
+ Key: models.ServiceItemParamNameReferenceDate,
+ KeyType: models.ServiceItemParamTypeDate,
+ Value: iofsitTestRequestedPickupDate.Format(DateParamFormat),
+ },
+ {
+ Key: models.ServiceItemParamNamePerUnitCents,
+ KeyType: models.ServiceItemParamTypeInteger,
+ Value: fmt.Sprintf("%d", int(iofsitTestPerUnitCents)),
+ },
+ {
+ Key: models.ServiceItemParamNameWeightBilled,
+ KeyType: models.ServiceItemParamTypeInteger,
+ Value: strconv.Itoa(iofsitTestWeight.Int()),
+ },
+ }, nil, nil,
+ )
+}
diff --git a/pkg/services/ghcrateengine/pricer_helpers_intl.go b/pkg/services/ghcrateengine/pricer_helpers_intl.go
index 7d8d70508ea..9754f552b3d 100644
--- a/pkg/services/ghcrateengine/pricer_helpers_intl.go
+++ b/pkg/services/ghcrateengine/pricer_helpers_intl.go
@@ -114,3 +114,111 @@ func priceIntlPackUnpack(appCtx appcontext.AppContext, packUnpackCode models.ReS
return totalCost, displayParams, nil
}
+
+func priceIntlFirstDaySIT(appCtx appcontext.AppContext, firstDaySITCode models.ReServiceCode, contractCode string, referenceDate time.Time, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) {
+ if firstDaySITCode != models.ReServiceCodeIOFSIT && firstDaySITCode != models.ReServiceCodeIDFSIT {
+ return 0, nil, fmt.Errorf("unsupported first day SIT code of %s", firstDaySITCode)
+ }
+ if len(contractCode) == 0 {
+ return 0, nil, errors.New("ContractCode is required")
+ }
+ if referenceDate.IsZero() {
+ return 0, nil, errors.New("ReferenceDate is required")
+ }
+ if perUnitCents == 0 {
+ return 0, nil, errors.New("PerUnitCents is required")
+ }
+
+ isPeakPeriod := IsPeakPeriod(referenceDate)
+
+ contract, err := fetchContractByContractCode(appCtx, contractCode)
+ if err != nil {
+ return 0, nil, fmt.Errorf("could not find contract with code: %s: %w", contractCode, err)
+ }
+
+ basePrice := float64(perUnitCents)
+ escalatedPrice, contractYear, err := escalatePriceForContractYear(appCtx, contract.ID, referenceDate, false, basePrice)
+ if err != nil {
+ return 0, nil, fmt.Errorf("could not calculate escalated price: %w", err)
+ }
+
+ escalatedPrice = escalatedPrice * weight.ToCWTFloat64()
+ totalCost := unit.Cents(math.Round(escalatedPrice))
+
+ displayParams := services.PricingDisplayParams{
+ {
+ Key: models.ServiceItemParamNameContractYearName,
+ Value: contractYear.Name,
+ },
+ {
+ Key: models.ServiceItemParamNamePriceRateOrFactor,
+ Value: FormatCents(unit.Cents(perUnitCents)),
+ },
+ {
+ Key: models.ServiceItemParamNameIsPeak,
+ Value: FormatBool(isPeakPeriod),
+ },
+ {
+ Key: models.ServiceItemParamNameEscalationCompounded,
+ Value: FormatEscalation(contractYear.EscalationCompounded),
+ },
+ }
+
+ return totalCost, displayParams, nil
+}
+
+func priceIntlAdditionalDaySIT(appCtx appcontext.AppContext, additionalDaySITCode models.ReServiceCode, contractCode string, referenceDate time.Time, numberOfDaysInSIT int, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) {
+ if additionalDaySITCode != models.ReServiceCodeIOASIT && additionalDaySITCode != models.ReServiceCodeIDASIT {
+ return 0, nil, fmt.Errorf("unsupported additional day SIT code of %s", additionalDaySITCode)
+ }
+ if len(contractCode) == 0 {
+ return 0, nil, errors.New("ContractCode is required")
+ }
+ if referenceDate.IsZero() {
+ return 0, nil, errors.New("ReferenceDate is required")
+ }
+ if numberOfDaysInSIT == 0 {
+ return 0, nil, errors.New("NumberDaysSIT is required")
+ }
+ if perUnitCents == 0 {
+ return 0, nil, errors.New("PerUnitCents is required")
+ }
+
+ isPeakPeriod := IsPeakPeriod(referenceDate)
+
+ contract, err := fetchContractByContractCode(appCtx, contractCode)
+ if err != nil {
+ return 0, nil, fmt.Errorf("could not find contract with code: %s: %w", contractCode, err)
+ }
+
+ basePrice := float64(perUnitCents)
+ escalatedPrice, contractYear, err := escalatePriceForContractYear(appCtx, contract.ID, referenceDate, false, basePrice)
+ if err != nil {
+ return 0, nil, fmt.Errorf("could not calculate escalated price: %w", err)
+ }
+
+ escalatedPrice = escalatedPrice * weight.ToCWTFloat64()
+ totalForNumberOfDaysPrice := escalatedPrice * float64(numberOfDaysInSIT)
+ totalCost := unit.Cents(math.Round(totalForNumberOfDaysPrice))
+
+ displayParams := services.PricingDisplayParams{
+ {
+ Key: models.ServiceItemParamNameContractYearName,
+ Value: contractYear.Name,
+ },
+ {
+ Key: models.ServiceItemParamNamePriceRateOrFactor,
+ Value: FormatCents(unit.Cents(perUnitCents)),
+ },
+ {
+ Key: models.ServiceItemParamNameIsPeak,
+ Value: FormatBool(isPeakPeriod),
+ },
+ {
+ Key: models.ServiceItemParamNameEscalationCompounded,
+ Value: FormatEscalation(contractYear.EscalationCompounded),
+ },
+ }
+
+ return totalCost, displayParams, nil
+}
diff --git a/pkg/services/ghcrateengine/pricer_helpers_intl_test.go b/pkg/services/ghcrateengine/pricer_helpers_intl_test.go
index 56d5bcce1dc..14e3d6c8618 100644
--- a/pkg/services/ghcrateengine/pricer_helpers_intl_test.go
+++ b/pkg/services/ghcrateengine/pricer_helpers_intl_test.go
@@ -99,3 +99,109 @@ func (suite *GHCRateEngineServiceSuite) TestPriceIntlPackUnpack() {
})
}
+
+func (suite *GHCRateEngineServiceSuite) TestPriceIntlFirstDaySIT() {
+ suite.Run("success with IDFSIT", func() {
+ suite.setupIntlDestinationFirstDayServiceItem()
+ totalCost, displayParams, err := priceIntlFirstDaySIT(suite.AppContextForTest(), models.ReServiceCodeIDFSIT, testdatagen.DefaultContractCode, idfsitTestRequestedPickupDate, idfsitTestWeight, idfsitTestPerUnitCents.Int())
+ suite.NoError(err)
+ suite.Equal(idfsitTestTotalCost, totalCost)
+
+ expectedParams := services.PricingDisplayParams{
+ {Key: models.ServiceItemParamNameContractYearName, Value: idfsitTestContractYearName},
+ {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(idfsitTestEscalationCompounded)},
+ {Key: models.ServiceItemParamNameIsPeak, Value: FormatBool(idfsitTestIsPeakPeriod)},
+ {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(idfsitTestPerUnitCents)},
+ }
+ suite.validatePricerCreatedParams(expectedParams, displayParams)
+ })
+
+ suite.Run("success with IOFSIT", func() {
+ suite.setupIntlOriginFirstDayServiceItem()
+ totalCost, displayParams, err := priceIntlFirstDaySIT(suite.AppContextForTest(), models.ReServiceCodeIOFSIT, testdatagen.DefaultContractCode, iofsitTestRequestedPickupDate, iofsitTestWeight, iofsitTestPerUnitCents.Int())
+ suite.NoError(err)
+ suite.Equal(iofsitTestTotalCost, totalCost)
+
+ expectedParams := services.PricingDisplayParams{
+ {Key: models.ServiceItemParamNameContractYearName, Value: iofsitTestContractYearName},
+ {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(iofsitTestEscalationCompounded)},
+ {Key: models.ServiceItemParamNameIsPeak, Value: FormatBool(iofsitTestIsPeakPeriod)},
+ {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(iofsitTestPerUnitCents)},
+ }
+ suite.validatePricerCreatedParams(expectedParams, displayParams)
+ })
+
+ suite.Run("Invalid parameters to Price", func() {
+ suite.setupIntlDestinationFirstDayServiceItem()
+ _, _, err := priceIntlFirstDaySIT(suite.AppContextForTest(), models.ReServiceCodeDLH, testdatagen.DefaultContractCode, idfsitTestRequestedPickupDate, idfsitTestWeight, idfsitTestPerUnitCents.Int())
+ suite.Error(err)
+ suite.Contains(err.Error(), "unsupported first day SIT code")
+
+ _, _, err = priceIntlFirstDaySIT(suite.AppContextForTest(), models.ReServiceCodeIDFSIT, "", idfsitTestRequestedPickupDate, idfsitTestWeight, idfsitTestPerUnitCents.Int())
+ suite.Error(err)
+ suite.Contains(err.Error(), "ContractCode is required")
+
+ _, _, err = priceIntlFirstDaySIT(suite.AppContextForTest(), models.ReServiceCodeIDFSIT, testdatagen.DefaultContractCode, time.Time{}, idfsitTestWeight, idfsitTestPerUnitCents.Int())
+ suite.Error(err)
+ suite.Contains(err.Error(), "ReferenceDate is required")
+
+ _, _, err = priceIntlFirstDaySIT(suite.AppContextForTest(), models.ReServiceCodeIDFSIT, testdatagen.DefaultContractCode, idfsitTestRequestedPickupDate, idfsitTestWeight, 0)
+ suite.Error(err)
+ suite.Contains(err.Error(), "PerUnitCents is required")
+ })
+}
+
+func (suite *GHCRateEngineServiceSuite) TestPriceIntlAdditionalDaySIT() {
+ suite.Run("success with IDASIT", func() {
+ suite.setupIntlDestinationAdditionalDayServiceItem()
+ totalCost, displayParams, err := priceIntlAdditionalDaySIT(suite.AppContextForTest(), models.ReServiceCodeIDASIT, testdatagen.DefaultContractCode, idasitTestRequestedPickupDate, idasitNumerDaysInSIT, idasitTestWeight, idasitTestPerUnitCents.Int())
+ suite.NoError(err)
+ suite.Equal(idasitTestTotalCost, totalCost)
+
+ expectedParams := services.PricingDisplayParams{
+ {Key: models.ServiceItemParamNameContractYearName, Value: idasitTestContractYearName},
+ {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(idasitTestEscalationCompounded)},
+ {Key: models.ServiceItemParamNameIsPeak, Value: FormatBool(idasitTestIsPeakPeriod)},
+ {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(idasitTestPerUnitCents)},
+ }
+ suite.validatePricerCreatedParams(expectedParams, displayParams)
+ })
+
+ suite.Run("success with IOASIT", func() {
+ suite.setupIntlOriginAdditionalDayServiceItem()
+ totalCost, displayParams, err := priceIntlAdditionalDaySIT(suite.AppContextForTest(), models.ReServiceCodeIOASIT, testdatagen.DefaultContractCode, ioasitTestRequestedPickupDate, idasitNumerDaysInSIT, ioasitTestWeight, ioasitTestPerUnitCents.Int())
+ suite.NoError(err)
+ suite.Equal(ioasitTestTotalCost, totalCost)
+
+ expectedParams := services.PricingDisplayParams{
+ {Key: models.ServiceItemParamNameContractYearName, Value: ioasitTestContractYearName},
+ {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(ioasitTestEscalationCompounded)},
+ {Key: models.ServiceItemParamNameIsPeak, Value: FormatBool(ioasitTestIsPeakPeriod)},
+ {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(ioasitTestPerUnitCents)},
+ }
+ suite.validatePricerCreatedParams(expectedParams, displayParams)
+ })
+
+ suite.Run("Invalid parameters to Price", func() {
+ suite.setupIntlDestinationAdditionalDayServiceItem()
+ _, _, err := priceIntlAdditionalDaySIT(suite.AppContextForTest(), models.ReServiceCodeDLH, testdatagen.DefaultContractCode, idasitTestRequestedPickupDate, idasitNumerDaysInSIT, idasitTestWeight, idasitTestPerUnitCents.Int())
+ suite.Error(err)
+ suite.Contains(err.Error(), "unsupported additional day SIT code")
+
+ _, _, err = priceIntlAdditionalDaySIT(suite.AppContextForTest(), models.ReServiceCodeIDASIT, "", idasitTestRequestedPickupDate, idasitNumerDaysInSIT, idasitTestWeight, idasitTestPerUnitCents.Int())
+ suite.Error(err)
+ suite.Contains(err.Error(), "ContractCode is required")
+
+ _, _, err = priceIntlAdditionalDaySIT(suite.AppContextForTest(), models.ReServiceCodeIDASIT, testdatagen.DefaultContractCode, time.Time{}, idasitNumerDaysInSIT, idasitTestWeight, idasitTestPerUnitCents.Int())
+ suite.Error(err)
+ suite.Contains(err.Error(), "ReferenceDate is required")
+
+ _, _, err = priceIntlAdditionalDaySIT(suite.AppContextForTest(), models.ReServiceCodeIDASIT, testdatagen.DefaultContractCode, idasitTestRequestedPickupDate, idasitNumerDaysInSIT, idasitTestWeight, 0)
+ suite.Error(err)
+ suite.Contains(err.Error(), "PerUnitCents is required")
+
+ _, _, err = priceIntlAdditionalDaySIT(suite.AppContextForTest(), models.ReServiceCodeIDASIT, testdatagen.DefaultContractCode, idasitTestRequestedPickupDate, 0, idasitTestWeight, 0)
+ suite.Error(err)
+ suite.Contains(err.Error(), "NumberDaysSIT is required")
+ })
+}
diff --git a/pkg/services/ghcrateengine/service_item_pricer.go b/pkg/services/ghcrateengine/service_item_pricer.go
index 130c137c7c6..777ca2283bf 100644
--- a/pkg/services/ghcrateengine/service_item_pricer.go
+++ b/pkg/services/ghcrateengine/service_item_pricer.go
@@ -107,6 +107,14 @@ func PricerForServiceItem(serviceCode models.ReServiceCode) (services.ParamsPric
return NewPortFuelSurchargePricer(), nil
case models.ReServiceCodePODFSC:
return NewPortFuelSurchargePricer(), nil
+ case models.ReServiceCodeIOFSIT:
+ return NewIntlOriginFirstDaySITPricer(), nil
+ case models.ReServiceCodeIOASIT:
+ return NewIntlOriginAdditionalDaySITPricer(), nil
+ case models.ReServiceCodeIDFSIT:
+ return NewIntlDestinationFirstDaySITPricer(), nil
+ case models.ReServiceCodeIDASIT:
+ return NewIntlDestinationAdditionalDaySITPricer(), nil
default:
// TODO: We may want a different error type here after all pricers have been implemented
return nil, apperror.NewNotImplementedError(fmt.Sprintf("pricer not found for code %s", serviceCode))
diff --git a/pkg/services/mocks/IntlDestinationAdditionalDaySITPricer.go b/pkg/services/mocks/IntlDestinationAdditionalDaySITPricer.go
new file mode 100644
index 00000000000..7a11b759d7f
--- /dev/null
+++ b/pkg/services/mocks/IntlDestinationAdditionalDaySITPricer.go
@@ -0,0 +1,109 @@
+// 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"
+
+ time "time"
+
+ unit "github.com/transcom/mymove/pkg/unit"
+)
+
+// IntlDestinationAdditionalDaySITPricer is an autogenerated mock type for the IntlDestinationAdditionalDaySITPricer type
+type IntlDestinationAdditionalDaySITPricer struct {
+ mock.Mock
+}
+
+// Price provides a mock function with given fields: appCtx, contractCode, requestedPickupDate, numberOfDaysInSIT, weight, perUnitCents
+func (_m *IntlDestinationAdditionalDaySITPricer) Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, numberOfDaysInSIT int, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) {
+ ret := _m.Called(appCtx, contractCode, requestedPickupDate, numberOfDaysInSIT, weight, perUnitCents)
+
+ if len(ret) == 0 {
+ panic("no return value specified for Price")
+ }
+
+ var r0 unit.Cents
+ var r1 services.PricingDisplayParams
+ var r2 error
+ if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, int, unit.Pound, int) (unit.Cents, services.PricingDisplayParams, error)); ok {
+ return rf(appCtx, contractCode, requestedPickupDate, numberOfDaysInSIT, weight, perUnitCents)
+ }
+ if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, int, unit.Pound, int) unit.Cents); ok {
+ r0 = rf(appCtx, contractCode, requestedPickupDate, numberOfDaysInSIT, weight, perUnitCents)
+ } else {
+ r0 = ret.Get(0).(unit.Cents)
+ }
+
+ if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, time.Time, int, unit.Pound, int) services.PricingDisplayParams); ok {
+ r1 = rf(appCtx, contractCode, requestedPickupDate, numberOfDaysInSIT, weight, perUnitCents)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(services.PricingDisplayParams)
+ }
+ }
+
+ if rf, ok := ret.Get(2).(func(appcontext.AppContext, string, time.Time, int, unit.Pound, int) error); ok {
+ r2 = rf(appCtx, contractCode, requestedPickupDate, numberOfDaysInSIT, weight, perUnitCents)
+ } else {
+ r2 = ret.Error(2)
+ }
+
+ return r0, r1, r2
+}
+
+// PriceUsingParams provides a mock function with given fields: appCtx, params
+func (_m *IntlDestinationAdditionalDaySITPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) {
+ ret := _m.Called(appCtx, params)
+
+ if len(ret) == 0 {
+ panic("no return value specified for PriceUsingParams")
+ }
+
+ var r0 unit.Cents
+ var r1 services.PricingDisplayParams
+ var r2 error
+ if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error)); ok {
+ return rf(appCtx, params)
+ }
+ if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) unit.Cents); ok {
+ r0 = rf(appCtx, params)
+ } else {
+ r0 = ret.Get(0).(unit.Cents)
+ }
+
+ if rf, ok := ret.Get(1).(func(appcontext.AppContext, models.PaymentServiceItemParams) services.PricingDisplayParams); ok {
+ r1 = rf(appCtx, params)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(services.PricingDisplayParams)
+ }
+ }
+
+ if rf, ok := ret.Get(2).(func(appcontext.AppContext, models.PaymentServiceItemParams) error); ok {
+ r2 = rf(appCtx, params)
+ } else {
+ r2 = ret.Error(2)
+ }
+
+ return r0, r1, r2
+}
+
+// NewIntlDestinationAdditionalDaySITPricer creates a new instance of IntlDestinationAdditionalDaySITPricer. 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 NewIntlDestinationAdditionalDaySITPricer(t interface {
+ mock.TestingT
+ Cleanup(func())
+}) *IntlDestinationAdditionalDaySITPricer {
+ mock := &IntlDestinationAdditionalDaySITPricer{}
+ mock.Mock.Test(t)
+
+ t.Cleanup(func() { mock.AssertExpectations(t) })
+
+ return mock
+}
diff --git a/pkg/services/mocks/IntlDestinationFirstDaySITPricer.go b/pkg/services/mocks/IntlDestinationFirstDaySITPricer.go
new file mode 100644
index 00000000000..99df36da131
--- /dev/null
+++ b/pkg/services/mocks/IntlDestinationFirstDaySITPricer.go
@@ -0,0 +1,109 @@
+// 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"
+
+ time "time"
+
+ unit "github.com/transcom/mymove/pkg/unit"
+)
+
+// IntlDestinationFirstDaySITPricer is an autogenerated mock type for the IntlDestinationFirstDaySITPricer type
+type IntlDestinationFirstDaySITPricer struct {
+ mock.Mock
+}
+
+// Price provides a mock function with given fields: appCtx, contractCode, requestedPickupDate, weight, perUnitCents
+func (_m *IntlDestinationFirstDaySITPricer) Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) {
+ ret := _m.Called(appCtx, contractCode, requestedPickupDate, weight, perUnitCents)
+
+ if len(ret) == 0 {
+ panic("no return value specified for Price")
+ }
+
+ var r0 unit.Cents
+ var r1 services.PricingDisplayParams
+ var r2 error
+ if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, unit.Pound, int) (unit.Cents, services.PricingDisplayParams, error)); ok {
+ return rf(appCtx, contractCode, requestedPickupDate, weight, perUnitCents)
+ }
+ if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, unit.Pound, int) unit.Cents); ok {
+ r0 = rf(appCtx, contractCode, requestedPickupDate, weight, perUnitCents)
+ } else {
+ r0 = ret.Get(0).(unit.Cents)
+ }
+
+ if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, time.Time, unit.Pound, int) services.PricingDisplayParams); ok {
+ r1 = rf(appCtx, contractCode, requestedPickupDate, weight, perUnitCents)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(services.PricingDisplayParams)
+ }
+ }
+
+ if rf, ok := ret.Get(2).(func(appcontext.AppContext, string, time.Time, unit.Pound, int) error); ok {
+ r2 = rf(appCtx, contractCode, requestedPickupDate, weight, perUnitCents)
+ } else {
+ r2 = ret.Error(2)
+ }
+
+ return r0, r1, r2
+}
+
+// PriceUsingParams provides a mock function with given fields: appCtx, params
+func (_m *IntlDestinationFirstDaySITPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) {
+ ret := _m.Called(appCtx, params)
+
+ if len(ret) == 0 {
+ panic("no return value specified for PriceUsingParams")
+ }
+
+ var r0 unit.Cents
+ var r1 services.PricingDisplayParams
+ var r2 error
+ if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error)); ok {
+ return rf(appCtx, params)
+ }
+ if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) unit.Cents); ok {
+ r0 = rf(appCtx, params)
+ } else {
+ r0 = ret.Get(0).(unit.Cents)
+ }
+
+ if rf, ok := ret.Get(1).(func(appcontext.AppContext, models.PaymentServiceItemParams) services.PricingDisplayParams); ok {
+ r1 = rf(appCtx, params)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(services.PricingDisplayParams)
+ }
+ }
+
+ if rf, ok := ret.Get(2).(func(appcontext.AppContext, models.PaymentServiceItemParams) error); ok {
+ r2 = rf(appCtx, params)
+ } else {
+ r2 = ret.Error(2)
+ }
+
+ return r0, r1, r2
+}
+
+// NewIntlDestinationFirstDaySITPricer creates a new instance of IntlDestinationFirstDaySITPricer. 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 NewIntlDestinationFirstDaySITPricer(t interface {
+ mock.TestingT
+ Cleanup(func())
+}) *IntlDestinationFirstDaySITPricer {
+ mock := &IntlDestinationFirstDaySITPricer{}
+ mock.Mock.Test(t)
+
+ t.Cleanup(func() { mock.AssertExpectations(t) })
+
+ return mock
+}
diff --git a/pkg/services/mocks/IntlOriginAdditionalDaySITPricer.go b/pkg/services/mocks/IntlOriginAdditionalDaySITPricer.go
new file mode 100644
index 00000000000..a931b2d3879
--- /dev/null
+++ b/pkg/services/mocks/IntlOriginAdditionalDaySITPricer.go
@@ -0,0 +1,109 @@
+// 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"
+
+ time "time"
+
+ unit "github.com/transcom/mymove/pkg/unit"
+)
+
+// IntlOriginAdditionalDaySITPricer is an autogenerated mock type for the IntlOriginAdditionalDaySITPricer type
+type IntlOriginAdditionalDaySITPricer struct {
+ mock.Mock
+}
+
+// Price provides a mock function with given fields: appCtx, contractCode, requestedPickupDate, numberOfDaysInSIT, weight, perUnitCents
+func (_m *IntlOriginAdditionalDaySITPricer) Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, numberOfDaysInSIT int, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) {
+ ret := _m.Called(appCtx, contractCode, requestedPickupDate, numberOfDaysInSIT, weight, perUnitCents)
+
+ if len(ret) == 0 {
+ panic("no return value specified for Price")
+ }
+
+ var r0 unit.Cents
+ var r1 services.PricingDisplayParams
+ var r2 error
+ if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, int, unit.Pound, int) (unit.Cents, services.PricingDisplayParams, error)); ok {
+ return rf(appCtx, contractCode, requestedPickupDate, numberOfDaysInSIT, weight, perUnitCents)
+ }
+ if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, int, unit.Pound, int) unit.Cents); ok {
+ r0 = rf(appCtx, contractCode, requestedPickupDate, numberOfDaysInSIT, weight, perUnitCents)
+ } else {
+ r0 = ret.Get(0).(unit.Cents)
+ }
+
+ if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, time.Time, int, unit.Pound, int) services.PricingDisplayParams); ok {
+ r1 = rf(appCtx, contractCode, requestedPickupDate, numberOfDaysInSIT, weight, perUnitCents)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(services.PricingDisplayParams)
+ }
+ }
+
+ if rf, ok := ret.Get(2).(func(appcontext.AppContext, string, time.Time, int, unit.Pound, int) error); ok {
+ r2 = rf(appCtx, contractCode, requestedPickupDate, numberOfDaysInSIT, weight, perUnitCents)
+ } else {
+ r2 = ret.Error(2)
+ }
+
+ return r0, r1, r2
+}
+
+// PriceUsingParams provides a mock function with given fields: appCtx, params
+func (_m *IntlOriginAdditionalDaySITPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) {
+ ret := _m.Called(appCtx, params)
+
+ if len(ret) == 0 {
+ panic("no return value specified for PriceUsingParams")
+ }
+
+ var r0 unit.Cents
+ var r1 services.PricingDisplayParams
+ var r2 error
+ if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error)); ok {
+ return rf(appCtx, params)
+ }
+ if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) unit.Cents); ok {
+ r0 = rf(appCtx, params)
+ } else {
+ r0 = ret.Get(0).(unit.Cents)
+ }
+
+ if rf, ok := ret.Get(1).(func(appcontext.AppContext, models.PaymentServiceItemParams) services.PricingDisplayParams); ok {
+ r1 = rf(appCtx, params)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(services.PricingDisplayParams)
+ }
+ }
+
+ if rf, ok := ret.Get(2).(func(appcontext.AppContext, models.PaymentServiceItemParams) error); ok {
+ r2 = rf(appCtx, params)
+ } else {
+ r2 = ret.Error(2)
+ }
+
+ return r0, r1, r2
+}
+
+// NewIntlOriginAdditionalDaySITPricer creates a new instance of IntlOriginAdditionalDaySITPricer. 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 NewIntlOriginAdditionalDaySITPricer(t interface {
+ mock.TestingT
+ Cleanup(func())
+}) *IntlOriginAdditionalDaySITPricer {
+ mock := &IntlOriginAdditionalDaySITPricer{}
+ mock.Mock.Test(t)
+
+ t.Cleanup(func() { mock.AssertExpectations(t) })
+
+ return mock
+}
diff --git a/pkg/services/mocks/IntlOriginFirstDaySITPricer.go b/pkg/services/mocks/IntlOriginFirstDaySITPricer.go
new file mode 100644
index 00000000000..d36cb1e92c9
--- /dev/null
+++ b/pkg/services/mocks/IntlOriginFirstDaySITPricer.go
@@ -0,0 +1,109 @@
+// 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"
+
+ time "time"
+
+ unit "github.com/transcom/mymove/pkg/unit"
+)
+
+// IntlOriginFirstDaySITPricer is an autogenerated mock type for the IntlOriginFirstDaySITPricer type
+type IntlOriginFirstDaySITPricer struct {
+ mock.Mock
+}
+
+// Price provides a mock function with given fields: appCtx, contractCode, requestedPickupDate, weight, perUnitCents
+func (_m *IntlOriginFirstDaySITPricer) Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, weight unit.Pound, perUnitCents int) (unit.Cents, services.PricingDisplayParams, error) {
+ ret := _m.Called(appCtx, contractCode, requestedPickupDate, weight, perUnitCents)
+
+ if len(ret) == 0 {
+ panic("no return value specified for Price")
+ }
+
+ var r0 unit.Cents
+ var r1 services.PricingDisplayParams
+ var r2 error
+ if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, unit.Pound, int) (unit.Cents, services.PricingDisplayParams, error)); ok {
+ return rf(appCtx, contractCode, requestedPickupDate, weight, perUnitCents)
+ }
+ if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, unit.Pound, int) unit.Cents); ok {
+ r0 = rf(appCtx, contractCode, requestedPickupDate, weight, perUnitCents)
+ } else {
+ r0 = ret.Get(0).(unit.Cents)
+ }
+
+ if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, time.Time, unit.Pound, int) services.PricingDisplayParams); ok {
+ r1 = rf(appCtx, contractCode, requestedPickupDate, weight, perUnitCents)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(services.PricingDisplayParams)
+ }
+ }
+
+ if rf, ok := ret.Get(2).(func(appcontext.AppContext, string, time.Time, unit.Pound, int) error); ok {
+ r2 = rf(appCtx, contractCode, requestedPickupDate, weight, perUnitCents)
+ } else {
+ r2 = ret.Error(2)
+ }
+
+ return r0, r1, r2
+}
+
+// PriceUsingParams provides a mock function with given fields: appCtx, params
+func (_m *IntlOriginFirstDaySITPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) {
+ ret := _m.Called(appCtx, params)
+
+ if len(ret) == 0 {
+ panic("no return value specified for PriceUsingParams")
+ }
+
+ var r0 unit.Cents
+ var r1 services.PricingDisplayParams
+ var r2 error
+ if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error)); ok {
+ return rf(appCtx, params)
+ }
+ if rf, ok := ret.Get(0).(func(appcontext.AppContext, models.PaymentServiceItemParams) unit.Cents); ok {
+ r0 = rf(appCtx, params)
+ } else {
+ r0 = ret.Get(0).(unit.Cents)
+ }
+
+ if rf, ok := ret.Get(1).(func(appcontext.AppContext, models.PaymentServiceItemParams) services.PricingDisplayParams); ok {
+ r1 = rf(appCtx, params)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(services.PricingDisplayParams)
+ }
+ }
+
+ if rf, ok := ret.Get(2).(func(appcontext.AppContext, models.PaymentServiceItemParams) error); ok {
+ r2 = rf(appCtx, params)
+ } else {
+ r2 = ret.Error(2)
+ }
+
+ return r0, r1, r2
+}
+
+// NewIntlOriginFirstDaySITPricer creates a new instance of IntlOriginFirstDaySITPricer. 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 NewIntlOriginFirstDaySITPricer(t interface {
+ mock.TestingT
+ Cleanup(func())
+}) *IntlOriginFirstDaySITPricer {
+ mock := &IntlOriginFirstDaySITPricer{}
+ mock.Mock.Test(t)
+
+ t.Cleanup(func() { mock.AssertExpectations(t) })
+
+ return mock
+}
diff --git a/pkg/services/mocks/MoveTaskOrderUpdater.go b/pkg/services/mocks/MoveTaskOrderUpdater.go
index 8eeaa09fcf3..ec3a1176939 100644
--- a/pkg/services/mocks/MoveTaskOrderUpdater.go
+++ b/pkg/services/mocks/MoveTaskOrderUpdater.go
@@ -16,12 +16,12 @@ type MoveTaskOrderUpdater struct {
mock.Mock
}
-// MakeAvailableToPrime provides a mock function with given fields: appCtx, moveTaskOrderID, eTag, includeServiceCodeMS, includeServiceCodeCS
-func (_m *MoveTaskOrderUpdater) MakeAvailableToPrime(appCtx appcontext.AppContext, moveTaskOrderID uuid.UUID, eTag string, includeServiceCodeMS bool, includeServiceCodeCS bool) (*models.Move, error) {
+// ApproveMoveAndCreateServiceItems provides a mock function with given fields: appCtx, moveTaskOrderID, eTag, includeServiceCodeMS, includeServiceCodeCS
+func (_m *MoveTaskOrderUpdater) ApproveMoveAndCreateServiceItems(appCtx appcontext.AppContext, moveTaskOrderID uuid.UUID, eTag string, includeServiceCodeMS bool, includeServiceCodeCS bool) (*models.Move, error) {
ret := _m.Called(appCtx, moveTaskOrderID, eTag, includeServiceCodeMS, includeServiceCodeCS)
if len(ret) == 0 {
- panic("no return value specified for MakeAvailableToPrime")
+ panic("no return value specified for ApproveMoveAndCreateServiceItems")
}
var r0 *models.Move
@@ -46,6 +46,43 @@ func (_m *MoveTaskOrderUpdater) MakeAvailableToPrime(appCtx appcontext.AppContex
return r0, r1
}
+// MakeAvailableToPrime provides a mock function with given fields: appCtx, moveTaskOrderID
+func (_m *MoveTaskOrderUpdater) MakeAvailableToPrime(appCtx appcontext.AppContext, moveTaskOrderID uuid.UUID) (*models.Move, bool, error) {
+ ret := _m.Called(appCtx, moveTaskOrderID)
+
+ if len(ret) == 0 {
+ panic("no return value specified for MakeAvailableToPrime")
+ }
+
+ var r0 *models.Move
+ var r1 bool
+ var r2 error
+ if rf, ok := ret.Get(0).(func(appcontext.AppContext, uuid.UUID) (*models.Move, bool, error)); ok {
+ return rf(appCtx, moveTaskOrderID)
+ }
+ if rf, ok := ret.Get(0).(func(appcontext.AppContext, uuid.UUID) *models.Move); ok {
+ r0 = rf(appCtx, moveTaskOrderID)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*models.Move)
+ }
+ }
+
+ if rf, ok := ret.Get(1).(func(appcontext.AppContext, uuid.UUID) bool); ok {
+ r1 = rf(appCtx, moveTaskOrderID)
+ } else {
+ r1 = ret.Get(1).(bool)
+ }
+
+ if rf, ok := ret.Get(2).(func(appcontext.AppContext, uuid.UUID) error); ok {
+ r2 = rf(appCtx, moveTaskOrderID)
+ } else {
+ r2 = ret.Error(2)
+ }
+
+ return r0, r1, r2
+}
+
// ShowHide provides a mock function with given fields: appCtx, moveTaskOrderID, show
func (_m *MoveTaskOrderUpdater) ShowHide(appCtx appcontext.AppContext, moveTaskOrderID uuid.UUID, show *bool) (*models.Move, error) {
ret := _m.Called(appCtx, moveTaskOrderID, show)
diff --git a/pkg/services/mocks/RequestedOfficeUserListFetcher.go b/pkg/services/mocks/RequestedOfficeUserListFetcher.go
index 98a226808eb..98211d4b57b 100644
--- a/pkg/services/mocks/RequestedOfficeUserListFetcher.go
+++ b/pkg/services/mocks/RequestedOfficeUserListFetcher.go
@@ -8,6 +8,8 @@ import (
models "github.com/transcom/mymove/pkg/models"
+ pop "github.com/gobuffalo/pop/v6"
+
services "github.com/transcom/mymove/pkg/services"
)
@@ -44,34 +46,41 @@ func (_m *RequestedOfficeUserListFetcher) FetchRequestedOfficeUsersCount(appCtx
return r0, r1
}
-// FetchRequestedOfficeUsersList provides a mock function with given fields: appCtx, filters, associations, pagination, ordering
-func (_m *RequestedOfficeUserListFetcher) FetchRequestedOfficeUsersList(appCtx appcontext.AppContext, filters []services.QueryFilter, associations services.QueryAssociations, pagination services.Pagination, ordering services.QueryOrder) (models.OfficeUsers, error) {
- ret := _m.Called(appCtx, filters, associations, pagination, ordering)
+// FetchRequestedOfficeUsersList provides a mock function with given fields: appCtx, filterFuncs, pagination, ordering
+func (_m *RequestedOfficeUserListFetcher) FetchRequestedOfficeUsersList(appCtx appcontext.AppContext, filterFuncs []func(*pop.Query), pagination services.Pagination, ordering services.QueryOrder) (models.OfficeUsers, int, error) {
+ ret := _m.Called(appCtx, filterFuncs, pagination, ordering)
if len(ret) == 0 {
panic("no return value specified for FetchRequestedOfficeUsersList")
}
var r0 models.OfficeUsers
- var r1 error
- if rf, ok := ret.Get(0).(func(appcontext.AppContext, []services.QueryFilter, services.QueryAssociations, services.Pagination, services.QueryOrder) (models.OfficeUsers, error)); ok {
- return rf(appCtx, filters, associations, pagination, ordering)
+ var r1 int
+ var r2 error
+ if rf, ok := ret.Get(0).(func(appcontext.AppContext, []func(*pop.Query), services.Pagination, services.QueryOrder) (models.OfficeUsers, int, error)); ok {
+ return rf(appCtx, filterFuncs, pagination, ordering)
}
- if rf, ok := ret.Get(0).(func(appcontext.AppContext, []services.QueryFilter, services.QueryAssociations, services.Pagination, services.QueryOrder) models.OfficeUsers); ok {
- r0 = rf(appCtx, filters, associations, pagination, ordering)
+ if rf, ok := ret.Get(0).(func(appcontext.AppContext, []func(*pop.Query), services.Pagination, services.QueryOrder) models.OfficeUsers); ok {
+ r0 = rf(appCtx, filterFuncs, pagination, ordering)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(models.OfficeUsers)
}
}
- if rf, ok := ret.Get(1).(func(appcontext.AppContext, []services.QueryFilter, services.QueryAssociations, services.Pagination, services.QueryOrder) error); ok {
- r1 = rf(appCtx, filters, associations, pagination, ordering)
+ if rf, ok := ret.Get(1).(func(appcontext.AppContext, []func(*pop.Query), services.Pagination, services.QueryOrder) int); ok {
+ r1 = rf(appCtx, filterFuncs, pagination, ordering)
} else {
- r1 = ret.Error(1)
+ r1 = ret.Get(1).(int)
}
- return r0, r1
+ if rf, ok := ret.Get(2).(func(appcontext.AppContext, []func(*pop.Query), services.Pagination, services.QueryOrder) error); ok {
+ r2 = rf(appCtx, filterFuncs, pagination, ordering)
+ } else {
+ r2 = ret.Error(2)
+ }
+
+ return r0, r1, r2
}
// NewRequestedOfficeUserListFetcher creates a new instance of RequestedOfficeUserListFetcher. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
diff --git a/pkg/services/mocks/ShipmentApprover.go b/pkg/services/mocks/ShipmentApprover.go
index ede7bdcc395..55722e53f55 100644
--- a/pkg/services/mocks/ShipmentApprover.go
+++ b/pkg/services/mocks/ShipmentApprover.go
@@ -8,6 +8,8 @@ import (
models "github.com/transcom/mymove/pkg/models"
+ services "github.com/transcom/mymove/pkg/services"
+
uuid "github.com/gofrs/uuid"
)
@@ -46,6 +48,36 @@ func (_m *ShipmentApprover) ApproveShipment(appCtx appcontext.AppContext, shipme
return r0, r1
}
+// ApproveShipments provides a mock function with given fields: appCtx, shipments
+func (_m *ShipmentApprover) ApproveShipments(appCtx appcontext.AppContext, shipments []services.ShipmentIdWithEtag) (*[]models.MTOShipment, error) {
+ ret := _m.Called(appCtx, shipments)
+
+ if len(ret) == 0 {
+ panic("no return value specified for ApproveShipments")
+ }
+
+ var r0 *[]models.MTOShipment
+ var r1 error
+ if rf, ok := ret.Get(0).(func(appcontext.AppContext, []services.ShipmentIdWithEtag) (*[]models.MTOShipment, error)); ok {
+ return rf(appCtx, shipments)
+ }
+ if rf, ok := ret.Get(0).(func(appcontext.AppContext, []services.ShipmentIdWithEtag) *[]models.MTOShipment); ok {
+ r0 = rf(appCtx, shipments)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*[]models.MTOShipment)
+ }
+ }
+
+ if rf, ok := ret.Get(1).(func(appcontext.AppContext, []services.ShipmentIdWithEtag) error); ok {
+ r1 = rf(appCtx, shipments)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
// NewShipmentApprover creates a new instance of ShipmentApprover. 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 NewShipmentApprover(t interface {
diff --git a/pkg/services/mocks/ShipmentRateAreaFinder.go b/pkg/services/mocks/ShipmentRateAreaFinder.go
index 663a74a3cb8..594551f1295 100644
--- a/pkg/services/mocks/ShipmentRateAreaFinder.go
+++ b/pkg/services/mocks/ShipmentRateAreaFinder.go
@@ -16,12 +16,12 @@ 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) {
+// GetPrimeMoveShipmentRateAreas provides a mock function with given fields: appCtx, move
+func (_m *ShipmentRateAreaFinder) GetPrimeMoveShipmentRateAreas(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")
+ panic("no return value specified for GetPrimeMoveShipmentRateAreas")
}
var r0 *[]services.ShipmentPostalCodeRateArea
diff --git a/pkg/services/mocks/TransportationOfficesFetcher.go b/pkg/services/mocks/TransportationOfficesFetcher.go
index eed32753594..d0c017b3e19 100644
--- a/pkg/services/mocks/TransportationOfficesFetcher.go
+++ b/pkg/services/mocks/TransportationOfficesFetcher.go
@@ -46,9 +46,9 @@ func (_m *TransportationOfficesFetcher) GetAllGBLOCs(appCtx appcontext.AppContex
return r0, r1
}
-// GetCounselingOffices provides a mock function with given fields: appCtx, dutyLocationID
-func (_m *TransportationOfficesFetcher) GetCounselingOffices(appCtx appcontext.AppContext, dutyLocationID uuid.UUID) (*models.TransportationOffices, error) {
- ret := _m.Called(appCtx, dutyLocationID)
+// GetCounselingOffices provides a mock function with given fields: appCtx, dutyLocationID, serviceMemberID
+func (_m *TransportationOfficesFetcher) GetCounselingOffices(appCtx appcontext.AppContext, dutyLocationID uuid.UUID, serviceMemberID uuid.UUID) (*models.TransportationOffices, error) {
+ ret := _m.Called(appCtx, dutyLocationID, serviceMemberID)
if len(ret) == 0 {
panic("no return value specified for GetCounselingOffices")
@@ -56,19 +56,19 @@ func (_m *TransportationOfficesFetcher) GetCounselingOffices(appCtx appcontext.A
var r0 *models.TransportationOffices
var r1 error
- if rf, ok := ret.Get(0).(func(appcontext.AppContext, uuid.UUID) (*models.TransportationOffices, error)); ok {
- return rf(appCtx, dutyLocationID)
+ if rf, ok := ret.Get(0).(func(appcontext.AppContext, uuid.UUID, uuid.UUID) (*models.TransportationOffices, error)); ok {
+ return rf(appCtx, dutyLocationID, serviceMemberID)
}
- if rf, ok := ret.Get(0).(func(appcontext.AppContext, uuid.UUID) *models.TransportationOffices); ok {
- r0 = rf(appCtx, dutyLocationID)
+ if rf, ok := ret.Get(0).(func(appcontext.AppContext, uuid.UUID, uuid.UUID) *models.TransportationOffices); ok {
+ r0 = rf(appCtx, dutyLocationID, serviceMemberID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.TransportationOffices)
}
}
- if rf, ok := ret.Get(1).(func(appcontext.AppContext, uuid.UUID) error); ok {
- r1 = rf(appCtx, dutyLocationID)
+ if rf, ok := ret.Get(1).(func(appcontext.AppContext, uuid.UUID, uuid.UUID) error); ok {
+ r1 = rf(appCtx, dutyLocationID, serviceMemberID)
} else {
r1 = ret.Error(1)
}
@@ -106,9 +106,9 @@ func (_m *TransportationOfficesFetcher) GetTransportationOffice(appCtx appcontex
return r0, r1
}
-// GetTransportationOffices provides a mock function with given fields: appCtx, search, forPpm
-func (_m *TransportationOfficesFetcher) GetTransportationOffices(appCtx appcontext.AppContext, search string, forPpm bool) (*models.TransportationOffices, error) {
- ret := _m.Called(appCtx, search, forPpm)
+// GetTransportationOffices provides a mock function with given fields: appCtx, search, forPpm, forAdminOfficeUserReqFilter
+func (_m *TransportationOfficesFetcher) GetTransportationOffices(appCtx appcontext.AppContext, search string, forPpm bool, forAdminOfficeUserReqFilter bool) (*models.TransportationOffices, error) {
+ ret := _m.Called(appCtx, search, forPpm, forAdminOfficeUserReqFilter)
if len(ret) == 0 {
panic("no return value specified for GetTransportationOffices")
@@ -116,19 +116,19 @@ func (_m *TransportationOfficesFetcher) GetTransportationOffices(appCtx appconte
var r0 *models.TransportationOffices
var r1 error
- if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, bool) (*models.TransportationOffices, error)); ok {
- return rf(appCtx, search, forPpm)
+ if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, bool, bool) (*models.TransportationOffices, error)); ok {
+ return rf(appCtx, search, forPpm, forAdminOfficeUserReqFilter)
}
- if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, bool) *models.TransportationOffices); ok {
- r0 = rf(appCtx, search, forPpm)
+ if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, bool, bool) *models.TransportationOffices); ok {
+ r0 = rf(appCtx, search, forPpm, forAdminOfficeUserReqFilter)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*models.TransportationOffices)
}
}
- if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, bool) error); ok {
- r1 = rf(appCtx, search, forPpm)
+ if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, bool, bool) error); ok {
+ r1 = rf(appCtx, search, forPpm, forAdminOfficeUserReqFilter)
} else {
r1 = ret.Error(1)
}
diff --git a/pkg/services/move.go b/pkg/services/move.go
index 0262e8da8ae..ff3abe681de 100644
--- a/pkg/services/move.go
+++ b/pkg/services/move.go
@@ -33,6 +33,7 @@ type MoveFetcherBulkAssignment interface {
FetchMovesForBulkAssignmentCounseling(appCtx appcontext.AppContext, gbloc string, officeId uuid.UUID) ([]models.MoveWithEarliestDate, error)
FetchMovesForBulkAssignmentCloseout(appCtx appcontext.AppContext, gbloc string, officeId uuid.UUID) ([]models.MoveWithEarliestDate, error)
FetchMovesForBulkAssignmentTaskOrder(appCtx appcontext.AppContext, gbloc string, officeId uuid.UUID) ([]models.MoveWithEarliestDate, error)
+ FetchMovesForBulkAssignmentPaymentRequest(appCtx appcontext.AppContext, gbloc string, officeId uuid.UUID) ([]models.MoveWithEarliestDate, error)
}
//go:generate mockery --name MoveSearcher
diff --git a/pkg/services/move/move_fetcher.go b/pkg/services/move/move_fetcher.go
index c0ca805f7c1..e0d6aea4f8a 100644
--- a/pkg/services/move/move_fetcher.go
+++ b/pkg/services/move/move_fetcher.go
@@ -27,7 +27,7 @@ func (f moveFetcher) FetchMove(appCtx appcontext.AppContext, locator string, sea
move := &models.Move{}
query := appCtx.DB().
EagerPreload("CloseoutOffice.Address", "Contractor", "ShipmentGBLOC", "LockedByOfficeUser", "LockedByOfficeUser.TransportationOffice", "AdditionalDocuments",
- "AdditionalDocuments.UserUploads").
+ "AdditionalDocuments.UserUploads", "CounselingOffice").
LeftJoin("move_to_gbloc", "move_to_gbloc.move_id = moves.id").
LeftJoin("office_users", "office_users.id = moves.locked_by").
Where("locator = $1", locator)
@@ -135,7 +135,8 @@ func (f moveFetcherBulkAssignment) FetchMovesForBulkAssignmentCounseling(appCtx
INNER JOIN mto_shipments ON mto_shipments.move_id = moves.id
LEFT JOIN ppm_shipments ON ppm_shipments.shipment_id = mto_shipments.id
WHERE
- moves.status = 'NEEDS SERVICE COUNSELING'
+ mto_shipments.deleted_at IS NULL
+ AND moves.status = 'NEEDS SERVICE COUNSELING'
AND orders.gbloc = $1
AND moves.show = $2
AND moves.sc_assigned_id IS NULL
@@ -223,37 +224,92 @@ func (f moveFetcherBulkAssignment) FetchMovesForBulkAssignmentCloseout(appCtx ap
func (f moveFetcherBulkAssignment) FetchMovesForBulkAssignmentTaskOrder(appCtx appcontext.AppContext, gbloc string, officeId uuid.UUID) ([]models.MoveWithEarliestDate, error) {
var moves []models.MoveWithEarliestDate
- err := appCtx.DB().
- RawQuery(`SELECT
- moves.id,
- MIN(LEAST(
- COALESCE(mto_shipments.requested_pickup_date, '9999-12-31'),
- COALESCE(mto_shipments.requested_delivery_date, '9999-12-31'),
- COALESCE(ppm_shipments.expected_departure_date, '9999-12-31')
- )) AS earliest_date
- FROM moves
- INNER JOIN orders ON orders.id = moves.orders_id
- INNER JOIN service_members ON orders.service_member_id = service_members.id
- INNER JOIN mto_shipments ON mto_shipments.move_id = moves.id
- LEFT JOIN ppm_shipments ON ppm_shipments.shipment_id = mto_shipments.id
- LEFT JOIN move_to_gbloc ON move_to_gbloc.move_id = moves.id
- WHERE
- (moves.status IN ('APPROVALS REQUESTED', 'SUBMITTED', 'SERVICE COUNSELING COMPLETED'))
- AND moves.show = $1
- AND moves.too_assigned_id IS NULL
- AND (orders.orders_type NOT IN ($2, $3, $4))
- AND service_members.affiliation != 'MARINES'
- AND ((mto_shipments.shipment_type != $5 AND move_to_gbloc.gbloc = $6) OR (mto_shipments.shipment_type = $7 AND orders.gbloc = $8))
- GROUP BY moves.id
- ORDER BY earliest_date ASC`,
- models.BoolPointer(true),
- internalmessages.OrdersTypeBLUEBARK,
- internalmessages.OrdersTypeWOUNDEDWARRIOR,
- internalmessages.OrdersTypeSAFETY,
- models.MTOShipmentTypeHHGOutOfNTS,
- gbloc,
- models.MTOShipmentTypeHHGOutOfNTS,
- gbloc).
+ sqlQuery := `
+ SELECT
+ moves.id,
+ MIN(LEAST(
+ COALESCE(mto_shipments.requested_pickup_date, '9999-12-31'),
+ COALESCE(mto_shipments.requested_delivery_date, '9999-12-31'),
+ COALESCE(ppm_shipments.expected_departure_date, '9999-12-31')
+ )) AS earliest_date
+ FROM moves
+ INNER JOIN orders ON orders.id = moves.orders_id
+ INNER JOIN service_members ON orders.service_member_id = service_members.id
+ INNER JOIN mto_shipments ON mto_shipments.move_id = moves.id
+ INNER JOIN duty_locations as origin_dl ON orders.origin_duty_location_id = origin_dl.id
+ LEFT JOIN ppm_shipments ON ppm_shipments.shipment_id = mto_shipments.id
+ LEFT JOIN move_to_gbloc ON move_to_gbloc.move_id = moves.id
+ WHERE
+ mto_shipments.deleted_at IS NULL
+ AND (moves.status IN ('APPROVALS REQUESTED', 'SUBMITTED', 'SERVICE COUNSELING COMPLETED'))
+ AND moves.show = $1
+ AND moves.too_assigned_id IS NULL
+ AND (orders.orders_type NOT IN ($2, $3, $4))
+ AND (moves.ppm_type IS NULL OR (moves.ppm_type = 'PARTIAL' or (moves.ppm_type = 'FULL' and origin_dl.provides_services_counseling = 'false'))) `
+ if gbloc == "USMC" {
+ sqlQuery += `
+ AND service_members.affiliation ILIKE 'MARINES' `
+ } else {
+ sqlQuery += `
+ AND service_members.affiliation != 'MARINES'
+ AND ((mto_shipments.shipment_type != 'HHG_OUTOF_NTS' AND move_to_gbloc.gbloc = '` + gbloc + `')
+ OR (mto_shipments.shipment_type = 'HHG_OUTOF_NTS' AND orders.gbloc = '` + gbloc + `')) `
+ }
+ sqlQuery += `
+ GROUP BY moves.id
+ ORDER BY earliest_date ASC`
+
+ err := appCtx.DB().RawQuery(sqlQuery,
+ models.BoolPointer(true),
+ internalmessages.OrdersTypeBLUEBARK,
+ internalmessages.OrdersTypeWOUNDEDWARRIOR,
+ internalmessages.OrdersTypeSAFETY).
+ All(&moves)
+
+ if err != nil {
+ return nil, fmt.Errorf("error fetching moves for office: %s with error %w", officeId, err)
+ }
+
+ if len(moves) < 1 {
+ return nil, nil
+ }
+
+ return moves, nil
+}
+
+func (f moveFetcherBulkAssignment) FetchMovesForBulkAssignmentPaymentRequest(appCtx appcontext.AppContext, gbloc string, officeId uuid.UUID) ([]models.MoveWithEarliestDate, error) {
+ var moves []models.MoveWithEarliestDate
+
+ sqlQuery := `
+ SELECT
+ moves.id,
+ min(payment_requests.requested_at) AS earliest_date
+ FROM moves
+ INNER JOIN orders ON orders.id = moves.orders_id
+ INNER JOIN service_members ON orders.service_member_id = service_members.id
+ INNER JOIN payment_requests on payment_requests.move_id = moves.id
+ LEFT JOIN move_to_gbloc ON move_to_gbloc.move_id = moves.id
+ WHERE moves.show = $1
+ AND (orders.orders_type NOT IN ($2, $3, $4))
+ AND moves.tio_assigned_id IS NULL
+ AND payment_requests.status = 'PENDING' `
+ if gbloc == "USMC" {
+ sqlQuery += `
+ AND service_members.affiliation ILIKE 'MARINES' `
+ } else {
+ sqlQuery += `
+ AND service_members.affiliation != 'MARINES'
+ AND move_to_gbloc.gbloc = '` + gbloc + `' `
+ }
+ sqlQuery += `
+ GROUP BY moves.id
+ ORDER BY earliest_date ASC`
+
+ err := appCtx.DB().RawQuery(sqlQuery,
+ models.BoolPointer(true),
+ internalmessages.OrdersTypeBLUEBARK,
+ internalmessages.OrdersTypeWOUNDEDWARRIOR,
+ internalmessages.OrdersTypeSAFETY).
All(&moves)
if err != nil {
diff --git a/pkg/services/move/move_fetcher_test.go b/pkg/services/move/move_fetcher_test.go
index e6144353c88..62b6551add5 100644
--- a/pkg/services/move/move_fetcher_test.go
+++ b/pkg/services/move/move_fetcher_test.go
@@ -3,6 +3,8 @@ package move
import (
"time"
+ "github.com/gofrs/uuid"
+
"github.com/transcom/mymove/pkg/apperror"
"github.com/transcom/mymove/pkg/factory"
"github.com/transcom/mymove/pkg/gen/internalmessages"
@@ -105,7 +107,7 @@ func (suite *MoveServiceSuite) TestMoveFetcher() {
})
}
-func (suite *MoveServiceSuite) TestMoveFetcherBulkAssignment() {
+func (suite *MoveServiceSuite) TestMoveFetcherBulkAssignmentSC() {
setupTestData := func() (services.MoveFetcherBulkAssignment, models.Move, models.TransportationOffice, models.OfficeUser) {
moveFetcher := NewMoveFetcherBulkAssignment()
transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil)
@@ -149,7 +151,7 @@ func (suite *MoveServiceSuite) TestMoveFetcherBulkAssignment() {
return moveFetcher, move, transportationOffice, officeUser
}
- suite.Run("Returns moves that fulfill the query's criteria", func() {
+ suite.Run("SC - Returns moves that fulfill the query's criteria", func() {
moveFetcher, _, _, officeUser := setupTestData()
moves, err := moveFetcher.FetchMovesForBulkAssignmentCounseling(suite.AppContextForTest(), "KKFA", officeUser.TransportationOffice.ID)
suite.FatalNoError(err)
@@ -517,10 +519,13 @@ func (suite *MoveServiceSuite) TestMoveFetcherBulkAssignment() {
suite.Equal(1, len(moves))
suite.NotEqual(marinePPM.ID, moves[0].ID)
})
+}
- suite.Run("TOO: Returns moves that fulfill the query criteria", func() {
+func (suite *MoveServiceSuite) TestMoveFetcherBulkAssignmentTOO() {
+ setupTestData := func() (services.MoveFetcherBulkAssignment, models.TransportationOffice, models.OfficeUser) {
moveFetcher := NewMoveFetcherBulkAssignment()
transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil)
+
officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{
{
Model: transportationOffice,
@@ -552,6 +557,128 @@ func (suite *MoveServiceSuite) TestMoveFetcherBulkAssignment() {
Type: &factory.TransportationOffices.CounselingOffice,
},
}, nil)
+
+ return moveFetcher, transportationOffice, officeUser
+ }
+
+ suite.Run("TOO: Returns moves that fulfill the query's criteria", func() {
+ moveFetcher, _, officeUser := setupTestData()
+ moves, err := moveFetcher.FetchMovesForBulkAssignmentTaskOrder(suite.AppContextForTest(), "KKFA", officeUser.TransportationOffice.ID)
+ suite.FatalNoError(err)
+ suite.Equal(2, len(moves))
+ })
+
+ suite.Run("TOO: Does not return moves with safety, bluebark, or wounded warrior order types", func() {
+ moveFetcher, transportationOffice, officeUser := setupTestData()
+ factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.Order{
+ OrdersType: internalmessages.OrdersTypeSAFETY,
+ },
+ },
+ {
+ Model: models.Move{
+ Status: models.MoveStatusServiceCounselingCompleted,
+ },
+ },
+ {
+ Model: transportationOffice,
+ LinkOnly: true,
+ Type: &factory.TransportationOffices.CounselingOffice,
+ },
+ }, nil)
+ factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.Order{
+ OrdersType: internalmessages.OrdersTypeBLUEBARK,
+ },
+ },
+ {
+ Model: models.Move{
+ Status: models.MoveStatusServiceCounselingCompleted,
+ },
+ },
+ {
+ Model: transportationOffice,
+ LinkOnly: true,
+ Type: &factory.TransportationOffices.CounselingOffice,
+ },
+ }, nil)
+ factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.Order{
+ OrdersType: internalmessages.OrdersTypeWOUNDEDWARRIOR,
+ },
+ },
+ {
+ Model: models.Move{
+ Status: models.MoveStatusServiceCounselingCompleted,
+ },
+ },
+ {
+ Model: transportationOffice,
+ LinkOnly: true,
+ Type: &factory.TransportationOffices.CounselingOffice,
+ },
+ }, nil)
+
+ moves, err := moveFetcher.FetchMovesForBulkAssignmentTaskOrder(suite.AppContextForTest(), "KKFA", officeUser.TransportationOffice.ID)
+ suite.FatalNoError(err)
+ suite.Equal(2, len(moves))
+ })
+
+ suite.Run("TOO: Does not return moves that are already assigned", func() {
+ moveFetcher := NewMoveFetcherBulkAssignment()
+ transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil)
+
+ officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{
+ {
+ Model: transportationOffice,
+ LinkOnly: true,
+ Type: &factory.TransportationOffices.CounselingOffice,
+ },
+ }, []roles.RoleType{roles.RoleTypeTOO})
+
+ assignedMove := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.Move{
+ Status: models.MoveStatusServiceCounselingCompleted,
+ },
+ },
+ {
+ Model: transportationOffice,
+ LinkOnly: true,
+ Type: &factory.TransportationOffices.CounselingOffice,
+ },
+ {
+ Model: officeUser,
+ LinkOnly: true,
+ Type: &factory.OfficeUsers.TOOAssignedUser,
+ },
+ }, nil)
+
+ moves, err := moveFetcher.FetchMovesForBulkAssignmentTaskOrder(suite.AppContextForTest(), "KKFA", officeUser.TransportationOffice.ID)
+ suite.FatalNoError(err)
+
+ // confirm that the assigned move isn't returned
+ for _, move := range moves {
+ suite.NotEqual(move.ID, assignedMove.ID)
+ }
+
+ // confirm that the rest of the details are correct
+ // move is SERVICE COUNSELING COMPLETED
+ suite.Equal(assignedMove.Status, models.MoveStatusServiceCounselingCompleted)
+ // GBLOC is the same
+ suite.Equal(*assignedMove.Orders.OriginDutyLocationGBLOC, officeUser.TransportationOffice.Gbloc)
+ // Show is true
+ suite.Equal(assignedMove.Show, models.BoolPointer(true))
+ // Orders type isn't WW, BB, or Safety
+ suite.Equal(assignedMove.Orders.OrdersType, internalmessages.OrdersTypePERMANENTCHANGEOFSTATION)
+ })
+
+ suite.Run("TOO: Does not return payment requests with Marines if GBLOC not USMC", func() {
+ moveFetcher, transportationOffice, officeUser := setupTestData()
+
marine := models.AffiliationMARINES
factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{
{
@@ -574,4 +701,360 @@ func (suite *MoveServiceSuite) TestMoveFetcherBulkAssignment() {
suite.FatalNoError(err)
suite.Equal(2, len(moves))
})
+
+ suite.Run("TOO: Only return payment requests with Marines if GBLOC is USMC", func() {
+ moveFetcher, transportationOffice, officeUser := setupTestData()
+
+ marine := models.AffiliationMARINES
+ factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.Move{
+ Status: models.MoveStatusServiceCounselingCompleted,
+ },
+ },
+ {
+ Model: transportationOffice,
+ LinkOnly: true,
+ Type: &factory.TransportationOffices.CounselingOffice,
+ },
+ {
+ Model: models.ServiceMember{
+ Affiliation: &marine,
+ },
+ },
+ }, nil)
+
+ moves, err := moveFetcher.FetchMovesForBulkAssignmentTaskOrder(suite.AppContextForTest(), "USMC", officeUser.TransportationOffice.ID)
+ suite.FatalNoError(err)
+ suite.Equal(1, len(moves))
+ })
+
+}
+
+func (suite *MoveServiceSuite) TestMoveFetcherBulkAssignmentTIO() {
+ setupTestData := func() (services.MoveFetcherBulkAssignment, models.TransportationOffice, models.OfficeUser) {
+ moveFetcher := NewMoveFetcherBulkAssignment()
+ transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil)
+
+ // this move has a transportation office associated with it that matches
+ // the TIO's transportation office and should be found
+ move := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.Move{
+ Status: models.MoveStatusAPPROVALSREQUESTED,
+ },
+ },
+ {
+ Model: transportationOffice,
+ LinkOnly: true,
+ Type: &factory.TransportationOffices.CounselingOffice,
+ },
+ }, nil)
+ factory.BuildPaymentRequest(suite.DB(), []factory.Customization{
+ {
+ Model: models.PaymentRequest{
+ ID: uuid.Must(uuid.NewV4()),
+ IsFinal: false,
+ Status: models.PaymentRequestStatusPending,
+ RejectionReason: nil,
+ },
+ },
+ {
+ Model: move,
+ LinkOnly: true,
+ },
+ }, nil)
+
+ move2 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.Move{
+ Status: models.MoveStatusAPPROVALSREQUESTED,
+ },
+ },
+ {
+ Model: transportationOffice,
+ LinkOnly: true,
+ Type: &factory.TransportationOffices.CounselingOffice,
+ },
+ }, nil)
+ factory.BuildPaymentRequest(suite.DB(), []factory.Customization{
+ {
+ Model: models.PaymentRequest{
+ ID: uuid.Must(uuid.NewV4()),
+ IsFinal: false,
+ Status: models.PaymentRequestStatusPending,
+ RejectionReason: nil,
+ },
+ },
+ {
+ Model: move2,
+ LinkOnly: true,
+ },
+ }, nil)
+
+ officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{
+ {
+ Model: transportationOffice,
+ LinkOnly: true,
+ Type: &factory.TransportationOffices.CounselingOffice,
+ },
+ }, []roles.RoleType{roles.RoleTypeTIO})
+
+ return moveFetcher, transportationOffice, officeUser
+ }
+
+ suite.Run("TIO: Returns moves that fulfill the query criteria", func() {
+ moveFetcher, _, officeUser := setupTestData()
+ moves, err := moveFetcher.FetchMovesForBulkAssignmentPaymentRequest(suite.AppContextForTest(), "KKFA", officeUser.TransportationOffice.ID)
+ suite.FatalNoError(err)
+ suite.Equal(2, len(moves))
+ })
+
+ suite.Run("Does not return moves that are already assigned", func() {
+ moveFetcher := NewMoveFetcherBulkAssignment()
+ transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil)
+
+ officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{
+ {
+ Model: transportationOffice,
+ LinkOnly: true,
+ Type: &factory.TransportationOffices.CounselingOffice,
+ },
+ }, []roles.RoleType{roles.RoleTypeTIO})
+
+ move := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.Move{
+ Status: models.MoveStatusAPPROVALSREQUESTED,
+ },
+ },
+ {
+ Model: transportationOffice,
+ LinkOnly: true,
+ Type: &factory.TransportationOffices.CounselingOffice,
+ },
+ {
+ Model: officeUser,
+ LinkOnly: true,
+ Type: &factory.OfficeUsers.TIOAssignedUser,
+ },
+ }, nil)
+ assignedPaymentRequest := factory.BuildPaymentRequest(suite.DB(), []factory.Customization{
+ {
+ Model: models.PaymentRequest{
+ ID: uuid.Must(uuid.NewV4()),
+ IsFinal: false,
+ Status: models.PaymentRequestStatusPending,
+ RejectionReason: nil,
+ },
+ },
+ {
+ Model: move,
+ LinkOnly: true,
+ },
+ }, nil)
+
+ moves, err := moveFetcher.FetchMovesForBulkAssignmentPaymentRequest(suite.AppContextForTest(), "KKFA", officeUser.TransportationOffice.ID)
+ suite.FatalNoError(err)
+
+ // confirm that the assigned move isn't returned
+ for _, move := range moves {
+ suite.NotEqual(move.ID, assignedPaymentRequest.ID)
+ }
+
+ // confirm that the rest of the details are correct
+ // move is APPROVALS REQUESTED STATUS
+ suite.Equal(assignedPaymentRequest.Status, models.PaymentRequestStatusPending)
+ // GBLOC is the same
+ suite.Equal(*move.Orders.OriginDutyLocationGBLOC, officeUser.TransportationOffice.Gbloc)
+ // Show is true
+ suite.Equal(move.Show, models.BoolPointer(true))
+ // Orders type isn't WW, BB, or Safety
+ suite.Equal(move.Orders.OrdersType, internalmessages.OrdersTypePERMANENTCHANGEOFSTATION)
+ })
+
+ suite.Run("TIO: Does not return moves with safety, bluebark, or wounded warrior order types", func() {
+ moveFetcher, transportationOffice, officeUser := setupTestData()
+ moveSafety := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.Order{
+ OrdersType: internalmessages.OrdersTypeSAFETY,
+ },
+ },
+ {
+ Model: models.Move{
+ Status: models.MoveStatusAPPROVALSREQUESTED,
+ },
+ },
+ {
+ Model: transportationOffice,
+ LinkOnly: true,
+ Type: &factory.TransportationOffices.CounselingOffice,
+ },
+ }, nil)
+ factory.BuildPaymentRequest(suite.DB(), []factory.Customization{
+ {
+ Model: models.PaymentRequest{
+ ID: uuid.Must(uuid.NewV4()),
+ IsFinal: false,
+ Status: models.PaymentRequestStatusPending,
+ RejectionReason: nil,
+ },
+ },
+ {
+ Model: moveSafety,
+ LinkOnly: true,
+ },
+ }, nil)
+
+ moveBB := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.Order{
+ OrdersType: internalmessages.OrdersTypeBLUEBARK,
+ },
+ },
+ {
+ Model: models.Move{
+ Status: models.MoveStatusAPPROVALSREQUESTED,
+ },
+ },
+ {
+ Model: transportationOffice,
+ LinkOnly: true,
+ Type: &factory.TransportationOffices.CounselingOffice,
+ },
+ }, nil)
+ factory.BuildPaymentRequest(suite.DB(), []factory.Customization{
+ {
+ Model: models.PaymentRequest{
+ ID: uuid.Must(uuid.NewV4()),
+ IsFinal: false,
+ Status: models.PaymentRequestStatusPending,
+ RejectionReason: nil,
+ },
+ },
+ {
+ Model: moveBB,
+ LinkOnly: true,
+ },
+ }, nil)
+
+ moveWW := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.Order{
+ OrdersType: internalmessages.OrdersTypeWOUNDEDWARRIOR,
+ },
+ },
+ {
+ Model: models.Move{
+ Status: models.MoveStatusAPPROVALSREQUESTED,
+ },
+ },
+ {
+ Model: transportationOffice,
+ LinkOnly: true,
+ Type: &factory.TransportationOffices.CounselingOffice,
+ },
+ }, nil)
+ factory.BuildPaymentRequest(suite.DB(), []factory.Customization{
+ {
+ Model: models.PaymentRequest{
+ ID: uuid.Must(uuid.NewV4()),
+ IsFinal: false,
+ Status: models.PaymentRequestStatusPending,
+ RejectionReason: nil,
+ },
+ },
+ {
+ Model: moveWW,
+ LinkOnly: true,
+ },
+ }, nil)
+
+ moves, err := moveFetcher.FetchMovesForBulkAssignmentPaymentRequest(suite.AppContextForTest(), "KKFA", officeUser.TransportationOffice.ID)
+ suite.FatalNoError(err)
+ suite.Equal(2, len(moves))
+ })
+
+ suite.Run("TIO: Does not return payment requests with Marines if GBLOC not USMC", func() {
+ moveFetcher, transportationOffice, officeUser := setupTestData()
+
+ marine := models.AffiliationMARINES
+ move := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.Move{
+ Status: models.MoveStatusAPPROVALSREQUESTED,
+ },
+ },
+ {
+ Model: transportationOffice,
+ LinkOnly: true,
+ Type: &factory.TransportationOffices.CounselingOffice,
+ },
+ {
+ Model: models.ServiceMember{
+ Affiliation: &marine,
+ },
+ },
+ }, nil)
+ factory.BuildPaymentRequest(suite.DB(), []factory.Customization{
+ {
+ Model: models.PaymentRequest{
+ ID: uuid.Must(uuid.NewV4()),
+ IsFinal: false,
+ Status: models.PaymentRequestStatusPending,
+ RejectionReason: nil,
+ },
+ },
+ {
+ Model: move,
+ LinkOnly: true,
+ },
+ }, nil)
+
+ moves, err := moveFetcher.FetchMovesForBulkAssignmentPaymentRequest(suite.AppContextForTest(), "KKFA", officeUser.TransportationOffice.ID)
+ suite.FatalNoError(err)
+ suite.Equal(2, len(moves))
+ })
+
+ suite.Run("TIO: Only return payment requests with Marines if GBLOC is USMC", func() {
+ moveFetcher, transportationOffice, officeUser := setupTestData()
+
+ marine := models.AffiliationMARINES
+ move := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.Move{
+ Status: models.MoveStatusServiceCounselingCompleted,
+ },
+ },
+ {
+ Model: transportationOffice,
+ LinkOnly: true,
+ Type: &factory.TransportationOffices.CounselingOffice,
+ },
+ {
+ Model: models.ServiceMember{
+ Affiliation: &marine,
+ },
+ },
+ }, nil)
+ factory.BuildPaymentRequest(suite.DB(), []factory.Customization{
+ {
+ Model: models.PaymentRequest{
+ ID: uuid.Must(uuid.NewV4()),
+ IsFinal: false,
+ Status: models.PaymentRequestStatusPending,
+ RejectionReason: nil,
+ },
+ },
+ {
+ Model: move,
+ LinkOnly: true,
+ },
+ }, nil)
+
+ moves, err := moveFetcher.FetchMovesForBulkAssignmentPaymentRequest(suite.AppContextForTest(), "USMC", officeUser.TransportationOffice.ID)
+ suite.FatalNoError(err)
+ suite.Equal(1, len(moves))
+ })
}
diff --git a/pkg/services/move_task_order.go b/pkg/services/move_task_order.go
index 717d2b7c566..9f0d3a9f01e 100644
--- a/pkg/services/move_task_order.go
+++ b/pkg/services/move_task_order.go
@@ -57,13 +57,14 @@ type MoveTaskOrderFetcher interface {
//
//go:generate mockery --name MoveTaskOrderUpdater
type MoveTaskOrderUpdater interface {
- MakeAvailableToPrime(appCtx appcontext.AppContext, moveTaskOrderID uuid.UUID, eTag string, includeServiceCodeMS bool, includeServiceCodeCS bool) (*models.Move, error)
+ ApproveMoveAndCreateServiceItems(appCtx appcontext.AppContext, moveTaskOrderID uuid.UUID, eTag string, includeServiceCodeMS bool, includeServiceCodeCS bool) (*models.Move, error)
UpdatePostCounselingInfo(appCtx appcontext.AppContext, moveTaskOrderID uuid.UUID, eTag string) (*models.Move, error)
UpdateStatusServiceCounselingCompleted(appCtx appcontext.AppContext, moveTaskOrderID uuid.UUID, eTag string) (*models.Move, error)
UpdateReviewedBillableWeightsAt(appCtx appcontext.AppContext, moveTaskOrderID uuid.UUID, eTag string) (*models.Move, error)
UpdateTIORemarks(appCtx appcontext.AppContext, moveTaskOrderID uuid.UUID, eTag string, remarks string) (*models.Move, error)
ShowHide(appCtx appcontext.AppContext, moveTaskOrderID uuid.UUID, show *bool) (*models.Move, error)
UpdatePPMType(appCtx appcontext.AppContext, moveTaskOrderID uuid.UUID) (*models.Move, error)
+ MakeAvailableToPrime(appCtx appcontext.AppContext, moveTaskOrderID uuid.UUID) (*models.Move, bool, error)
}
// MoveTaskOrderChecker is the service object interface for checking if a MoveTaskOrder is in a certain state
diff --git a/pkg/services/move_task_order/move_task_order_fetcher.go b/pkg/services/move_task_order/move_task_order_fetcher.go
index 50fc813bd06..f93bbf51681 100644
--- a/pkg/services/move_task_order/move_task_order_fetcher.go
+++ b/pkg/services/move_task_order/move_task_order_fetcher.go
@@ -14,6 +14,7 @@ import (
"github.com/transcom/mymove/pkg/appcontext"
"github.com/transcom/mymove/pkg/apperror"
"github.com/transcom/mymove/pkg/cli"
+ "github.com/transcom/mymove/pkg/db/utilities"
"github.com/transcom/mymove/pkg/models"
"github.com/transcom/mymove/pkg/services"
"github.com/transcom/mymove/pkg/services/featureflag"
@@ -158,7 +159,6 @@ func (f moveTaskOrderFetcher) FetchMoveTaskOrder(appCtx appcontext.AppContext, s
"MTOShipments.SecondaryPickupAddress.Country",
"MTOShipments.TertiaryDeliveryAddress.Country",
"MTOShipments.TertiaryPickupAddress.Country",
- "MTOShipments.MTOAgents",
"MTOShipments.SITDurationUpdates",
"MTOShipments.StorageFacility",
"MTOShipments.StorageFacility.Address",
@@ -199,6 +199,20 @@ func (f moveTaskOrderFetcher) FetchMoveTaskOrder(appCtx appcontext.AppContext, s
}
}
+ for i := range mto.MTOShipments {
+ var nonDeletedAgents models.MTOAgents
+ loadErr := appCtx.DB().
+ Scope(utilities.ExcludeDeletedScope()).
+ Where("mto_shipment_id = ?", mto.MTOShipments[i].ID).
+ All(&nonDeletedAgents)
+
+ if loadErr != nil {
+ return &models.Move{}, apperror.NewQueryError("MTOAgents", loadErr, "")
+ }
+
+ mto.MTOShipments[i].MTOAgents = nonDeletedAgents
+ }
+
// Now that we have the move and order, construct the allotment (hhg allowance)
// Only fetch if grade is not nil
if mto.Orders.Grade != nil {
@@ -338,12 +352,11 @@ func (f moveTaskOrderFetcher) FetchMoveTaskOrder(appCtx appcontext.AppContext, s
if loadErr != nil {
return &models.Move{}, apperror.NewQueryError("CustomerContacts", loadErr, "")
}
- } else if serviceItem.ReService.Code == models.ReServiceCodeICRT || // use address.isOconus to get 'market' value for intl crating
- serviceItem.ReService.Code == models.ReServiceCodeIUCRT {
- loadErr := appCtx.DB().Load(&mto.MTOServiceItems[i], "MTOShipment.PickupAddress", "MTOShipment.DestinationAddress")
- if loadErr != nil {
- return &models.Move{}, apperror.NewQueryError("MTOShipment.PickupAddress, MTOShipment.DestinationAddress", loadErr, "")
- }
+ }
+
+ loadErr := appCtx.DB().Load(&mto.MTOServiceItems[i], "MTOShipment.PickupAddress", "MTOShipment.DestinationAddress")
+ if loadErr != nil {
+ return &models.Move{}, apperror.NewQueryError("MTOShipment", loadErr, "")
}
loadedServiceItems = append(loadedServiceItems, mto.MTOServiceItems[i])
diff --git a/pkg/services/move_task_order/move_task_order_fetcher_test.go b/pkg/services/move_task_order/move_task_order_fetcher_test.go
index 07565525901..3cf793bc3ba 100644
--- a/pkg/services/move_task_order/move_task_order_fetcher_test.go
+++ b/pkg/services/move_task_order/move_task_order_fetcher_test.go
@@ -569,6 +569,75 @@ func (suite *MoveTaskOrderServiceSuite) TestMoveTaskOrderFetcher() {
suite.True(found, "Expected service item ReServiceCodePODFSC")
})
+ suite.Run("Success - Move contains only deleted MTOAgents", func() {
+ move := factory.BuildMove(suite.DB(), nil, nil)
+
+ factory.BuildMTOAgent(suite.DB(), []factory.Customization{
+ {Model: models.MTOAgent{
+ MTOAgentType: models.MTOAgentReceiving,
+ DeletedAt: models.TimePointer(time.Now()),
+ }},
+ {Model: move, LinkOnly: true},
+ }, nil)
+
+ searchParams := services.MoveTaskOrderFetcherParams{
+ MoveTaskOrderID: move.ID,
+ }
+
+ actualMTO, err := mtoFetcher.FetchMoveTaskOrder(suite.AppContextForTest(), &searchParams)
+ suite.NoError(err)
+
+ suite.Equal(move.ID, actualMTO.ID)
+ suite.Len(actualMTO.MTOShipments[0].MTOAgents, 0, "Expected no active agents since all are deleted")
+ })
+
+ suite.Run("Success - Move contains one MTOAgent", func() {
+ move := factory.BuildMove(suite.DB(), nil, nil)
+
+ shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{
+ {Model: move,
+ LinkOnly: true},
+ }, nil)
+
+ factory.BuildMTOAgent(suite.DB(), []factory.Customization{
+ {Model: models.MTOAgent{
+ MTOAgentType: models.MTOAgentReceiving,
+ DeletedAt: models.TimePointer(time.Now()),
+ }},
+ {Model: move, LinkOnly: true},
+ {Model: shipment, LinkOnly: true},
+ }, nil)
+
+ activeAgent := factory.BuildMTOAgent(suite.DB(), []factory.Customization{
+ {Model: models.MTOAgent{
+ FirstName: models.StringPointer("John"),
+ LastName: models.StringPointer("Doe"),
+ Email: models.StringPointer("John.doe@example.com"),
+ Phone: models.StringPointer("222-222-2222"),
+ MTOAgentType: models.MTOAgentReleasing,
+ }},
+ {Model: move, LinkOnly: true},
+ {Model: shipment, LinkOnly: true},
+ }, nil)
+
+ searchParams := services.MoveTaskOrderFetcherParams{
+ MoveTaskOrderID: move.ID,
+ }
+
+ actualMTO, err := mtoFetcher.FetchMoveTaskOrder(suite.AppContextForTest(), &searchParams)
+ suite.NoError(err)
+
+ suite.Equal(move.ID, actualMTO.ID)
+ suite.Len(actualMTO.MTOShipments[0].MTOAgents, 1, "Expected only one active agent in the result")
+
+ activeAgentReturned := actualMTO.MTOShipments[0].MTOAgents[0]
+ suite.Equal(activeAgent.FirstName, activeAgentReturned.FirstName, "First names should match")
+ suite.Equal(activeAgent.LastName, activeAgentReturned.LastName, "Last names should match")
+ suite.Equal(activeAgent.Email, activeAgentReturned.Email, "Emails should match")
+ suite.Equal(activeAgent.Phone, activeAgentReturned.Phone, "Phone numbers should match")
+ suite.Equal(activeAgent.MTOAgentType, activeAgentReturned.MTOAgentType, "Agent types should match")
+ })
+
}
func (suite *MoveTaskOrderServiceSuite) TestGetMoveTaskOrderFetcher() {
diff --git a/pkg/services/move_task_order/move_task_order_updater.go b/pkg/services/move_task_order/move_task_order_updater.go
index d0cd55aed37..463c5e33a37 100644
--- a/pkg/services/move_task_order/move_task_order_updater.go
+++ b/pkg/services/move_task_order/move_task_order_updater.go
@@ -239,11 +239,11 @@ func (o moveTaskOrderUpdater) UpdateTIORemarks(appCtx appcontext.AppContext, mov
return move, nil
}
-// MakeAvailableToPrime approves a Move, makes it available to prime, and
+// ApproveMoveAndCreateServiceItems approves a Move and
// creates Move-level service items (counseling and move management) if the
// TOO selected them. If the move received service counseling, the counseling
// service item will automatically be created without the TOO having to select it.
-func (o *moveTaskOrderUpdater) MakeAvailableToPrime(appCtx appcontext.AppContext, moveTaskOrderID uuid.UUID, eTag string,
+func (o *moveTaskOrderUpdater) ApproveMoveAndCreateServiceItems(appCtx appcontext.AppContext, moveTaskOrderID uuid.UUID, eTag string,
includeServiceCodeMS bool, includeServiceCodeCS bool) (*models.Move, error) {
searchParams := services.MoveTaskOrderFetcherParams{
@@ -263,14 +263,9 @@ func (o *moveTaskOrderUpdater) MakeAvailableToPrime(appCtx appcontext.AppContext
//When approving a shipment - remove the assigned TOO user
move.TOOAssignedID = nil
- // If the move is already been made available to prime, we will not need to approve and update the move,
- // just the provided service items.
updateMove := false
- if move.AvailableToPrimeAt == nil {
+ if move.ApprovedAt == nil {
updateMove = true
- now := time.Now()
- move.AvailableToPrimeAt = &now
-
err = o.moveRouter.Approve(appCtx, move)
if err != nil {
return &models.Move{}, apperror.NewConflictError(move.ID, err.Error())
@@ -308,6 +303,42 @@ func (o *moveTaskOrderUpdater) MakeAvailableToPrime(appCtx appcontext.AppContext
return move, nil
}
+// MakeAvailableToPrime makes the move available to prime
+func (o *moveTaskOrderUpdater) MakeAvailableToPrime(appCtx appcontext.AppContext, moveTaskOrderID uuid.UUID) (*models.Move, bool, error) {
+ var move *models.Move
+ var wasMadeAvailableToPrime = false
+
+ transactionError := appCtx.NewTransaction(func(txnAppCtx appcontext.AppContext) error {
+ searchParams := services.MoveTaskOrderFetcherParams{
+ IncludeHidden: false,
+ MoveTaskOrderID: moveTaskOrderID,
+ }
+ var err error
+ move, err = o.FetchMoveTaskOrder(txnAppCtx, &searchParams)
+ if err != nil {
+ return err
+ }
+
+ if move.AvailableToPrimeAt == nil {
+ now := time.Now()
+ move.AvailableToPrimeAt = &now
+
+ err = o.updateMove(txnAppCtx, move, order.CheckRequiredFields())
+ if err != nil {
+ return err
+ }
+ wasMadeAvailableToPrime = true
+ }
+ return nil
+ })
+
+ if transactionError != nil {
+ return &models.Move{}, false, transactionError
+ }
+
+ return move, wasMadeAvailableToPrime, nil
+}
+
func (o *moveTaskOrderUpdater) updateMove(appCtx appcontext.AppContext, move *models.Move, checks ...order.Validator) error {
if verr := order.ValidateOrder(&move.Orders, checks...); verr != nil {
return verr
diff --git a/pkg/services/move_task_order/move_task_order_updater_test.go b/pkg/services/move_task_order/move_task_order_updater_test.go
index b6368248eb0..8795b151aae 100644
--- a/pkg/services/move_task_order/move_task_order_updater_test.go
+++ b/pkg/services/move_task_order/move_task_order_updater_test.go
@@ -88,7 +88,7 @@ func (suite *MoveTaskOrderServiceSuite) TestMoveTaskOrderUpdater_UpdateStatusSer
moveRouter, setUpSignedCertificationCreatorMock(nil, nil), setUpSignedCertificationUpdaterMock(nil, nil), ppmEstimator,
)
- suite.Run("Makes move available to Prime and Removes assigned TOO office user", func() {
+ suite.Run("Completes counseling and removes assigned SC office user", func() {
session := suite.AppContextWithSessionForTest(&auth.Session{
ApplicationName: auth.OfficeApp,
OfficeUserID: uuid.Must(uuid.NewV4()),
@@ -697,7 +697,7 @@ func (suite *MoveTaskOrderServiceSuite) TestMoveTaskOrderUpdater_ShowHide() {
})
}
-func (suite *MoveTaskOrderServiceSuite) TestMoveTaskOrderUpdater_MakeAvailableToPrime() {
+func (suite *MoveTaskOrderServiceSuite) TestMoveTaskOrderUpdater_ApproveMoveAndCreateServiceItems() {
ppmEstimator := &mocks.PPMEstimator{}
setupTestData := func() models.OfficeUser {
@@ -791,13 +791,12 @@ func (suite *MoveTaskOrderServiceSuite) TestMoveTaskOrderUpdater_MakeAvailableTo
eTag := etag.GenerateEtag(move.UpdatedAt)
fetchedMove := models.Move{}
- _, err := mtoUpdater.MakeAvailableToPrime(suite.AppContextForTest(), move.ID, eTag, true, true)
+ _, err := mtoUpdater.ApproveMoveAndCreateServiceItems(suite.AppContextForTest(), move.ID, eTag, true, true)
mockserviceItemCreator.AssertNumberOfCalls(suite.T(), "CreateMTOServiceItem", 0)
suite.Error(err)
err = suite.DB().Find(&fetchedMove, move.ID)
suite.NoError(err)
- suite.Nil(fetchedMove.AvailableToPrimeAt)
suite.Nil(fetchedMove.ApprovedAt)
})
@@ -810,14 +809,14 @@ func (suite *MoveTaskOrderServiceSuite) TestMoveTaskOrderUpdater_MakeAvailableTo
move := factory.BuildSubmittedMove(suite.DB(), nil, nil)
eTag := etag.GenerateEtag(time.Now())
- _, err := mtoUpdater.MakeAvailableToPrime(suite.AppContextForTest(), move.ID, eTag, true, true)
+ _, err := mtoUpdater.ApproveMoveAndCreateServiceItems(suite.AppContextForTest(), move.ID, eTag, true, true)
mockserviceItemCreator.AssertNumberOfCalls(suite.T(), "CreateMTOServiceItem", 0)
suite.Error(err)
suite.IsType(apperror.PreconditionFailedError{}, err)
})
- suite.Run("Makes move available to Prime and creates Move management and Service counseling service items when both are specified", func() {
+ suite.Run("Approves a move and creates Move management and Service counseling service items when both are specified", func() {
queryBuilder := query.NewQueryBuilder()
moveRouter := moverouter.NewMoveRouter()
planner := &routemocks.Planner{}
@@ -835,13 +834,11 @@ func (suite *MoveTaskOrderServiceSuite) TestMoveTaskOrderUpdater_MakeAvailableTo
fetchedMove := models.Move{}
var serviceItems models.MTOServiceItems
- suite.Nil(move.AvailableToPrimeAt)
suite.Nil(move.ApprovedAt)
- updatedMove, err := mtoUpdater.MakeAvailableToPrime(suite.AppContextForTest(), move.ID, eTag, true, true)
+ updatedMove, err := mtoUpdater.ApproveMoveAndCreateServiceItems(suite.AppContextForTest(), move.ID, eTag, true, true)
suite.NoError(err)
- suite.NotNil(updatedMove.AvailableToPrimeAt)
suite.NotNil(updatedMove.ApprovedAt)
suite.Equal(models.MoveStatusAPPROVED, updatedMove.Status)
err = suite.DB().Eager("ReService").Where("move_id = ?", move.ID).All(&serviceItems)
@@ -851,11 +848,10 @@ func (suite *MoveTaskOrderServiceSuite) TestMoveTaskOrderUpdater_MakeAvailableTo
suite.True(suite.containsServiceCode(serviceItems, models.ReServiceCodeCS), fmt.Sprintf("Expected to find reServiceCode, %s, in array.", models.ReServiceCodeCS))
err = suite.DB().Find(&fetchedMove, move.ID)
suite.NoError(err)
- suite.NotNil(fetchedMove.AvailableToPrimeAt)
suite.NotNil(fetchedMove.ApprovedAt)
suite.Equal(models.MoveStatusAPPROVED, fetchedMove.Status)
})
- suite.Run("Makes move available to Prime and Removes assigned TOO office user", func() {
+ suite.Run("Approves a move and removes assigned TOO office user", func() {
queryBuilder := query.NewQueryBuilder()
moveRouter := moverouter.NewMoveRouter()
planner := &routemocks.Planner{}
@@ -876,13 +872,13 @@ func (suite *MoveTaskOrderServiceSuite) TestMoveTaskOrderUpdater_MakeAvailableTo
serviceItemCreator := mtoserviceitem.NewMTOServiceItemCreator(planner, queryBuilder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer())
mtoUpdater := mt.NewMoveTaskOrderUpdater(queryBuilder, serviceItemCreator, moveRouter, setUpSignedCertificationCreatorMock(nil, nil), setUpSignedCertificationUpdaterMock(nil, nil), ppmEstimator)
eTag := etag.GenerateEtag(move.UpdatedAt)
- updatedMove, err := mtoUpdater.MakeAvailableToPrime(suite.AppContextForTest(), move.ID, eTag, false, false)
+ updatedMove, err := mtoUpdater.ApproveMoveAndCreateServiceItems(suite.AppContextForTest(), move.ID, eTag, false, false)
suite.NoError(err)
suite.Nil(updatedMove.TOOAssignedID)
})
- suite.Run("Makes move available to Prime and only creates Move management when it's the only one specified", func() {
+ suite.Run("Approves a move and only creates Move management when it's the only one specified", func() {
queryBuilder := query.NewQueryBuilder()
moveRouter := moverouter.NewMoveRouter()
planner := &routemocks.Planner{}
@@ -900,15 +896,13 @@ func (suite *MoveTaskOrderServiceSuite) TestMoveTaskOrderUpdater_MakeAvailableTo
fetchedMove := models.Move{}
var serviceItems models.MTOServiceItems
- suite.Nil(move.AvailableToPrimeAt)
suite.Nil(move.ApprovedAt)
- _, err := mtoUpdater.MakeAvailableToPrime(suite.AppContextForTest(), move.ID, eTag, true, false)
+ _, err := mtoUpdater.ApproveMoveAndCreateServiceItems(suite.AppContextForTest(), move.ID, eTag, true, false)
suite.NoError(err)
err = suite.DB().Find(&fetchedMove, move.ID)
suite.NoError(err)
- suite.NotNil(fetchedMove.AvailableToPrimeAt)
suite.NotNil(fetchedMove.ApprovedAt)
err = suite.DB().Eager("ReService").Where("move_id = ?", move.ID).All(&serviceItems)
suite.NoError(err)
@@ -917,7 +911,7 @@ func (suite *MoveTaskOrderServiceSuite) TestMoveTaskOrderUpdater_MakeAvailableTo
suite.False(suite.containsServiceCode(serviceItems, models.ReServiceCodeCS), fmt.Sprintf("Expected to find reServiceCode, %s, in array.", models.ReServiceCodeCS))
})
- suite.Run("Makes move available to Prime and only creates CS service item when it's the only one specified", func() {
+ suite.Run("Approves a move and only creates CS service item when it's the only one specified", func() {
queryBuilder := query.NewQueryBuilder()
moveRouter := moverouter.NewMoveRouter()
planner := &routemocks.Planner{}
@@ -929,15 +923,13 @@ func (suite *MoveTaskOrderServiceSuite) TestMoveTaskOrderUpdater_MakeAvailableTo
fetchedMove := models.Move{}
var serviceItems models.MTOServiceItems
- suite.Nil(move.AvailableToPrimeAt)
suite.Nil(move.ApprovedAt)
- _, err := mtoUpdater.MakeAvailableToPrime(suite.AppContextForTest(), move.ID, eTag, false, true)
+ _, err := mtoUpdater.ApproveMoveAndCreateServiceItems(suite.AppContextForTest(), move.ID, eTag, false, true)
suite.NoError(err)
err = suite.DB().Find(&fetchedMove, move.ID)
suite.NoError(err)
- suite.NotNil(fetchedMove.AvailableToPrimeAt)
suite.NotNil(fetchedMove.ApprovedAt)
err = suite.DB().Eager("ReService").Where("move_id = ?", move.ID).All(&serviceItems)
suite.NoError(err)
@@ -956,20 +948,18 @@ func (suite *MoveTaskOrderServiceSuite) TestMoveTaskOrderUpdater_MakeAvailableTo
eTag := etag.GenerateEtag(move.UpdatedAt)
fetchedMove := models.Move{}
- suite.Nil(move.AvailableToPrimeAt)
suite.Nil(move.ApprovedAt)
- _, err := mtoUpdater.MakeAvailableToPrime(suite.AppContextForTest(), move.ID, eTag, false, false)
+ _, err := mtoUpdater.ApproveMoveAndCreateServiceItems(suite.AppContextForTest(), move.ID, eTag, false, false)
mockserviceItemCreator.AssertNumberOfCalls(suite.T(), "CreateMTOServiceItem", 0)
suite.NoError(err)
err = suite.DB().Find(&fetchedMove, move.ID)
suite.NoError(err)
- suite.NotNil(fetchedMove.AvailableToPrimeAt)
suite.NotNil(fetchedMove.ApprovedAt)
})
- suite.Run("Does not make move available to prime if Order is missing required fields", func() {
+ suite.Run("Does not approve a move if Order is missing required fields", func() {
mockserviceItemCreator := &mocks.MTOServiceItemCreator{}
queryBuilder := query.NewQueryBuilder()
moveRouter := moverouter.NewMoveRouter()
@@ -985,18 +975,134 @@ func (suite *MoveTaskOrderServiceSuite) TestMoveTaskOrderUpdater_MakeAvailableTo
eTag := etag.GenerateEtag(move.UpdatedAt)
fetchedMove := models.Move{}
- _, err := mtoUpdater.MakeAvailableToPrime(suite.AppContextForTest(), move.ID, eTag, true, true)
+ _, err := mtoUpdater.ApproveMoveAndCreateServiceItems(suite.AppContextForTest(), move.ID, eTag, true, true)
mockserviceItemCreator.AssertNumberOfCalls(suite.T(), "CreateMTOServiceItem", 0)
suite.Error(err)
suite.IsType(apperror.InvalidInputError{}, err)
err = suite.DB().Find(&fetchedMove, move.ID)
suite.NoError(err)
- suite.Nil(fetchedMove.AvailableToPrimeAt)
suite.Nil(fetchedMove.ApprovedAt)
})
}
+func (suite *MoveTaskOrderServiceSuite) TestMoveTaskOrderUpdater_MakeAvailableToPrime() {
+ ppmEstimator := &mocks.PPMEstimator{}
+
+ setUpSignedCertificationCreatorMock := func(returnValue ...interface{}) services.SignedCertificationCreator {
+ mockCreator := &mocks.SignedCertificationCreator{}
+
+ mockCreator.On(
+ "CreateSignedCertification",
+ mock.AnythingOfType("*appcontext.appContext"),
+ mock.AnythingOfType("models.SignedCertification"),
+ ).Return(returnValue...)
+
+ return mockCreator
+ }
+
+ setUpSignedCertificationUpdaterMock := func(returnValue ...interface{}) services.SignedCertificationUpdater {
+ mockUpdater := &mocks.SignedCertificationUpdater{}
+
+ mockUpdater.On(
+ "UpdateSignedCertification",
+ mock.AnythingOfType("*appcontext.appContext"),
+ mock.AnythingOfType("models.SignedCertification"),
+ mock.AnythingOfType("string"),
+ ).Return(returnValue...)
+
+ return mockUpdater
+ }
+
+ setupPricerData := func() {
+ contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{})
+
+ 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)
+ contractYear := testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{
+ ReContractYear: models.ReContractYear{
+ Contract: contract,
+ ContractID: contract.ID,
+ StartDate: startDate,
+ EndDate: endDate,
+ Escalation: 1.0,
+ EscalationCompounded: 1.0,
+ },
+ })
+
+ service := factory.FetchReServiceByCode(suite.DB(), "MS")
+ msTaskOrderFee := models.ReTaskOrderFee{
+ ContractYearID: contractYear.ID,
+ ServiceID: service.ID,
+ PriceCents: 90000,
+ }
+ suite.MustSave(&msTaskOrderFee)
+
+ service = factory.FetchReServiceByCode(suite.DB(), "CS")
+ csTaskOrderFee := models.ReTaskOrderFee{
+ ContractYearID: contractYear.ID,
+ ServiceID: service.ID,
+ PriceCents: 90000,
+ }
+ suite.MustSave(&csTaskOrderFee)
+ }
+
+ suite.PreloadData(setupPricerData)
+
+ suite.Run("Successfully makes move available to Prime", func() {
+ mockserviceItemCreator := &mocks.MTOServiceItemCreator{}
+ queryBuilder := query.NewQueryBuilder()
+ moveRouter := moverouter.NewMoveRouter()
+ mtoUpdater := mt.NewMoveTaskOrderUpdater(queryBuilder, mockserviceItemCreator, moveRouter, setUpSignedCertificationCreatorMock(nil, nil), setUpSignedCertificationUpdaterMock(nil, nil), ppmEstimator)
+ move := factory.BuildMove(suite.DB(), nil, nil)
+
+ result, wasUpdated, err := mtoUpdater.MakeAvailableToPrime(suite.AppContextForTest(), move.ID)
+
+ suite.NoError(err)
+ suite.NotNil(result)
+ suite.True(wasUpdated)
+ suite.NotNil(result.AvailableToPrimeAt)
+ })
+
+ suite.Run("Does not update move that is already available to Prime", func() {
+ mockserviceItemCreator := &mocks.MTOServiceItemCreator{}
+ queryBuilder := query.NewQueryBuilder()
+ moveRouter := moverouter.NewMoveRouter()
+ mtoUpdater := mt.NewMoveTaskOrderUpdater(queryBuilder, mockserviceItemCreator, moveRouter, setUpSignedCertificationCreatorMock(nil, nil), setUpSignedCertificationUpdaterMock(nil, nil), ppmEstimator)
+
+ now := time.Now()
+
+ move := factory.BuildMove(suite.DB(), []factory.Customization{
+ {
+ Model: models.Move{
+ AvailableToPrimeAt: &now,
+ },
+ },
+ }, nil)
+
+ result, wasUpdated, err := mtoUpdater.MakeAvailableToPrime(suite.AppContextForTest(), move.ID)
+
+ suite.NoError(err)
+ suite.NotNil(result)
+ suite.False(wasUpdated)
+ suite.WithinDuration(now.UTC(), result.AvailableToPrimeAt.UTC(), time.Second)
+ })
+
+ suite.Run("Returns error if move is not found", func() {
+ mockserviceItemCreator := &mocks.MTOServiceItemCreator{}
+ queryBuilder := query.NewQueryBuilder()
+ moveRouter := moverouter.NewMoveRouter()
+ mtoUpdater := mt.NewMoveTaskOrderUpdater(queryBuilder, mockserviceItemCreator, moveRouter, setUpSignedCertificationCreatorMock(nil, nil), setUpSignedCertificationUpdaterMock(nil, nil), ppmEstimator)
+ moveID := uuid.Must(uuid.NewV4())
+
+ _, wasUpdated, err := mtoUpdater.MakeAvailableToPrime(suite.AppContextForTest(), moveID)
+
+ suite.Error(err)
+ suite.False(wasUpdated)
+ suite.IsType(apperror.NotFoundError{}, err)
+ })
+}
+
func (suite *MoveTaskOrderServiceSuite) TestMoveTaskOrderUpdater_BillableWeightsReviewedAt() {
ppmEstimator := &mocks.PPMEstimator{}
setUpSignedCertificationCreatorMock := func(returnValue ...interface{}) services.SignedCertificationCreator {
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 88ce0cde8c2..e71a4d5249d 100644
--- a/pkg/services/mto_service_item/mto_service_item_creator.go
+++ b/pkg/services/mto_service_item/mto_service_item_creator.go
@@ -375,7 +375,7 @@ func (o *mtoServiceItemCreator) CreateMTOServiceItem(appCtx appcontext.AppContex
err := o.checkDuplicateServiceCodes(appCtx, serviceItem)
if err != nil {
appCtx.Logger().Error(fmt.Sprintf("Error trying to create a duplicate MS service item for move ID: %s", move.ID), zap.Error(err))
- return nil, nil, err
+ return &createdServiceItems, nil, nil
}
}
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 619be14b026..fe102abe17a 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
@@ -503,7 +503,7 @@ func (suite *MTOServiceItemServiceSuite) TestCreateMTOServiceItem() {
// Under test: CreateMTOServiceItem function
// Set up: Then create service items for CS or MS. Then try to create again.
// Expected outcome:
- // Fail, MS cannot be created if there is one already created for the move.
+ // Return empty MTOServiceItems and continue, MS cannot be created if there is one already created for the move.
contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{})
@@ -555,10 +555,9 @@ func (suite *MTOServiceItemServiceSuite) TestCreateMTOServiceItem() {
createdServiceItemsMSDupe, _, err := creator.CreateMTOServiceItem(suite.AppContextForTest(), &serviceItemMS)
- fakeMTOShipmentRouterErr := apperror.NewConflictError(serviceItemMS.ID, fmt.Sprintf("for creating a service item. A service item with reServiceCode %s already exists for this move and/or shipment.", serviceItemMS.ReService.Code))
-
- suite.Nil(createdServiceItemsMSDupe)
- suite.Equal(fakeMTOShipmentRouterErr.Error(), err.Error())
+ suite.Nil(err)
+ suite.NotNil(createdServiceItemsMSDupe)
+ suite.Equal(*createdServiceItemsMSDupe, models.MTOServiceItems(nil))
})
// Should not be able to create CS or MS service items unless a shipment within the move has a requested pickup date
@@ -820,7 +819,7 @@ func (suite *MTOServiceItemServiceSuite) TestCreateMTOServiceItem() {
})
// If the service item we're trying to create is shuttle service and there is no estimated weight, it fails.
- suite.Run("MTOServiceItemShuttle no prime weight is okay", func() {
+ suite.Run("MTOServiceItemDomesticShuttle no prime weight is okay", func() {
// TESTCASE SCENARIO
// Under test: CreateMTOServiceItem function
// Set up: Create DDSHUT service item on a shipment without estimated weight
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 25a6768c335..989c892c20f 100644
--- a/pkg/services/mto_service_item/mto_service_item_validators.go
+++ b/pkg/services/mto_service_item/mto_service_item_validators.go
@@ -344,13 +344,10 @@ func (v *updateMTOServiceItemData) checkOldServiceItemStatus(_ appcontext.AppCon
}
if slices.Contains(allAccessorialServiceItemsToCheck, serviceItemData.oldServiceItem.ReService.Code) {
+ invalidFieldChange := false
if serviceItemData.oldServiceItem.Status == models.MTOServiceItemStatusRejected {
return nil
- } else if serviceItemData.oldServiceItem.Status == models.MTOServiceItemStatusApproved {
-
- invalidFieldChange := false
- // Fields that are not allowed to change when status is approved
-
+ } else if serviceItemData.oldServiceItem.Status == models.MTOServiceItemStatusSubmitted || serviceItemData.oldServiceItem.Status == models.MTOServiceItemStatusApproved {
if serviceItemData.updatedServiceItem.ReService.Code.String() != "" && serviceItemData.updatedServiceItem.ReService.Code.String() != serviceItemData.oldServiceItem.ReService.Code.String() {
invalidFieldChange = true
}
@@ -359,6 +356,10 @@ func (v *updateMTOServiceItemData) checkOldServiceItemStatus(_ appcontext.AppCon
invalidFieldChange = true
}
+ if serviceItemData.updatedServiceItem.EstimatedWeight != nil {
+ invalidFieldChange = true
+ }
+
if serviceItemData.updatedServiceItem.RequestedApprovalsRequestedStatus != nil {
invalidFieldChange = true
}
@@ -366,10 +367,10 @@ func (v *updateMTOServiceItemData) checkOldServiceItemStatus(_ appcontext.AppCon
if invalidFieldChange {
return apperror.NewConflictError(serviceItemData.oldServiceItem.ID,
"- one or more fields is not allowed to be updated when the shuttle service item has an approved status.")
+ } else {
+ return nil
}
- return apperror.NewConflictError(serviceItemData.oldServiceItem.ID,
- "- unknown field or fields attempting to be updated.")
} else {
return apperror.NewConflictError(serviceItemData.oldServiceItem.ID,
"- this shuttle service item cannot be updated because the status is not in an editable state.")
diff --git a/pkg/services/mto_shipment.go b/pkg/services/mto_shipment.go
index 187d290cb9f..fab8507e43d 100644
--- a/pkg/services/mto_shipment.go
+++ b/pkg/services/mto_shipment.go
@@ -49,11 +49,17 @@ type ShipmentDeleter interface {
DeleteShipment(appCtx appcontext.AppContext, shipmentID uuid.UUID) (uuid.UUID, error)
}
+type ShipmentIdWithEtag struct {
+ ShipmentID uuid.UUID
+ ETag string
+}
+
// ShipmentApprover is the service object interface for approving a shipment
//
//go:generate mockery --name ShipmentApprover
type ShipmentApprover interface {
ApproveShipment(appCtx appcontext.AppContext, shipmentID uuid.UUID, eTag string) (*models.MTOShipment, error)
+ ApproveShipments(appCtx appcontext.AppContext, shipments []ShipmentIdWithEtag) (*[]models.MTOShipment, error)
}
// ShipmentDiversionRequester is the service object interface for requesting a shipment diversion
@@ -163,5 +169,5 @@ type ShipmentPostalCodeRateArea struct {
//
//go:generate mockery --name ShipmentRateAreaFinder
type ShipmentRateAreaFinder interface {
- GetPrimeMoveShipmentOconusRateArea(appCtx appcontext.AppContext, move models.Move) (*[]ShipmentPostalCodeRateArea, error)
+ GetPrimeMoveShipmentRateAreas(appCtx appcontext.AppContext, move models.Move) (*[]ShipmentPostalCodeRateArea, error)
}
diff --git a/pkg/services/mto_shipment/mto_shipment_rate_area_fetcher.go b/pkg/services/mto_shipment/mto_shipment_rate_area_fetcher.go
index bbc290dba9e..ed7381c60a9 100644
--- a/pkg/services/mto_shipment/mto_shipment_rate_area_fetcher.go
+++ b/pkg/services/mto_shipment/mto_shipment_rate_area_fetcher.go
@@ -22,7 +22,7 @@ func NewMTOShipmentRateAreaFetcher() services.ShipmentRateAreaFinder {
return &mtoShipmentRateAreaFetcher{}
}
-func (f mtoShipmentRateAreaFetcher) GetPrimeMoveShipmentOconusRateArea(appCtx appcontext.AppContext, moveTaskOrder models.Move) (*[]services.ShipmentPostalCodeRateArea, error) {
+func (f mtoShipmentRateAreaFetcher) GetPrimeMoveShipmentRateAreas(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")
}
@@ -33,53 +33,87 @@ func (f mtoShipmentRateAreaFetcher) GetPrimeMoveShipmentOconusRateArea(appCtx ap
}
// build set of postalCodes to fetch rateArea for
- var postalCodes = make([]string, 0)
+ var oconusPostalCodes = make([]string, 0)
+ var conusPostalCodes = 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 !slices.Contains(oconusPostalCodes, shipment.PickupAddress.PostalCode) &&
+ shipment.PickupAddress.IsOconus != nil && *shipment.PickupAddress.IsOconus {
+ oconusPostalCodes = append(oconusPostalCodes, shipment.PickupAddress.PostalCode)
+ } else if !slices.Contains(conusPostalCodes, shipment.PickupAddress.PostalCode) {
+ conusPostalCodes = append(conusPostalCodes, shipment.PickupAddress.PostalCode)
}
}
if shipment.DestinationAddress != nil {
- if !slices.Contains(postalCodes, shipment.DestinationAddress.PostalCode) {
- postalCodes = append(postalCodes, shipment.DestinationAddress.PostalCode)
+ if !slices.Contains(oconusPostalCodes, shipment.DestinationAddress.PostalCode) &&
+ shipment.DestinationAddress.IsOconus != nil && *shipment.DestinationAddress.IsOconus {
+ oconusPostalCodes = append(oconusPostalCodes, shipment.DestinationAddress.PostalCode)
+ } else if !slices.Contains(conusPostalCodes, shipment.DestinationAddress.PostalCode) {
+ conusPostalCodes = append(conusPostalCodes, 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 !slices.Contains(oconusPostalCodes, shipment.PPMShipment.PickupAddress.PostalCode) &&
+ shipment.PPMShipment.PickupAddress.IsOconus != nil && *shipment.PPMShipment.PickupAddress.IsOconus {
+ oconusPostalCodes = append(oconusPostalCodes, shipment.PPMShipment.PickupAddress.PostalCode)
+ } else if !slices.Contains(conusPostalCodes, shipment.PPMShipment.PickupAddress.PostalCode) {
+ conusPostalCodes = append(conusPostalCodes, shipment.PPMShipment.PickupAddress.PostalCode)
}
}
if shipment.PPMShipment.DestinationAddress != nil {
- if !slices.Contains(postalCodes, shipment.PPMShipment.DestinationAddress.PostalCode) {
- postalCodes = append(postalCodes, shipment.PPMShipment.DestinationAddress.PostalCode)
+ if !slices.Contains(oconusPostalCodes, shipment.PPMShipment.DestinationAddress.PostalCode) &&
+ shipment.PPMShipment.DestinationAddress.IsOconus != nil && *shipment.PPMShipment.DestinationAddress.IsOconus {
+ oconusPostalCodes = append(oconusPostalCodes, shipment.PPMShipment.DestinationAddress.PostalCode)
+ } else if !slices.Contains(conusPostalCodes, shipment.PPMShipment.DestinationAddress.PostalCode) {
+ conusPostalCodes = append(conusPostalCodes, shipment.PPMShipment.DestinationAddress.PostalCode)
}
}
}
}
- ra, err := fetchRateArea(appCtx, contract.ID, postalCodes)
+ ora, err := fetchOconusRateAreas(appCtx, contract.ID, oconusPostalCodes)
if err != nil {
return nil, err
}
- return ra, nil
+ cra, err := fetchConusRateAreas(appCtx, contract.ID, conusPostalCodes)
+ if err != nil {
+ return nil, err
+ }
+
+ ra := append(*ora, *cra...)
+ return &ra, nil
+}
+
+func fetchOconusRateAreas(appCtx appcontext.AppContext, contractId uuid.UUID, postalCodes []string) (*[]services.ShipmentPostalCodeRateArea, error) {
+ var rateAreasMap = make([]services.ShipmentPostalCodeRateArea, 0)
+ for _, postalCode := range postalCodes {
+ ra, err := fetchOconusRateAreaByPostalCode(appCtx, contractId, postalCode)
+ if err != nil {
+ if err != sql.ErrNoRows {
+ return nil, apperror.NewQueryError("GetRateArea", err, fmt.Sprintf("error retrieving rateArea for contractId:%s, postalCode:%s", contractId, postalCode))
+ }
+ } else {
+ rateAreasMap = append(rateAreasMap, services.ShipmentPostalCodeRateArea{PostalCode: postalCode, RateArea: ra})
+ }
+ }
+ return &rateAreasMap, 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)
+func fetchConusRateAreas(appCtx appcontext.AppContext, contractId uuid.UUID, postalCodes []string) (*[]services.ShipmentPostalCodeRateArea, error) {
+ var rateAreasMap = make([]services.ShipmentPostalCodeRateArea, 0)
+ for _, postalCode := range postalCodes {
+ ra, err := models.FetchConusRateAreaByPostalCode(appCtx.DB(), postalCode, contractId)
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))
+ return nil, apperror.NewQueryError("GetRateArea", err, fmt.Sprintf("error retrieving rateArea for contractId:%s, postalCode:%s", contractId, postalCode))
}
} else {
- rateArea = append(rateArea, services.ShipmentPostalCodeRateArea{PostalCode: code, RateArea: ra})
+ rateAreasMap = append(rateAreasMap, services.ShipmentPostalCodeRateArea{PostalCode: postalCode, RateArea: ra})
}
}
- return &rateArea, nil
+ return &rateAreasMap, nil
}
func fetchOconusRateAreaByPostalCode(appCtx appcontext.AppContext, contractId uuid.UUID, postalCode string) (*models.ReRateArea, error) {
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
index 976e03ac2e1..e94fea2c4a1 100644
--- a/pkg/services/mto_shipment/mto_shipment_rate_area_fetcher_test.go
+++ b/pkg/services/mto_shipment/mto_shipment_rate_area_fetcher_test.go
@@ -19,6 +19,9 @@ const testContractName = "Test Contract"
const fairbanksAlaskaPostalCode = "99716"
const anchorageAlaskaPostalCode = "99521"
const wasillaAlaskaPostalCode = "99652"
+const beverlyHillsCAPostalCode = "90210"
+const sanDiegoCAPostalCode = "92075"
+const brooklynNYPostalCode = "11220"
func (suite *MTOShipmentServiceSuite) TestGetMoveShipmentRateArea() {
shipmentRateAreaFetcher := NewMTOShipmentRateAreaFetcher()
@@ -34,12 +37,14 @@ func (suite *MTOShipmentServiceSuite) TestGetMoveShipmentRateArea() {
City: "Fairbanks",
State: "AK",
PostalCode: fairbanksAlaskaPostalCode,
+ IsOconus: models.BoolPointer(true),
},
DestinationAddress: &models.Address{
StreetAddress1: "123 Main St",
City: "Anchorage",
State: "AK",
PostalCode: anchorageAlaskaPostalCode,
+ IsOconus: models.BoolPointer(true),
},
},
models.MTOShipment{
@@ -47,13 +52,15 @@ func (suite *MTOShipmentServiceSuite) TestGetMoveShipmentRateArea() {
StreetAddress1: "123 Main St",
City: "Beverly Hills",
State: "CA",
- PostalCode: "90210",
+ PostalCode: beverlyHillsCAPostalCode,
+ IsOconus: models.BoolPointer(false),
},
DestinationAddress: &models.Address{
StreetAddress1: "123 Main St",
City: "San Diego",
State: "CA",
- PostalCode: "92075",
+ PostalCode: sanDiegoCAPostalCode,
+ IsOconus: models.BoolPointer(false),
},
},
models.MTOShipment{
@@ -63,32 +70,20 @@ func (suite *MTOShipmentServiceSuite) TestGetMoveShipmentRateArea() {
City: "Wasilla",
State: "AK",
PostalCode: wasillaAlaskaPostalCode,
+ IsOconus: models.BoolPointer(true),
},
DestinationAddress: &models.Address{
StreetAddress1: "123 Main St",
City: "Wasilla",
State: "AK",
PostalCode: wasillaAlaskaPostalCode,
+ IsOconus: models.BoolPointer(true),
},
},
},
},
}
- // 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{
@@ -146,15 +141,46 @@ func (suite *MTOShipmentServiceSuite) TestGetMoveShipmentRateArea() {
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)
+ setupDomesticRateAreaAndZip3s := func(rateAreaCode string, rateAreaName string, postalCodes map[string]string, domesticServiceArea models.ReDomesticServiceArea) (models.ReRateArea, error) {
+ rateArea := models.ReRateArea{
+ ID: uuid.Must(uuid.NewV4()),
+ ContractID: domesticServiceArea.ContractID,
+ IsOconus: false,
+ Code: rateAreaCode,
+ Name: rateAreaName,
+ Contract: domesticServiceArea.Contract,
+ }
+ verrs, err := suite.DB().ValidateAndCreate(&rateArea)
+ if verrs.HasAny() {
+ return rateArea, verrs
+ }
+ if err != nil {
+ return rateArea, err
+ }
- shipmentPostalCodeRateArea, err := shipmentRateAreaFetcher.GetPrimeMoveShipmentOconusRateArea(suite.AppContextForTest(), testMove)
- suite.NotNil(shipmentPostalCodeRateArea)
- suite.FatalNoError(err)
- suite.Equal(3, len(*shipmentPostalCodeRateArea))
+ for postalCode, basePointCity := range postalCodes {
+ zip3 := models.ReZip3{
+ ID: uuid.Must(uuid.NewV4()),
+ ContractID: domesticServiceArea.ContractID,
+ Contract: domesticServiceArea.Contract,
+ Zip3: postalCode[0:3],
+ RateAreaID: models.UUIDPointer(rateArea.ID),
+ HasMultipleRateAreas: false,
+ BasePointCity: basePointCity,
+ State: "ST",
+ DomesticServiceAreaID: domesticServiceArea.ID,
+ }
+ verrs, err = suite.DB().ValidateAndCreate(&zip3)
+ if verrs.HasAny() {
+ return rateArea, verrs
+ }
+ if err != nil {
+ return rateArea, err
+ }
+ }
+
+ return rateArea, nil
+ }
isRateAreaEquals := func(expectedRateArea models.ReRateArea, postalCode string, shipmentPostalCodeRateArea *[]services.ShipmentPostalCodeRateArea) bool {
var shipmentPostalCodeRateAreaLookupMap = make(map[string]services.ShipmentPostalCodeRateArea)
@@ -167,16 +193,60 @@ func (suite *MTOShipmentServiceSuite) TestGetMoveShipmentRateArea() {
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))
+ // create test contract
+ contract, err := suite.createContract(suite.AppContextForTest(), testContractCode, testContractName)
+ suite.NotNil(contract)
+ suite.FatalNoError(err)
- suite.Equal(false, isRateAreaEquals(rateArea2, fairbanksAlaskaPostalCode, shipmentPostalCodeRateArea))
- suite.Equal(false, isRateAreaEquals(rateArea2, anchorageAlaskaPostalCode, shipmentPostalCodeRateArea))
- suite.Equal(false, isRateAreaEquals(rateArea1, wasillaAlaskaPostalCode, shipmentPostalCodeRateArea))
+ // setup contract year within availableToPrimeAtTime time
+ testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{
+ ReContractYear: models.ReContractYear{
+ StartDate: availableToPrimeAtTime,
+ EndDate: time.Now(),
+ ContractID: contract.ID,
+ },
+ })
+
+ domServiceArea := testdatagen.MakeReDomesticServiceArea(suite.DB(), testdatagen.Assertions{
+ ReDomesticServiceArea: models.ReDomesticServiceArea{
+ ContractID: contract.ID,
+ },
+ })
+
+ // 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)
+
+ rateAreaCA, err := setupDomesticRateAreaAndZip3s("US88", "California-South", map[string]string{beverlyHillsCAPostalCode: "Beverly Hills", sanDiegoCAPostalCode: "San Diego"}, domServiceArea)
+ if err != nil {
+ suite.Fail(err.Error())
+ }
+
+ shipmentPostalCodeRateAreas, err := shipmentRateAreaFetcher.GetPrimeMoveShipmentRateAreas(suite.AppContextForTest(), testMove)
+ suite.NotNil(shipmentPostalCodeRateAreas)
+ suite.FatalNoError(err)
+ suite.Equal(5, len(*shipmentPostalCodeRateAreas))
+
+ suite.Equal(true, isRateAreaEquals(rateArea1, fairbanksAlaskaPostalCode, shipmentPostalCodeRateAreas))
+ suite.Equal(true, isRateAreaEquals(rateArea1, anchorageAlaskaPostalCode, shipmentPostalCodeRateAreas))
+ suite.Equal(true, isRateAreaEquals(rateArea2, wasillaAlaskaPostalCode, shipmentPostalCodeRateAreas))
+ suite.Equal(true, isRateAreaEquals(rateAreaCA, beverlyHillsCAPostalCode, shipmentPostalCodeRateAreas))
+ suite.Equal(true, isRateAreaEquals(rateAreaCA, sanDiegoCAPostalCode, shipmentPostalCodeRateAreas))
+
+ suite.Equal(false, isRateAreaEquals(rateArea2, fairbanksAlaskaPostalCode, shipmentPostalCodeRateAreas))
+ suite.Equal(false, isRateAreaEquals(rateArea2, anchorageAlaskaPostalCode, shipmentPostalCodeRateAreas))
+ suite.Equal(false, isRateAreaEquals(rateArea1, wasillaAlaskaPostalCode, shipmentPostalCodeRateAreas))
+ suite.Equal(false, isRateAreaEquals(rateAreaCA, fairbanksAlaskaPostalCode, shipmentPostalCodeRateAreas))
+ suite.Equal(false, isRateAreaEquals(rateAreaCA, anchorageAlaskaPostalCode, shipmentPostalCodeRateAreas))
+ suite.Equal(false, isRateAreaEquals(rateAreaCA, wasillaAlaskaPostalCode, shipmentPostalCodeRateAreas))
+ suite.Equal(false, isRateAreaEquals(rateArea1, beverlyHillsCAPostalCode, shipmentPostalCodeRateAreas))
+ suite.Equal(false, isRateAreaEquals(rateArea1, sanDiegoCAPostalCode, shipmentPostalCodeRateAreas))
+ suite.Equal(false, isRateAreaEquals(rateArea2, beverlyHillsCAPostalCode, shipmentPostalCodeRateAreas))
+ suite.Equal(false, isRateAreaEquals(rateArea2, sanDiegoCAPostalCode, shipmentPostalCodeRateAreas))
})
- suite.Run("no oconus rateArea found returns empty array", func() {
+ suite.Run("Returns matching CONUS rate areas", func() {
availableToPrimeAtTime := time.Now().Add(-500 * time.Hour)
testMove := models.Move{
AvailableToPrimeAt: &availableToPrimeAtTime,
@@ -186,52 +256,107 @@ func (suite *MTOShipmentServiceSuite) TestGetMoveShipmentRateArea() {
StreetAddress1: "123 Main St",
City: "Beverly Hills",
State: "CA",
- PostalCode: "90210",
+ PostalCode: beverlyHillsCAPostalCode,
+ IsOconus: models.BoolPointer(false),
},
DestinationAddress: &models.Address{
StreetAddress1: "123 Main St",
City: "San Diego",
State: "CA",
- PostalCode: "92075",
+ PostalCode: sanDiegoCAPostalCode,
+ IsOconus: models.BoolPointer(false),
},
},
models.MTOShipment{
PPMShipment: &models.PPMShipment{
PickupAddress: &models.Address{
StreetAddress1: "123 Main St",
- City: "NY",
+ City: "Brooklyn",
State: "NY",
- PostalCode: "11220",
+ PostalCode: brooklynNYPostalCode,
+ IsOconus: models.BoolPointer(false),
},
DestinationAddress: &models.Address{
StreetAddress1: "123 Main St",
City: "Beverly Hills",
State: "CA",
- PostalCode: "90210",
+ PostalCode: beverlyHillsCAPostalCode,
+ IsOconus: models.BoolPointer(false),
},
},
},
},
}
- // create test contract
- contract, err := suite.createContract(suite.AppContextForTest(), testContractCode, testContractName)
- suite.NotNil(contract)
- suite.FatalNoError(err)
+ domServiceArea := testdatagen.FetchOrMakeReDomesticServiceArea(suite.DB(), testdatagen.Assertions{
+ ReDomesticServiceArea: models.ReDomesticServiceArea{
+ ServiceArea: "004",
+ ServicesSchedule: 2,
+ },
+ ReContract: testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}),
+ })
+ suite.NotNil(domServiceArea)
+ suite.NotNil(domServiceArea.Contract)
// setup contract year within availableToPrimeAtTime time
- testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{
+ testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{
ReContractYear: models.ReContractYear{
StartDate: availableToPrimeAtTime,
EndDate: time.Now(),
- ContractID: contract.ID,
+ ContractID: domServiceArea.ContractID,
},
})
- shipmentPostalCodeRateArea, err := shipmentRateAreaFetcher.GetPrimeMoveShipmentOconusRateArea(suite.AppContextForTest(), testMove)
- suite.NotNil(shipmentPostalCodeRateArea)
- suite.Equal(0, len(*shipmentPostalCodeRateArea))
+ setupDomesticRateAreaAndZip3s := func(rateAreaCode string, rateAreaName string, postalCodes map[string]string, domesticServiceArea models.ReDomesticServiceArea) (models.ReRateArea, error) {
+ rateArea := testdatagen.FetchOrMakeReRateArea(suite.DB(), testdatagen.Assertions{
+ ReRateArea: models.ReRateArea{
+ ContractID: domesticServiceArea.ContractID,
+ IsOconus: false,
+ Code: rateAreaCode,
+ Name: rateAreaName,
+ Contract: domesticServiceArea.Contract,
+ },
+ })
+
+ for postalCode, basePointCity := range postalCodes {
+ testdatagen.FetchOrMakeReZip3(suite.DB(), testdatagen.Assertions{
+ ReZip3: models.ReZip3{
+ Contract: domesticServiceArea.Contract,
+ ContractID: domesticServiceArea.ContractID,
+ DomesticServiceAreaID: domesticServiceArea.ID,
+ DomesticServiceArea: domesticServiceArea,
+ Zip3: postalCode[0:3],
+ BasePointCity: basePointCity,
+ },
+ })
+ }
+
+ return rateArea, nil
+ }
+
+ rateAreaCA, err := setupDomesticRateAreaAndZip3s("US88", "California-South", map[string]string{beverlyHillsCAPostalCode: "Beverly Hills", sanDiegoCAPostalCode: "San Diego"}, domServiceArea)
+ if err != nil {
+ suite.Fail(err.Error())
+ }
+
+ rateAreaNY, err := setupDomesticRateAreaAndZip3s("US17", "New York", map[string]string{brooklynNYPostalCode: "Brooklyn"}, domServiceArea)
+ if err != nil {
+ suite.Fail(err.Error())
+ }
+
+ shipmentPostalCodeRateAreas, err := shipmentRateAreaFetcher.GetPrimeMoveShipmentRateAreas(suite.AppContextForTest(), testMove)
+ suite.NotNil(shipmentPostalCodeRateAreas)
+ suite.Equal(3, len(*shipmentPostalCodeRateAreas))
suite.Nil(err)
+
+ var shipmentPostalCodeRateAreaLookupMap = make(map[string]services.ShipmentPostalCodeRateArea)
+ for _, pcra := range *shipmentPostalCodeRateAreas {
+ shipmentPostalCodeRateAreaLookupMap[pcra.PostalCode] = pcra
+ }
+
+ suite.Equal(rateAreaCA.Name, shipmentPostalCodeRateAreaLookupMap[beverlyHillsCAPostalCode].RateArea.Name)
+ suite.Equal(rateAreaCA.Name, shipmentPostalCodeRateAreaLookupMap[sanDiegoCAPostalCode].RateArea.Name)
+ suite.Equal(rateAreaNY.Name, shipmentPostalCodeRateAreaLookupMap[brooklynNYPostalCode].RateArea.Name)
})
suite.Run("not available to prime error", func() {
@@ -254,7 +379,7 @@ func (suite *MTOShipmentServiceSuite) TestGetMoveShipmentRateArea() {
},
}
- shipmentPostalCodeRateArea, err := shipmentRateAreaFetcher.GetPrimeMoveShipmentOconusRateArea(suite.AppContextForTest(), testMove)
+ shipmentPostalCodeRateArea, err := shipmentRateAreaFetcher.GetPrimeMoveShipmentRateAreas(suite.AppContextForTest(), testMove)
suite.Nil(shipmentPostalCodeRateArea)
suite.NotNil(err)
suite.IsType(apperror.UnprocessableEntityError{}, err)
@@ -296,7 +421,7 @@ func (suite *MTOShipmentServiceSuite) TestGetMoveShipmentRateArea() {
},
})
- shipmentPostalCodeRateArea, err := shipmentRateAreaFetcher.GetPrimeMoveShipmentOconusRateArea(suite.AppContextForTest(), testMove)
+ shipmentPostalCodeRateArea, err := shipmentRateAreaFetcher.GetPrimeMoveShipmentRateAreas(suite.AppContextForTest(), testMove)
suite.Nil(shipmentPostalCodeRateArea)
suite.NotNil(err)
suite.IsType(apperror.NotFoundError{}, err)
diff --git a/pkg/services/mto_shipment/mto_shipment_updater.go b/pkg/services/mto_shipment/mto_shipment_updater.go
index 1f3696da161..fb75c795a77 100644
--- a/pkg/services/mto_shipment/mto_shipment_updater.go
+++ b/pkg/services/mto_shipment/mto_shipment_updater.go
@@ -844,7 +844,9 @@ func (f *mtoShipmentUpdater) updateShipmentRecord(appCtx appcontext.AppContext,
}
// when populating the market_code column, it is considered domestic if both pickup & dest are CONUS addresses
- newShipment = models.DetermineShipmentMarketCode(newShipment)
+ if newShipment.ShipmentType != models.MTOShipmentTypePPM {
+ newShipment = models.DetermineShipmentMarketCode(newShipment)
+ }
if err := txnAppCtx.DB().Update(newShipment); err != nil {
return err
diff --git a/pkg/services/mto_shipment/shipment_approver.go b/pkg/services/mto_shipment/shipment_approver.go
index ce0cde22c2a..9191657787c 100644
--- a/pkg/services/mto_shipment/shipment_approver.go
+++ b/pkg/services/mto_shipment/shipment_approver.go
@@ -17,19 +17,23 @@ import (
)
type shipmentApprover struct {
- router services.ShipmentRouter
- siCreator services.MTOServiceItemCreator
- planner route.Planner
- moveWeights services.MoveWeights
+ router services.ShipmentRouter
+ siCreator services.MTOServiceItemCreator
+ planner route.Planner
+ moveWeights services.MoveWeights
+ moveTaskOrderUpdater services.MoveTaskOrderUpdater
+ moveRouter services.MoveRouter
}
// NewShipmentApprover creates a new struct with the service dependencies
-func NewShipmentApprover(router services.ShipmentRouter, siCreator services.MTOServiceItemCreator, planner route.Planner, moveWeights services.MoveWeights) services.ShipmentApprover {
+func NewShipmentApprover(router services.ShipmentRouter, siCreator services.MTOServiceItemCreator, planner route.Planner, moveWeights services.MoveWeights, moveTaskOrderUpdater services.MoveTaskOrderUpdater, moveRouter services.MoveRouter) services.ShipmentApprover {
return &shipmentApprover{
router,
siCreator,
planner,
moveWeights,
+ moveTaskOrderUpdater,
+ moveRouter,
}
}
@@ -81,7 +85,7 @@ func (f *shipmentApprover) ApproveShipment(appCtx appcontext.AppContext, shipmen
transactionError := appCtx.NewTransaction(func(txnAppCtx appcontext.AppContext) error {
// create international shipment service items before approving
// we use a database proc to create the basic auto-approved service items
- internationalShipmentTypes := []models.MTOShipmentType{models.MTOShipmentTypeHHG, models.MTOShipmentTypeHHGIntoNTS, models.MTOShipmentTypeUnaccompaniedBaggage}
+ internationalShipmentTypes := []models.MTOShipmentType{models.MTOShipmentTypeHHG, models.MTOShipmentTypeHHGIntoNTS, models.MTOShipmentTypeHHGOutOfNTS, models.MTOShipmentTypeUnaccompaniedBaggage}
if slices.Contains(internationalShipmentTypes, shipment.ShipmentType) && shipment.MarketCode == models.MarketCodeInternational {
err := models.CreateApprovedServiceItemsForShipment(appCtx.DB(), shipment)
if err != nil {
@@ -144,6 +148,13 @@ func (f *shipmentApprover) ApproveShipment(appCtx appcontext.AppContext, shipmen
return err
}
+ var move models.Move
+ move.ID = shipment.MoveTaskOrderID
+ // re-evaluate move status
+ if _, err = f.moveRouter.ApproveOrRequestApproval(txnAppCtx, move); err != nil {
+ return err
+ }
+
return nil
})
@@ -154,6 +165,29 @@ func (f *shipmentApprover) ApproveShipment(appCtx appcontext.AppContext, shipmen
return shipment, nil
}
+// ApproveShipments Approves one or more shipments in one transaction
+func (f *shipmentApprover) ApproveShipments(appCtx appcontext.AppContext, shipments []services.ShipmentIdWithEtag) (*[]models.MTOShipment, error) {
+ var approvedShipments []models.MTOShipment
+
+ transactionError := appCtx.NewTransaction(func(txnAppCtx appcontext.AppContext) error {
+ for _, shipment := range shipments {
+ shipmentID := shipment.ShipmentID
+ eTag := shipment.ETag
+
+ approvedShipment, err := f.ApproveShipment(txnAppCtx, shipmentID, eTag)
+ if err != nil {
+ return err
+ }
+
+ approvedShipments = append(approvedShipments, *approvedShipment)
+ }
+
+ return nil
+ })
+
+ return &approvedShipments, transactionError
+}
+
func (f *shipmentApprover) findShipment(appCtx appcontext.AppContext, shipmentID uuid.UUID) (*models.MTOShipment, error) {
shipment, err := FindShipment(appCtx, shipmentID, "MoveTaskOrder", "PickupAddress", "DestinationAddress", "StorageFacility")
diff --git a/pkg/services/mto_shipment/shipment_approver_test.go b/pkg/services/mto_shipment/shipment_approver_test.go
index 4e2a45f68ee..492f0862bad 100644
--- a/pkg/services/mto_shipment/shipment_approver_test.go
+++ b/pkg/services/mto_shipment/shipment_approver_test.go
@@ -19,8 +19,10 @@ import (
"github.com/transcom/mymove/pkg/services"
"github.com/transcom/mymove/pkg/services/entitlements"
"github.com/transcom/mymove/pkg/services/ghcrateengine"
+ servicesMocks "github.com/transcom/mymove/pkg/services/mocks"
shipmentmocks "github.com/transcom/mymove/pkg/services/mocks"
moverouter "github.com/transcom/mymove/pkg/services/move"
+ mt "github.com/transcom/mymove/pkg/services/move_task_order"
mtoserviceitem "github.com/transcom/mymove/pkg/services/mto_service_item"
"github.com/transcom/mymove/pkg/services/query"
"github.com/transcom/mymove/pkg/testdatagen"
@@ -36,6 +38,7 @@ type approveShipmentSubtestData struct {
mockedShipmentRouter *shipmentmocks.ShipmentRouter
reServiceCodes []models.ReServiceCode
moveWeights services.MoveWeights
+ mtoUpdater services.MoveTaskOrderUpdater
}
// Creates data for the TestApproveShipment function
@@ -81,6 +84,31 @@ func (suite *MTOShipmentServiceSuite) createApproveShipmentSubtestData() (subtes
factory.FetchReServiceByCode(suite.DB(), serviceCode)
}
+ setUpSignedCertificationCreatorMock := func(returnValue ...interface{}) services.SignedCertificationCreator {
+ mockCreator := &servicesMocks.SignedCertificationCreator{}
+
+ mockCreator.On(
+ "CreateSignedCertification",
+ mock.AnythingOfType("*appcontext.appContext"),
+ mock.AnythingOfType("models.SignedCertification"),
+ ).Return(returnValue...)
+
+ return mockCreator
+ }
+
+ setUpSignedCertificationUpdaterMock := func(returnValue ...interface{}) services.SignedCertificationUpdater {
+ mockUpdater := &servicesMocks.SignedCertificationUpdater{}
+
+ mockUpdater.On(
+ "UpdateSignedCertification",
+ mock.AnythingOfType("*appcontext.appContext"),
+ mock.AnythingOfType("models.SignedCertification"),
+ mock.AnythingOfType("string"),
+ ).Return(returnValue...)
+
+ return mockUpdater
+ }
+
subtestData.mockedShipmentRouter = &shipmentmocks.ShipmentRouter{}
router := NewShipmentRouter()
@@ -95,12 +123,19 @@ func (suite *MTOShipmentServiceSuite) createApproveShipmentSubtestData() (subtes
mock.Anything,
false,
).Return(400, nil)
+ ppmEstimator := &servicesMocks.PPMEstimator{}
+ queryBuilder := query.NewQueryBuilder()
+ subtestData.mtoUpdater = mt.NewMoveTaskOrderUpdater(
+ queryBuilder,
+ mtoserviceitem.NewMTOServiceItemCreator(planner, queryBuilder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()),
+ moveRouter, setUpSignedCertificationCreatorMock(nil, nil), setUpSignedCertificationUpdaterMock(nil, nil), ppmEstimator,
+ )
siCreator := mtoserviceitem.NewMTOServiceItemCreator(planner, builder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer())
subtestData.planner = &mocks.Planner{}
subtestData.moveWeights = moverouter.NewMoveWeights(NewShipmentReweighRequester(), waf)
- subtestData.shipmentApprover = NewShipmentApprover(router, siCreator, subtestData.planner, subtestData.moveWeights)
- subtestData.mockedShipmentApprover = NewShipmentApprover(subtestData.mockedShipmentRouter, siCreator, subtestData.planner, subtestData.moveWeights)
+ subtestData.shipmentApprover = NewShipmentApprover(router, siCreator, subtestData.planner, subtestData.moveWeights, subtestData.mtoUpdater, moveRouter)
+ subtestData.mockedShipmentApprover = NewShipmentApprover(subtestData.mockedShipmentRouter, siCreator, subtestData.planner, subtestData.moveWeights, subtestData.mtoUpdater, moveRouter)
subtestData.appCtx = suite.AppContextWithSessionForTest(&auth.Session{
ApplicationName: auth.OfficeApp,
OfficeUserID: uuid.Must(uuid.NewV4()),
@@ -193,6 +228,40 @@ func (suite *MTOShipmentServiceSuite) createApproveShipmentSubtestData() (subtes
}
func (suite *MTOShipmentServiceSuite) TestApproveShipment() {
+ setUpSignedCertificationCreatorMock := func(returnValue ...interface{}) services.SignedCertificationCreator {
+ mockCreator := &servicesMocks.SignedCertificationCreator{}
+
+ mockCreator.On(
+ "CreateSignedCertification",
+ mock.AnythingOfType("*appcontext.appContext"),
+ mock.AnythingOfType("models.SignedCertification"),
+ ).Return(returnValue...)
+
+ return mockCreator
+ }
+
+ setUpSignedCertificationUpdaterMock := func(returnValue ...interface{}) services.SignedCertificationUpdater {
+ mockUpdater := &servicesMocks.SignedCertificationUpdater{}
+
+ mockUpdater.On(
+ "UpdateSignedCertification",
+ mock.AnythingOfType("*appcontext.appContext"),
+ mock.AnythingOfType("models.SignedCertification"),
+ mock.AnythingOfType("string"),
+ ).Return(returnValue...)
+
+ return mockUpdater
+ }
+
+ moveRouter := moverouter.NewMoveRouter()
+ ppmEstimator := &servicesMocks.PPMEstimator{}
+ queryBuilder := query.NewQueryBuilder()
+ planner := &mocks.Planner{}
+ mtoUpdater := mt.NewMoveTaskOrderUpdater(
+ queryBuilder,
+ mtoserviceitem.NewMTOServiceItemCreator(planner, queryBuilder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()),
+ moveRouter, setUpSignedCertificationCreatorMock(nil, nil), setUpSignedCertificationUpdaterMock(nil, nil), ppmEstimator,
+ )
suite.Run("If the international mtoShipment is approved successfully it should create pre approved mtoServiceItems and should NOT update pricing without port data", func() {
move := factory.BuildAvailableToPrimeMove(suite.DB(), []factory.Customization{
{
@@ -285,7 +354,7 @@ func (suite *MTOShipmentServiceSuite) TestApproveShipment() {
).Return(500, nil)
// Approve international shipment
- shipmentApprover := NewShipmentApprover(shipmentRouter, serviceItemCreator, planner, moveWeights)
+ shipmentApprover := NewShipmentApprover(shipmentRouter, serviceItemCreator, planner, moveWeights, mtoUpdater, moveRouter)
_, err = shipmentApprover.ApproveShipment(appCtx, internationalShipment.ID, internationalShipmentEtag)
suite.NoError(err)
@@ -371,7 +440,7 @@ func (suite *MTOShipmentServiceSuite) TestApproveShipment() {
var moveWeights services.MoveWeights
// Approve international shipment
- shipmentApprover := NewShipmentApprover(shipmentRouter, serviceItemCreator, planner, moveWeights)
+ shipmentApprover := NewShipmentApprover(shipmentRouter, serviceItemCreator, planner, moveWeights, mtoUpdater, moveRouter)
_, err := shipmentApprover.ApproveShipment(suite.AppContextForTest(), internationalShipment.ID, internationalShipmentEtag)
suite.NoError(err)
@@ -449,7 +518,7 @@ func (suite *MTOShipmentServiceSuite) TestApproveShipment() {
var moveWeights services.MoveWeights
// Approve international shipment
- shipmentApprover := NewShipmentApprover(shipmentRouter, serviceItemCreator, planner, moveWeights)
+ shipmentApprover := NewShipmentApprover(shipmentRouter, serviceItemCreator, planner, moveWeights, mtoUpdater, moveRouter)
_, err := shipmentApprover.ApproveShipment(suite.AppContextForTest(), internationalShipment.ID, internationalShipmentEtag)
suite.NoError(err)
@@ -471,6 +540,164 @@ func (suite *MTOShipmentServiceSuite) TestApproveShipment() {
}
})
+ suite.Run("Given international mtoShipment is approved successfully pre-approved mtoServiceItems are created NTS-R CONUS to OCONUS", func() {
+ storageFacility := factory.BuildStorageFacility(suite.DB(), []factory.Customization{
+ {
+ Model: models.StorageFacility{
+ FacilityName: *models.StringPointer("Test Storage Name"),
+ Email: models.StringPointer("old@email.com"),
+ LotNumber: models.StringPointer("Test lot number"),
+ Phone: models.StringPointer("555-555-5555"),
+ },
+ },
+ {
+ Model: models.Address{
+ StreetAddress1: "Tester Address",
+ City: "Des Moines",
+ State: "IA",
+ PostalCode: "50314",
+ IsOconus: models.BoolPointer(false),
+ },
+ },
+ }, nil)
+
+ internationalShipment := factory.BuildNTSRShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.Move{
+ Status: models.MoveStatusAPPROVED,
+ },
+ },
+ {
+ Model: models.Address{
+ StreetAddress1: "JBER",
+ City: "Anchorage",
+ State: "AK",
+ PostalCode: "99507",
+ IsOconus: models.BoolPointer(true),
+ },
+ Type: &factory.Addresses.DeliveryAddress,
+ },
+ {
+ Model: models.MTOShipment{
+ MarketCode: models.MarketCodeInternational,
+ Status: models.MTOShipmentStatusSubmitted,
+ ShipmentType: models.MTOShipmentTypeHHGOutOfNTS,
+ },
+ },
+ {
+ Model: storageFacility,
+ LinkOnly: true,
+ },
+ }, 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, mtoUpdater, moveRouter)
+ _, 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.ReServiceCodeISLH,
+ models.ReServiceCodePOEFSC,
+ models.ReServiceCodeIHUPK,
+ }
+
+ suite.Equal(len(expectedReserviceCodes), len(serviceItems))
+ for i := 0; i < len(serviceItems); i++ {
+ actualReServiceCode := serviceItems[i].ReService.Code
+ suite.True(slices.Contains(expectedReserviceCodes, actualReServiceCode))
+ }
+ })
+
+ suite.Run("Given international mtoShipment is approved successfully pre-approved mtoServiceItems are created NTS-R OCONUS to CONUS", func() {
+ storageFacility := factory.BuildStorageFacility(suite.DB(), []factory.Customization{
+ {
+ Model: models.StorageFacility{
+ FacilityName: *models.StringPointer("Test Storage Name"),
+ Email: models.StringPointer("old@email.com"),
+ LotNumber: models.StringPointer("Test lot number"),
+ Phone: models.StringPointer("555-555-5555"),
+ },
+ },
+ {
+ Model: models.Address{
+ StreetAddress1: "JBER",
+ City: "Anchorage",
+ State: "AK",
+ PostalCode: "99507",
+ IsOconus: models.BoolPointer(true),
+ },
+ },
+ }, nil)
+
+ internationalShipment := factory.BuildNTSRShipment(suite.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.DeliveryAddress,
+ },
+ {
+ Model: models.MTOShipment{
+ MarketCode: models.MarketCodeInternational,
+ Status: models.MTOShipmentStatusSubmitted,
+ ShipmentType: models.MTOShipmentTypeHHGOutOfNTS,
+ },
+ },
+ {
+ Model: storageFacility,
+ LinkOnly: true,
+ },
+ }, 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, mtoUpdater, moveRouter)
+ _, 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.ReServiceCodeISLH,
+ models.ReServiceCodePODFSC,
+ models.ReServiceCodeIHUPK,
+ }
+
+ suite.Equal(len(expectedReserviceCodes), 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
@@ -1227,8 +1454,134 @@ func (suite *MTOShipmentServiceSuite) TestApproveShipment() {
var moveWeights services.MoveWeights
// Approve international shipment
- shipmentApprover := NewShipmentApprover(shipmentRouter, serviceItemCreator, planner, moveWeights)
+ shipmentApprover := NewShipmentApprover(shipmentRouter, serviceItemCreator, planner, moveWeights, mtoUpdater, moveRouter)
_, err := shipmentApprover.ApproveShipment(suite.AppContextForTest(), invalidShipment.ID, invalidShipmentEtag)
suite.Error(err)
})
}
+
+func (suite *MTOShipmentServiceSuite) TestApproveShipments() {
+ suite.Run("Successfully approves multiple shipments", func() {
+ subtestData := suite.createApproveShipmentSubtestData()
+ shipmentApprover := subtestData.shipmentApprover
+
+ move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil)
+
+ shipment1 := factory.BuildMTOShipment(suite.DB(), []factory.Customization{
+ {
+ Model: move,
+ LinkOnly: true,
+ },
+ {
+ Model: models.MTOShipment{
+ Status: models.MTOShipmentStatusSubmitted,
+ },
+ },
+ }, nil)
+
+ shipment2 := factory.BuildMTOShipment(suite.DB(), []factory.Customization{
+ {
+ Model: move,
+ LinkOnly: true,
+ },
+ {
+ Model: models.MTOShipment{
+ Status: models.MTOShipmentStatusSubmitted,
+ },
+ },
+ }, nil)
+
+ eTag1 := etag.GenerateEtag(shipment1.UpdatedAt)
+ eTag2 := etag.GenerateEtag(shipment2.UpdatedAt)
+
+ shipmentIdWithEtagArr := []services.ShipmentIdWithEtag{
+ {
+ ShipmentID: shipment1.ID,
+ ETag: eTag1,
+ },
+ {
+ ShipmentID: shipment2.ID,
+ ETag: eTag2,
+ },
+ }
+ approvedShipments, err := shipmentApprover.ApproveShipments(suite.AppContextForTest(), shipmentIdWithEtagArr)
+
+ suite.NoError(err)
+ suite.NotNil(approvedShipments)
+ suite.Len(*approvedShipments, 2)
+ suite.Equal(shipment1.ID, (*approvedShipments)[0].ID)
+ suite.Equal(shipment2.ID, (*approvedShipments)[1].ID)
+ })
+
+ suite.Run("Returns error if one shipment approval fails", func() {
+ subtestData := suite.createApproveShipmentSubtestData()
+ shipmentApprover := subtestData.shipmentApprover
+
+ move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil)
+
+ shipment1 := factory.BuildMTOShipment(suite.DB(), []factory.Customization{
+ {
+ Model: move,
+ LinkOnly: true,
+ },
+ {
+ Model: models.MTOShipment{
+ Status: models.MTOShipmentStatusSubmitted,
+ },
+ },
+ }, nil)
+
+ shipment2 := factory.BuildMTOShipment(suite.DB(), []factory.Customization{
+ {
+ Model: move,
+ LinkOnly: true,
+ },
+ {
+ Model: models.MTOShipment{
+ Status: models.MTOShipmentStatusSubmitted,
+ },
+ },
+ }, nil)
+
+ eTag := etag.GenerateEtag(shipment2.UpdatedAt)
+
+ shipmentIdWithEtagArr := []services.ShipmentIdWithEtag{
+ {
+ ShipmentID: shipment1.ID,
+ ETag: eTag,
+ },
+ {
+ ShipmentID: shipment2.ID,
+ ETag: eTag,
+ },
+ }
+
+ approvedShipments, err := shipmentApprover.ApproveShipments(suite.AppContextForTest(), shipmentIdWithEtagArr)
+
+ suite.Error(err)
+ suite.Len(*approvedShipments, 0)
+ })
+
+ suite.Run("Given invalid shipment error returned", func() {
+ subtestData := suite.createApproveShipmentSubtestData()
+ shipmentApprover := subtestData.shipmentApprover
+ invalidShipment := factory.BuildMTOShipment(suite.AppContextForTest().DB(), []factory.Customization{
+ {
+ Model: models.MTOShipment{
+ ShipmentType: models.MTOShipmentTypePPM,
+ },
+ },
+ }, nil)
+ invalidShipmentEtag := etag.GenerateEtag(invalidShipment.UpdatedAt)
+
+ shipmentIdWithEtagArr := []services.ShipmentIdWithEtag{
+ {
+ ShipmentID: invalidShipment.ID,
+ ETag: invalidShipmentEtag,
+ },
+ }
+
+ _, err := shipmentApprover.ApproveShipments(suite.AppContextForTest(), shipmentIdWithEtagArr)
+ suite.Error(err)
+ })
+}
diff --git a/pkg/services/office_user/office_user_creator.go b/pkg/services/office_user/office_user_creator.go
index 87a01051cc4..7f96c3c79ce 100644
--- a/pkg/services/office_user/office_user_creator.go
+++ b/pkg/services/office_user/office_user_creator.go
@@ -171,8 +171,8 @@ func (o *officeUserCreator) checkAndUpdateRejectedOfficeUser(
// checking if the office user currently exists and has a previous status of rejected
var requestedOfficeUser models.OfficeUser
- previouslyRejectedCheck := query.NewQueryFilter("email", "=", officeUser.Email)
- fetchErr := o.builder.FetchOne(appCtx, &requestedOfficeUser, []services.QueryFilter{previouslyRejectedCheck})
+
+ fetchErr := appCtx.DB().Where("(status = 'REJECTED' and edipi = (?)) or email = (?)", officeUser.EDIPI, officeUser.Email).First(&requestedOfficeUser)
if fetchErr != nil && fetchErr != sql.ErrNoRows {
return nil, nil, fetchErr // Return the actual error if it's not a "no rows" error
} else if fetchErr == sql.ErrNoRows {
diff --git a/pkg/services/office_user/office_user_creator_test.go b/pkg/services/office_user/office_user_creator_test.go
index 3eee281b96a..0e2075668f9 100644
--- a/pkg/services/office_user/office_user_creator_test.go
+++ b/pkg/services/office_user/office_user_creator_test.go
@@ -232,6 +232,96 @@ func (suite *OfficeUserServiceSuite) TestCreateOfficeUser() {
suite.Nil(updatedOfficeUser.RejectionReason)
})
+ suite.Run("Updates previously rejected office user instead of create different email same ID", func() {
+ rejectedStatus := models.OfficeUserStatusREJECTED
+ requestedStatus := models.OfficeUserStatusREQUESTED
+
+ transportationOffice := factory.BuildTransportationOffice(suite.DB(), []factory.Customization{
+ {
+ Model: models.TransportationOffice{
+ ID: uuid.Must(uuid.NewV4()),
+ },
+ }}, nil)
+
+ user := factory.BuildUser(suite.DB(), []factory.Customization{
+ {
+ Model: models.User{
+ ID: uuid.Must(uuid.NewV4()),
+ OktaEmail: "billy+existing@leo.org",
+ },
+ },
+ }, nil)
+ edipi := "1225202401"
+ officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{
+ {
+ Model: models.OfficeUser{
+ ID: uuid.Must(uuid.NewV4()),
+ FirstName: "Billy",
+ LastName: "Bob",
+ Status: &rejectedStatus,
+ Email: "billy+was+rejected@leo.org",
+ EDIPI: &edipi,
+ },
+ },
+ {
+ Model: user,
+ LinkOnly: true,
+ },
+ }, []roles.RoleType{roles.RoleTypeTOO})
+
+ appCtx := appcontext.NewAppContext(suite.AppContextForTest().DB(), suite.AppContextForTest().Logger(), &auth.Session{})
+ queryBuilder := query.NewQueryBuilder()
+
+ officeUserInfo := models.OfficeUser{
+ LastName: "Spaceman",
+ FirstName: "Billy",
+ Email: "billy+was+sucessful@leo.org",
+ TransportationOfficeID: transportationOffice.ID,
+ Telephone: "312-111-1111",
+ TransportationOffice: transportationOffice,
+ EDIPI: &edipi,
+ }
+
+ fakeFetchOne := func(appCtx appcontext.AppContext, model interface{}) error {
+ switch model.(type) {
+ case *models.TransportationOffice:
+ reflect.ValueOf(model).Elem().FieldByName("ID").Set(reflect.ValueOf(transportationOffice.ID))
+ case *models.User:
+ reflect.ValueOf(model).Elem().FieldByName("ID").Set(reflect.ValueOf(officeUser.User.ID))
+ reflect.ValueOf(model).Elem().FieldByName("OktaID").Set(reflect.ValueOf(officeUser.User.OktaID))
+ reflect.ValueOf(model).Elem().FieldByName("OktaEmail").Set(reflect.ValueOf(officeUser.User.OktaEmail))
+ case *models.OfficeUser:
+ reflect.ValueOf(model).Elem().FieldByName("ID").Set(reflect.ValueOf(officeUser.ID))
+ reflect.ValueOf(model).Elem().FieldByName("Email").Set(reflect.ValueOf(officeUser.Email))
+ reflect.ValueOf(model).Elem().FieldByName("FirstName").Set(reflect.ValueOf(officeUser.FirstName))
+ reflect.ValueOf(model).Elem().FieldByName("LastName").Set(reflect.ValueOf(officeUser.LastName))
+ reflect.ValueOf(model).Elem().FieldByName("TransportationOfficeID").Set(reflect.ValueOf(officeUser.TransportationOfficeID))
+ reflect.ValueOf(model).Elem().FieldByName("Telephone").Set(reflect.ValueOf(officeUser.Telephone))
+ reflect.ValueOf(model).Elem().FieldByName("Status").Set(reflect.ValueOf(officeUser.Status))
+ }
+ return nil
+ }
+
+ filter := []services.QueryFilter{query.NewQueryFilter("id", "=", transportationOffice.ID)}
+
+ builder := &testOfficeUserQueryBuilder{
+ fakeFetchOne: fakeFetchOne,
+ fakeCreateOne: queryBuilder.CreateOne,
+ }
+ mockSender := setUpMockNotificationSender()
+
+ creator := NewOfficeUserCreator(builder, mockSender)
+ updatedOfficeUser, verrs, err := creator.CreateOfficeUser(appCtx, &officeUserInfo, filter)
+ suite.NoError(err)
+ suite.Nil(verrs)
+ suite.NotNil(updatedOfficeUser)
+ suite.Equal(updatedOfficeUser.ID, officeUser.ID)
+ suite.Equal(updatedOfficeUser.Status, &requestedStatus)
+ suite.Equal(updatedOfficeUser.Email, "billy+was+sucessful@leo.org")
+ suite.Equal(updatedOfficeUser.EDIPI, &edipi)
+ suite.Nil(updatedOfficeUser.RejectionReason)
+ })
+
// Bad transportation office ID
suite.Run("If we are provided a transportation office that doesn't exist, the create should fail", func() {
_, userInfo := setupTestData()
diff --git a/pkg/services/office_user/office_user_fetcher.go b/pkg/services/office_user/office_user_fetcher.go
index 881c278ac8a..99a6448f1fe 100644
--- a/pkg/services/office_user/office_user_fetcher.go
+++ b/pkg/services/office_user/office_user_fetcher.go
@@ -82,8 +82,6 @@ func (o *officeUserFetcherPop) FetchOfficeUsersByRoleAndOffice(appCtx appcontext
"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").
@@ -109,8 +107,6 @@ func (o *officeUserFetcherPop) FetchSafetyMoveOfficeUsersByRoleAndOffice(appCtx
"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").
diff --git a/pkg/services/order/order_updater.go b/pkg/services/order/order_updater.go
index e08896380c3..8929d047684 100644
--- a/pkg/services/order/order_updater.go
+++ b/pkg/services/order/order_updater.go
@@ -485,6 +485,11 @@ func allowanceFromTOOPayload(appCtx appcontext.AppContext, existingOrder models.
order.Entitlement.GunSafe = *payload.GunSafe
}
+ if payload.WeightRestriction != nil {
+ weightRestriction := int(*payload.WeightRestriction)
+ order.Entitlement.WeightRestriction = &weightRestriction
+ }
+
if payload.AccompaniedTour != nil {
order.Entitlement.AccompaniedTour = payload.AccompaniedTour
}
@@ -531,7 +536,6 @@ func allowanceFromTOOPayload(appCtx appcontext.AppContext, existingOrder models.
return order, nil
}
-
func allowanceFromCounselingPayload(appCtx appcontext.AppContext, existingOrder models.Order, payload ghcmessages.CounselingUpdateAllowancePayload) (models.Order, error) {
order := existingOrder
waf := entitlements.NewWeightAllotmentFetcher()
@@ -589,6 +593,11 @@ func allowanceFromCounselingPayload(appCtx appcontext.AppContext, existingOrder
order.Entitlement.GunSafe = *payload.GunSafe
}
+ if payload.WeightRestriction != nil {
+ weightRestriction := int(*payload.WeightRestriction)
+ order.Entitlement.WeightRestriction = &weightRestriction
+ }
+
if payload.AccompaniedTour != nil {
order.Entitlement.AccompaniedTour = payload.AccompaniedTour
}
@@ -631,7 +640,6 @@ func allowanceFromCounselingPayload(appCtx appcontext.AppContext, existingOrder
return order, nil
}
-
func (f *orderUpdater) saveDocumentForAmendedOrder(appCtx appcontext.AppContext, doc *models.Document) (*models.Document, error) {
var docID uuid.UUID
if doc != nil {
diff --git a/pkg/services/order/order_updater_test.go b/pkg/services/order/order_updater_test.go
index 1bf72866552..9e86e990f95 100644
--- a/pkg/services/order/order_updater_test.go
+++ b/pkg/services/order/order_updater_test.go
@@ -747,6 +747,7 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsCounselor() {
AccompaniedTour: models.BoolPointer(true),
DependentsTwelveAndOver: models.Int64Pointer(1),
DependentsUnderTwelve: models.Int64Pointer(2),
+ WeightRestriction: models.Int64Pointer(0),
}
updatedOrder, _, err := orderUpdater.UpdateAllowanceAsCounselor(suite.AppContextForTest(), order.ID, payload, eTag)
@@ -775,6 +776,7 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsCounselor() {
proGearWeightSpouse := models.Int64Pointer(10)
rmeWeight := models.Int64Pointer(10000)
eTag := etag.GenerateEtag(order.UpdatedAt)
+ weightRestriction := models.Int64Pointer(5000)
payload := ghcmessages.CounselingUpdateAllowancePayload{
Agency: &affiliation,
@@ -784,6 +786,7 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsCounselor() {
ProGearWeight: proGearWeight,
ProGearWeightSpouse: proGearWeightSpouse,
RequiredMedicalEquipmentWeight: rmeWeight,
+ WeightRestriction: weightRestriction,
}
updatedOrder, _, err := orderUpdater.UpdateAllowanceAsCounselor(suite.AppContextForTest(), order.ID, payload, eTag)
@@ -804,6 +807,7 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsCounselor() {
suite.Equal(*payload.OrganizationalClothingAndIndividualEquipment, updatedOrder.Entitlement.OrganizationalClothingAndIndividualEquipment)
suite.EqualValues(payload.Agency, fetchedSM.Affiliation)
suite.Equal(*updatedOrder.Entitlement.DBAuthorizedWeight, 16000)
+ suite.Equal(*payload.WeightRestriction, int64(*updatedOrder.Entitlement.WeightRestriction))
})
suite.Run("Updates the allowance when all fields are valid with dependents present and authorized", func() {
@@ -829,6 +833,7 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsCounselor() {
ProGearWeight: proGearWeight,
ProGearWeightSpouse: proGearWeightSpouse,
RequiredMedicalEquipmentWeight: rmeWeight,
+ WeightRestriction: models.Int64Pointer(0),
}
updatedOrder, _, err := orderUpdater.UpdateAllowanceAsCounselor(suite.AppContextForTest(), order.ID, payload, eTag)
@@ -878,6 +883,7 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsCounselor() {
ProGearWeight: proGearWeight,
ProGearWeightSpouse: proGearWeightSpouse,
RequiredMedicalEquipmentWeight: rmeWeight,
+ WeightRestriction: models.Int64Pointer(0),
}
updatedOrder, _, err := orderUpdater.UpdateAllowanceAsCounselor(suite.AppContextForTest(), orderWithoutDefaults.ID, payload, eTag)
@@ -929,6 +935,7 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsCounselor() {
ProGearWeight: proGearWeight,
ProGearWeightSpouse: proGearWeightSpouse,
RequiredMedicalEquipmentWeight: rmeWeight,
+ WeightRestriction: models.Int64Pointer(0),
}
updatedOrder, _, err := orderUpdater.UpdateAllowanceAsCounselor(suite.AppContextForTest(), order.ID, payload, eTag)
@@ -965,6 +972,7 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsCounselor() {
ProGearWeight: proGearWeight,
ProGearWeightSpouse: proGearWeightSpouse,
RequiredMedicalEquipmentWeight: rmeWeight,
+ WeightRestriction: models.Int64Pointer(0),
}
updatedOrder, _, err := orderUpdater.UpdateAllowanceAsCounselor(suite.AppContextForTest(), order.ID, payload, eTag)
diff --git a/pkg/services/payment_request/payment_request_list_fetcher.go b/pkg/services/payment_request/payment_request_list_fetcher.go
index 11f0c52805c..fc42b14dc63 100644
--- a/pkg/services/payment_request/payment_request_list_fetcher.go
+++ b/pkg/services/payment_request/payment_request_list_fetcher.go
@@ -65,6 +65,7 @@ func (f *paymentRequestListFetcher) FetchPaymentRequestList(appCtx appcontext.Ap
"MoveTaskOrder.Orders.OriginDutyLocation.TransportationOffice",
"MoveTaskOrder.Orders.OriginDutyLocation.Address",
"MoveTaskOrder.TIOAssignedUser",
+ "MoveTaskOrder.TIOAssignedUser.TransportationOfficeID",
"MoveTaskOrder.CounselingOffice",
// See note further below about having to do this in a separate Load call due to a Pop issue.
// "MoveTaskOrder.Orders.ServiceMember",
diff --git a/pkg/services/ppm_closeout/ppm_closeout.go b/pkg/services/ppm_closeout/ppm_closeout.go
index 925385d079b..7ced6a8c257 100644
--- a/pkg/services/ppm_closeout/ppm_closeout.go
+++ b/pkg/services/ppm_closeout/ppm_closeout.go
@@ -36,6 +36,9 @@ type serviceItemPrices struct {
haulPrice *unit.Cents
haulFSC *unit.Cents
haulType models.HaulType
+ intlPackPrice *unit.Cents
+ intlUnpackPrice *unit.Cents
+ intlLinehaulPrice *unit.Cents
}
func NewPPMCloseoutFetcher(planner route.Planner, paymentRequestHelper paymentrequesthelper.Helper, estimator services.PPMEstimator) services.PPMCloseoutFetcher {
@@ -60,11 +63,6 @@ func (p *ppmCloseoutFetcher) GetPPMCloseout(appCtx appcontext.AppContext, ppmShi
proGearWeightCustomer, proGearWeightSpouse := p.GetProGearWeights(*ppmShipment)
- serviceItems, err := p.getServiceItemPrices(appCtx, *ppmShipment)
- if err != nil {
- return nil, err
- }
-
var remainingIncentive unit.Cents
// Most moves generated by `make db_dev_e2e_populate` skip the part that generates the FinalIncentive for the move, so just return 0.
// Moves created through the UI shouldn't be able to skip this part
@@ -96,6 +94,11 @@ func (p *ppmCloseoutFetcher) GetPPMCloseout(appCtx appcontext.AppContext, ppmShi
fullWeightGCCShipment.ProGearWeight = &proGearCustomerMax
fullWeightGCCShipment.SpouseProGearWeight = &proGearSpouseMax
gcc, _ := p.calculateGCC(appCtx, *fullWeightGCCShipment, fullAllowableWeight)
+
+ serviceItems, err := p.getServiceItemPrices(appCtx, *ppmShipment)
+ if err != nil {
+ return nil, err
+ }
if serviceItems.storageReimbursementCosts != nil {
gcc = gcc.AddCents(*serviceItems.storageReimbursementCosts)
}
@@ -114,11 +117,14 @@ func (p *ppmCloseoutFetcher) GetPPMCloseout(appCtx appcontext.AppContext, ppmShi
ppmCloseoutObj.RemainingIncentive = &remainingIncentive
ppmCloseoutObj.HaulPrice = serviceItems.haulPrice
ppmCloseoutObj.HaulFSC = serviceItems.haulFSC
- ppmCloseoutObj.HaulType = serviceItems.haulType
+ ppmCloseoutObj.HaulType = &serviceItems.haulType
ppmCloseoutObj.DOP = serviceItems.dop
ppmCloseoutObj.DDP = serviceItems.ddp
ppmCloseoutObj.PackPrice = serviceItems.packPrice
ppmCloseoutObj.UnpackPrice = serviceItems.unpackPrice
+ ppmCloseoutObj.IntlLinehaulPrice = serviceItems.intlLinehaulPrice
+ ppmCloseoutObj.IntlUnpackPrice = serviceItems.intlUnpackPrice
+ ppmCloseoutObj.IntlPackPrice = serviceItems.intlPackPrice
ppmCloseoutObj.SITReimbursement = serviceItems.storageReimbursementCosts
return &ppmCloseoutObj, nil
@@ -249,53 +255,6 @@ func (p *ppmCloseoutFetcher) GetExpenseStoragePrice(appCtx appcontext.AppContext
return storageExpensePrice, err
}
-func (p *ppmCloseoutFetcher) GetEntitlement(appCtx appcontext.AppContext, moveID uuid.UUID) (*models.Entitlement, error) {
- var moveModel models.Move
- err := appCtx.DB().EagerPreload(
- "OrdersID",
- ).Find(&moveModel, moveID)
-
- if err != nil {
- switch err {
- case sql.ErrNoRows:
- return nil, apperror.NewNotFoundError(moveID, "while looking for Move")
- default:
- return nil, apperror.NewQueryError("Move", err, "unable to find Move")
- }
- }
-
- var order models.Order
- orderID := &moveModel.OrdersID
- errOrder := appCtx.DB().EagerPreload(
- "EntitlementID",
- ).Find(&order, orderID)
-
- if errOrder != nil {
- switch errOrder {
- case sql.ErrNoRows:
- return nil, apperror.NewNotFoundError(*orderID, "while looking for Order")
- default:
- return nil, apperror.NewQueryError("Order", errOrder, "unable to find Order")
- }
- }
-
- var entitlement models.Entitlement
- entitlementID := order.EntitlementID
- errEntitlement := appCtx.DB().EagerPreload(
- "DBAuthorizedWeight",
- ).Find(&entitlement, entitlementID)
-
- if errEntitlement != nil {
- switch errEntitlement {
- case sql.ErrNoRows:
- return nil, apperror.NewNotFoundError(*entitlementID, "while looking for Entitlement")
- default:
- return nil, apperror.NewQueryError("Entitlement", errEntitlement, "unable to find Entitlement")
- }
- }
- return &entitlement, nil
-}
-
func paramsForServiceCode(code models.ReServiceCode, serviceParams models.ServiceParams) models.ServiceParams {
var serviceItemParams models.ServiceParams
for _, serviceParam := range serviceParams {
@@ -312,17 +271,13 @@ func (p *ppmCloseoutFetcher) getServiceItemPrices(appCtx appcontext.AppContext,
var returnPriceObj serviceItemPrices
logger := appCtx.Logger()
- err := appCtx.DB().Where("mto_shipment_id = ?", ppmShipment.ShipmentID).All(&serviceItemsToPrice)
- if err != nil {
- return serviceItemPrices{}, err
- }
+ isInternationalShipment := ppmShipment.Shipment.MarketCode == models.MarketCodeInternational
+ serviceItemsToPrice = ppmshipment.BaseServiceItems(ppmShipment)
- serviceItemsToPrice = ppmshipment.BaseServiceItems(ppmShipment.ShipmentID)
-
- // Change DLH to DSH if move within same Zip3
actualPickupPostal := *ppmShipment.ActualPickupPostalCode
actualDestPostal := *ppmShipment.ActualDestinationPostalCode
- if actualPickupPostal[0:3] == actualDestPostal[0:3] {
+ // Change DLH to DSH if move within same Zip3 (only for domestic shipments - intl uses ISLH)
+ if !isInternationalShipment && actualPickupPostal[0:3] == actualDestPostal[0:3] {
serviceItemsToPrice[0] = models.MTOServiceItem{ReService: models.ReService{Code: models.ReServiceCodeDSH}, MTOShipmentID: &ppmShipment.ShipmentID}
}
contractDate := ppmShipment.ExpectedDepartureDate
@@ -335,10 +290,12 @@ func (p *ppmCloseoutFetcher) getServiceItemPrices(appCtx appcontext.AppContext,
if paramErr != nil {
return serviceItemPrices{}, paramErr
}
- var totalPrice, packPrice, unpackPrice, destinationPrice, originPrice, haulPrice, haulFSC unit.Cents
+
+ var totalPrice, packPrice, unpackPrice, destinationPrice, originPrice, haulPrice, haulFSC, intlPackPrice, intlUnpackPrice, intlLinehaulPrice unit.Cents
var totalWeight unit.Pound
var ppmToMtoShipment models.MTOShipment
+ // adding all the weight tickets together to get the total weight of the moved PPM
if len(ppmShipment.WeightTickets) >= 1 {
for _, weightTicket := range ppmShipment.WeightTickets {
if weightTicket.Status != nil && *weightTicket.Status == models.PPMDocumentStatusRejected {
@@ -373,14 +330,18 @@ func (p *ppmCloseoutFetcher) getServiceItemPrices(appCtx appcontext.AppContext,
return serviceItemPrices{}, err
}
+ // combo of domestic & int'l service items
validCodes := map[models.ReServiceCode]string{
- models.ReServiceCodeDPK: "DPK",
- models.ReServiceCodeDUPK: "DUPK",
- models.ReServiceCodeDOP: "DOP",
- models.ReServiceCodeDDP: "DDP",
- models.ReServiceCodeDSH: "DSH",
- models.ReServiceCodeDLH: "DLH",
- models.ReServiceCodeFSC: "FSC",
+ models.ReServiceCodeDPK: "DPK",
+ models.ReServiceCodeDUPK: "DUPK",
+ models.ReServiceCodeDOP: "DOP",
+ models.ReServiceCodeDDP: "DDP",
+ models.ReServiceCodeDSH: "DSH",
+ models.ReServiceCodeDLH: "DLH",
+ models.ReServiceCodeFSC: "FSC",
+ models.ReServiceCodeISLH: "ISLH",
+ models.ReServiceCodeIHPK: "IHPK",
+ models.ReServiceCodeIHUPK: "IHUPK",
}
// If service item is of a type we need for a specific calculation, get its price
@@ -402,11 +363,11 @@ func (p *ppmCloseoutFetcher) getServiceItemPrices(appCtx appcontext.AppContext,
serviceItemLookups := serviceparamvaluelookups.InitializeLookups(appCtx, ppmToMtoShipment, serviceItem)
// This is the struct that gets passed to every param lookup() method that was initialized above
- keyData := serviceparamvaluelookups.NewServiceItemParamKeyData(p.planner, serviceItemLookups, serviceItem, ppmToMtoShipment, contract.Code)
+ keyData := serviceparamvaluelookups.NewServiceItemParamKeyData(p.planner, serviceItemLookups, serviceItem, ppmToMtoShipment, contract.Code, contract.ID)
// The distance value gets saved to the mto shipment model to reduce repeated api calls.
var shipmentWithDistance models.MTOShipment
- err = appCtx.DB().Find(&shipmentWithDistance, ppmShipment.Shipment.ID)
+ err = appCtx.DB().Eager("PPMShipment").Find(&shipmentWithDistance, ppmShipment.Shipment.ID)
if err != nil {
logger.Error("could not find shipment in the database")
return serviceItemPrices{}, err
@@ -419,7 +380,7 @@ func (p *ppmCloseoutFetcher) getServiceItemPrices(appCtx appcontext.AppContext,
for _, param := range paramsForServiceCode(serviceItem.ReService.Code, paramsForServiceItems) {
paramKey := param.ServiceItemParamKey
// This is where the lookup() method of each service item param is actually evaluated
- paramValue, serviceParamErr := keyData.ServiceParamValue(appCtx, paramKey.Key) // Fails with "DistanceZip" param?
+ paramValue, serviceParamErr := keyData.ServiceParamValue(appCtx, paramKey.Key)
if serviceParamErr != nil {
logger.Error("could not calculate param value lookup", zap.Error(serviceParamErr))
return serviceItemPrices{}, serviceParamErr
@@ -452,6 +413,12 @@ func (p *ppmCloseoutFetcher) getServiceItemPrices(appCtx appcontext.AppContext,
totalPrice = totalPrice.AddCents(centsValue)
switch serviceItem.ReService.Code {
+ case models.ReServiceCodeIHPK: // Int'l pack
+ intlPackPrice += centsValue
+ case models.ReServiceCodeIHUPK: // Int'l unpack
+ intlUnpackPrice += centsValue
+ case models.ReServiceCodeISLH: // Int'l shipping & linehaul
+ intlLinehaulPrice += centsValue
case models.ReServiceCodeDPK:
packPrice += centsValue
case models.ReServiceCodeDUPK:
@@ -488,6 +455,9 @@ func (p *ppmCloseoutFetcher) getServiceItemPrices(appCtx appcontext.AppContext,
returnPriceObj.storageReimbursementCosts = &sitCosts
returnPriceObj.haulPrice = &haulPrice
returnPriceObj.haulFSC = &haulFSC
+ returnPriceObj.intlLinehaulPrice = &intlLinehaulPrice
+ returnPriceObj.intlPackPrice = &intlPackPrice
+ returnPriceObj.intlUnpackPrice = &intlUnpackPrice
return returnPriceObj, nil
}
diff --git a/pkg/services/ppm_closeout/ppm_closeout_test.go b/pkg/services/ppm_closeout/ppm_closeout_test.go
index ac86dee3f04..cc0fff6a390 100644
--- a/pkg/services/ppm_closeout/ppm_closeout_test.go
+++ b/pkg/services/ppm_closeout/ppm_closeout_test.go
@@ -24,7 +24,7 @@ const (
ppmBuildWaitingOnCustomer = "waitingOnCustomer"
)
-func (suite *PPMCloseoutSuite) TestPPMShipmentCreator() {
+func (suite *PPMCloseoutSuite) TestPPMShipmentCloseout() {
// One-time test setup
mockedPlanner := &mocks.Planner{}
diff --git a/pkg/services/ppmshipment/ppm_estimator.go b/pkg/services/ppmshipment/ppm_estimator.go
index e49d7846bbe..cf77b16fb04 100644
--- a/pkg/services/ppmshipment/ppm_estimator.go
+++ b/pkg/services/ppmshipment/ppm_estimator.go
@@ -203,6 +203,12 @@ func (f *estimatePPM) estimateIncentive(appCtx appcontext.AppContext, oldPPMShip
}
}
+ contractDate := newPPMShipment.ExpectedDepartureDate
+ contract, err := serviceparamvaluelookups.FetchContract(appCtx, contractDate)
+ if err != nil {
+ return nil, nil, err
+ }
+
calculateSITEstimate := shouldCalculateSITCost(newPPMShipment, &oldPPMShipment)
// Clear out any previously calculated SIT estimated costs, if SIT is no longer expected
@@ -216,33 +222,66 @@ func (f *estimatePPM) estimateIncentive(appCtx appcontext.AppContext, oldPPMShip
return oldPPMShipment.EstimatedIncentive, newPPMShipment.SITEstimatedCost, nil
}
- contractDate := newPPMShipment.ExpectedDepartureDate
- contract, err := serviceparamvaluelookups.FetchContract(appCtx, contractDate)
- if err != nil {
- return nil, nil, err
- }
-
estimatedIncentive := oldPPMShipment.EstimatedIncentive
- if !skipCalculatingEstimatedIncentive {
- // Clear out advance and advance requested fields when the estimated incentive is reset.
- newPPMShipment.HasRequestedAdvance = nil
- newPPMShipment.AdvanceAmountRequested = nil
+ estimatedSITCost := oldPPMShipment.SITEstimatedCost
- estimatedIncentive, err = f.calculatePrice(appCtx, newPPMShipment, 0, contract, false)
- if err != nil {
- return nil, nil, err
+ // if the PPM is international, we will use a db func
+ if newPPMShipment.Shipment.MarketCode != models.MarketCodeInternational {
+
+ if !skipCalculatingEstimatedIncentive {
+ // Clear out advance and advance requested fields when the estimated incentive is reset.
+ newPPMShipment.HasRequestedAdvance = nil
+ newPPMShipment.AdvanceAmountRequested = nil
+
+ estimatedIncentive, err = f.calculatePrice(appCtx, newPPMShipment, 0, contract, false)
+ if err != nil {
+ return nil, nil, err
+ }
}
- }
- estimatedSITCost := oldPPMShipment.SITEstimatedCost
- if calculateSITEstimate {
- estimatedSITCost, err = CalculateSITCost(appCtx, newPPMShipment, contract)
- if err != nil {
- return nil, nil, err
+ if calculateSITEstimate {
+ estimatedSITCost, err = CalculateSITCost(appCtx, newPPMShipment, contract)
+ if err != nil {
+ return nil, nil, err
+ }
+ }
+
+ return estimatedIncentive, estimatedSITCost, nil
+
+ } else {
+ pickupAddress := newPPMShipment.PickupAddress
+ destinationAddress := newPPMShipment.DestinationAddress
+
+ if !skipCalculatingEstimatedIncentive {
+ // Clear out advance and advance requested fields when the estimated incentive is reset.
+ newPPMShipment.HasRequestedAdvance = nil
+ newPPMShipment.AdvanceAmountRequested = nil
+
+ estimatedIncentive, err = f.CalculateOCONUSIncentive(appCtx, newPPMShipment.ID, *pickupAddress, *destinationAddress, contractDate, newPPMShipment.EstimatedWeight.Int(), true, false, false)
+ if err != nil {
+ return nil, nil, fmt.Errorf("failed to calculate estimated PPM incentive: %w", err)
+ }
+ }
+
+ if calculateSITEstimate {
+ var sitAddress models.Address
+ isOrigin := *newPPMShipment.SITLocation == models.SITLocationTypeOrigin
+ if isOrigin {
+ sitAddress = *newPPMShipment.PickupAddress
+ } else if !isOrigin {
+ sitAddress = *newPPMShipment.DestinationAddress
+ } else {
+ return estimatedIncentive, estimatedSITCost, nil
+ }
+ daysInSIT := additionalDaysInSIT(*newPPMShipment.SITEstimatedEntryDate, *newPPMShipment.SITEstimatedDepartureDate)
+ estimatedSITCost, err = f.CalculateOCONUSSITCosts(appCtx, newPPMShipment.ID, sitAddress.ID, isOrigin, contractDate, newPPMShipment.EstimatedWeight.Int(), daysInSIT)
+ if err != nil {
+ return nil, nil, fmt.Errorf("failed to calculate estimated PPM incentive: %w", err)
+ }
}
- }
- return estimatedIncentive, estimatedSITCost, nil
+ return estimatedIncentive, estimatedSITCost, nil
+ }
}
func (f *estimatePPM) maxIncentive(appCtx appcontext.AppContext, oldPPMShipment models.PPMShipment, newPPMShipment *models.PPMShipment, checks ...ppmShipmentValidator) (*unit.Cents, error) {
@@ -261,7 +300,7 @@ func (f *estimatePPM) maxIncentive(appCtx appcontext.AppContext, oldPPMShipment
// 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",
+ "Orders.Entitlement", "Orders.OriginDutyLocation.Address", "Orders.NewDutyLocation.Address",
).Where("show = TRUE").Find(&move, newPPMShipment.Shipment.MoveTaskOrderID)
if err != nil {
return nil, apperror.NewNotFoundError(newPPMShipment.ID, " error querying move")
@@ -277,14 +316,27 @@ func (f *estimatePPM) maxIncentive(appCtx appcontext.AppContext, oldPPMShipment
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
- }
+ if newPPMShipment.Shipment.MarketCode != models.MarketCodeInternational {
- return maxIncentive, nil
+ // 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
+ } else {
+ pickupAddress := orders.OriginDutyLocation.Address
+ destinationAddress := orders.NewDutyLocation.Address
+
+ maxIncentive, err := f.CalculateOCONUSIncentive(appCtx, newPPMShipment.ID, pickupAddress, destinationAddress, contractDate, *orders.Entitlement.DBAuthorizedWeight, false, false, true)
+ if err != nil {
+ return nil, fmt.Errorf("failed to calculate estimated PPM incentive: %w", err)
+ }
+
+ return maxIncentive, nil
+ }
}
func (f *estimatePPM) finalIncentive(appCtx appcontext.AppContext, oldPPMShipment models.PPMShipment, newPPMShipment *models.PPMShipment, checks ...ppmShipmentValidator) (*unit.Cents, error) {
@@ -307,32 +359,51 @@ func (f *estimatePPM) finalIncentive(appCtx appcontext.AppContext, oldPPMShipmen
newTotalWeight = *newPPMShipment.AllowableWeight
}
- isMissingInfo := shouldSetFinalIncentiveToNil(newPPMShipment, newTotalWeight)
- var skipCalculateFinalIncentive bool
- finalIncentive := oldPPMShipment.FinalIncentive
+ contractDate := newPPMShipment.ExpectedDepartureDate
+ if newPPMShipment.ActualMoveDate != nil {
+ contractDate = *newPPMShipment.ActualMoveDate
+ }
+ contract, err := serviceparamvaluelookups.FetchContract(appCtx, contractDate)
+ if err != nil {
+ return nil, err
+ }
+
+ if newPPMShipment.Shipment.MarketCode != models.MarketCodeInternational {
+ isMissingInfo := shouldSetFinalIncentiveToNil(newPPMShipment, newTotalWeight)
+ var skipCalculateFinalIncentive bool
+ finalIncentive := oldPPMShipment.FinalIncentive
+ if !isMissingInfo {
+ skipCalculateFinalIncentive = shouldSkipCalculatingFinalIncentive(newPPMShipment, &oldPPMShipment, originalTotalWeight, newTotalWeight)
+ if !skipCalculateFinalIncentive {
- if !isMissingInfo {
- skipCalculateFinalIncentive = shouldSkipCalculatingFinalIncentive(newPPMShipment, &oldPPMShipment, originalTotalWeight, newTotalWeight)
- if !skipCalculateFinalIncentive {
- contractDate := newPPMShipment.ExpectedDepartureDate
- if newPPMShipment.ActualMoveDate != nil {
- contractDate = *newPPMShipment.ActualMoveDate
- }
- contract, err := serviceparamvaluelookups.FetchContract(appCtx, contractDate)
- if err != nil {
- return nil, err
+ finalIncentive, err := f.calculatePrice(appCtx, newPPMShipment, newTotalWeight, contract, false)
+ if err != nil {
+ return nil, err
+ }
+ return finalIncentive, nil
}
+ } else {
+ finalIncentive = nil
- finalIncentive, err = f.calculatePrice(appCtx, newPPMShipment, newTotalWeight, contract, false)
+ return finalIncentive, nil
+ }
+
+ return finalIncentive, nil
+ } else {
+ pickupAddress := newPPMShipment.PickupAddress
+ destinationAddress := newPPMShipment.DestinationAddress
+
+ // we can't calculate actual incentive without the weight
+ if newTotalWeight != 0 {
+ finalIncentive, err := f.CalculateOCONUSIncentive(appCtx, newPPMShipment.ID, *pickupAddress, *destinationAddress, contractDate, newTotalWeight.Int(), false, true, false)
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("failed to calculate estimated PPM incentive: %w", err)
}
+ return finalIncentive, nil
+ } else {
+ return nil, nil
}
- } else {
- finalIncentive = nil
}
-
- return finalIncentive, nil
}
// SumWeightTickets return the total weight of all weightTickets associated with a PPMShipment, returns 0 if there is no valid weight
@@ -372,7 +443,7 @@ func (f estimatePPM) calculatePrice(appCtx appcontext.AppContext, ppmShipment *m
logger := appCtx.Logger()
zeroTotal := false
- serviceItemsToPrice := BaseServiceItems(ppmShipment.ShipmentID)
+ serviceItemsToPrice := BaseServiceItems(*ppmShipment)
var move models.Move
err := appCtx.DB().Q().Eager(
@@ -462,7 +533,7 @@ func (f estimatePPM) calculatePrice(appCtx appcontext.AppContext, ppmShipment *m
serviceItemLookups := serviceparamvaluelookups.InitializeLookups(appCtx, mtoShipment, serviceItem)
// This is the struct that gets passed to every param lookup() method that was initialized above
- keyData := serviceparamvaluelookups.NewServiceItemParamKeyData(f.planner, serviceItemLookups, serviceItem, mtoShipment, contract.Code)
+ keyData := serviceparamvaluelookups.NewServiceItemParamKeyData(f.planner, serviceItemLookups, serviceItem, mtoShipment, contract.Code, contract.ID)
// The distance value gets saved to the mto shipment model to reduce repeated api calls.
var shipmentWithDistance models.MTOShipment
@@ -540,7 +611,7 @@ func (f estimatePPM) priceBreakdown(appCtx appcontext.AppContext, ppmShipment *m
var unpacking unit.Cents
var storage unit.Cents
- serviceItemsToPrice := BaseServiceItems(ppmShipment.ShipmentID)
+ serviceItemsToPrice := BaseServiceItems(*ppmShipment)
// Replace linehaul pricer with shorthaul pricer if move is within the same Zip3
var pickupPostal, destPostal string
@@ -625,7 +696,7 @@ func (f estimatePPM) priceBreakdown(appCtx appcontext.AppContext, ppmShipment *m
serviceItemLookups := serviceparamvaluelookups.InitializeLookups(appCtx, mtoShipment, serviceItem)
// This is the struct that gets passed to every param lookup() method that was initialized above
- keyData := serviceparamvaluelookups.NewServiceItemParamKeyData(f.planner, serviceItemLookups, serviceItem, mtoShipment, contract.Code)
+ keyData := serviceparamvaluelookups.NewServiceItemParamKeyData(f.planner, serviceItemLookups, serviceItem, mtoShipment, contract.Code, contract.ID)
// The distance value gets saved to the mto shipment model to reduce repeated api calls.
var shipmentWithDistance models.MTOShipment
@@ -693,40 +764,117 @@ func (f estimatePPM) priceBreakdown(appCtx appcontext.AppContext, ppmShipment *m
return linehaul, fuel, origin, dest, packing, unpacking, storage, nil
}
-func CalculateSITCost(appCtx appcontext.AppContext, ppmShipment *models.PPMShipment, contract models.ReContract) (*unit.Cents, error) {
- logger := appCtx.Logger()
+// function for calculating incentives for OCONUS PPM shipments
+// this uses a db function that takes in values needed to come up with the estimated/actual/max incentives
+// this simulates the reimbursement for an iHHG move with ISLH, IHPK, IHUPK, and CONUS portion of FSC
+func (f *estimatePPM) CalculateOCONUSIncentive(appCtx appcontext.AppContext, ppmShipmentID uuid.UUID, pickupAddress models.Address, destinationAddress models.Address, moveDate time.Time, weight int, isEstimated bool, isActual bool, isMax bool) (*unit.Cents, error) {
+ var mileage int
+ ppmPort, err := models.FetchPortLocationByCode(appCtx.DB(), "4E1") // Tacoma, WA port
+ if err != nil {
+ return nil, fmt.Errorf("failed to fetch port location: %w", err)
+ }
+
+ // check if addresses are OCONUS or CONUS -> this determines how we check mileage to/from the authorized port
+ isPickupOconus := pickupAddress.IsOconus != nil && *pickupAddress.IsOconus
+ isDestinationOconus := destinationAddress.IsOconus != nil && *destinationAddress.IsOconus
+
+ switch {
+ case isPickupOconus && isDestinationOconus:
+ // OCONUS -> OCONUS, we only reimburse for the CONUS mileage of the PPM
+ mileage = 0
+ case isPickupOconus && !isDestinationOconus:
+ // OCONUS -> CONUS (port ZIP -> address ZIP)
+ mileage, err = f.planner.ZipTransitDistance(appCtx, ppmPort.UsPostRegionCity.UsprZipID, destinationAddress.PostalCode, true)
+ if err != nil {
+ return nil, fmt.Errorf("failed to calculate OCONUS to CONUS mileage: %w", err)
+ }
+ case !isPickupOconus && isDestinationOconus:
+ // CONUS -> OCONUS (address ZIP -> port ZIP)
+ mileage, err = f.planner.ZipTransitDistance(appCtx, pickupAddress.PostalCode, ppmPort.UsPostRegionCity.UsprZipID, true)
+ if err != nil {
+ return nil, fmt.Errorf("failed to calculate CONUS to OCONUS mileage: %w", err)
+ }
+ default:
+ // covering down on CONUS -> CONUS moves - they should not appear here
+ return nil, fmt.Errorf("invalid pickup and destination configuration: pickup isOconus=%v, destination isOconus=%v", isPickupOconus, isDestinationOconus)
+ }
+
+ incentive, err := models.CalculatePPMIncentive(appCtx.DB(), ppmShipmentID, pickupAddress.ID, destinationAddress.ID, moveDate, mileage, weight, isEstimated, isActual, isMax)
+ if err != nil {
+ return nil, fmt.Errorf("failed to calculate PPM incentive: %w", err)
+ }
+
+ return (*unit.Cents)(&incentive.TotalIncentive), nil
+}
+
+func (f *estimatePPM) CalculateOCONUSSITCosts(appCtx appcontext.AppContext, ppmID uuid.UUID, addressID uuid.UUID, isOrigin bool, moveDate time.Time, weight int, sitDays int) (*unit.Cents, error) {
+ if sitDays <= 0 {
+ return nil, fmt.Errorf("SIT days must be greater than zero")
+ }
+
+ if weight <= 0 {
+ return nil, fmt.Errorf("weight must be greater than zero")
+ }
+ sitCosts, err := models.CalculatePPMSITCost(appCtx.DB(), ppmID, addressID, isOrigin, moveDate, weight, sitDays)
+ if err != nil {
+ return nil, fmt.Errorf("failed to calculate SIT costs: %w", err)
+ }
+
+ return (*unit.Cents)(&sitCosts.TotalSITCost), nil
+}
+
+func CalculateSITCost(appCtx appcontext.AppContext, ppmShipment *models.PPMShipment, contract models.ReContract) (*unit.Cents, error) {
additionalDaysInSIT := additionalDaysInSIT(*ppmShipment.SITEstimatedEntryDate, *ppmShipment.SITEstimatedDepartureDate)
- serviceItemsToPrice := StorageServiceItems(ppmShipment.ShipmentID, *ppmShipment.SITLocation, additionalDaysInSIT)
+ if ppmShipment.Shipment.MarketCode != models.MarketCodeInternational {
+ logger := appCtx.Logger()
- totalPrice := unit.Cents(0)
- for _, serviceItem := range serviceItemsToPrice {
- pricer, err := ghcrateengine.PricerForServiceItem(serviceItem.ReService.Code)
- if err != nil {
- logger.Error("unable to find pricer for service item", zap.Error(err))
- return nil, err
+ serviceItemsToPrice := StorageServiceItems(*ppmShipment, *ppmShipment.SITLocation, additionalDaysInSIT)
+
+ totalPrice := unit.Cents(0)
+ for _, serviceItem := range serviceItemsToPrice {
+ pricer, err := ghcrateengine.PricerForServiceItem(serviceItem.ReService.Code)
+ if err != nil {
+ logger.Error("unable to find pricer for service item", zap.Error(err))
+ return nil, err
+ }
+
+ var price *unit.Cents
+ switch serviceItemPricer := pricer.(type) {
+ case services.DomesticOriginFirstDaySITPricer, services.DomesticDestinationFirstDaySITPricer:
+ price, _, err = priceFirstDaySIT(appCtx, serviceItemPricer, serviceItem, ppmShipment, contract)
+ case services.DomesticOriginAdditionalDaysSITPricer, services.DomesticDestinationAdditionalDaysSITPricer:
+ price, _, err = priceAdditionalDaySIT(appCtx, serviceItemPricer, serviceItem, ppmShipment, additionalDaysInSIT, contract)
+ default:
+ return nil, fmt.Errorf("unknown SIT pricer type found for service item code %s", serviceItem.ReService.Code)
+ }
+
+ if err != nil {
+ return nil, err
+ }
+
+ logger.Debug(fmt.Sprintf("Price of service item %s %d", serviceItem.ReService.Code, *price))
+ totalPrice += *price
}
- var price *unit.Cents
- switch serviceItemPricer := pricer.(type) {
- case services.DomesticOriginFirstDaySITPricer, services.DomesticDestinationFirstDaySITPricer:
- price, _, err = priceFirstDaySIT(appCtx, serviceItemPricer, serviceItem, ppmShipment, contract)
- case services.DomesticOriginAdditionalDaysSITPricer, services.DomesticDestinationAdditionalDaysSITPricer:
- price, _, err = priceAdditionalDaySIT(appCtx, serviceItemPricer, serviceItem, ppmShipment, additionalDaysInSIT, contract)
- default:
- return nil, fmt.Errorf("unknown SIT pricer type found for service item code %s", serviceItem.ReService.Code)
+ return &totalPrice, nil
+ } else {
+ var sitAddress models.Address
+ isOrigin := *ppmShipment.SITLocation == models.SITLocationTypeOrigin
+ if isOrigin {
+ sitAddress = *ppmShipment.PickupAddress
+ } else {
+ sitAddress = *ppmShipment.DestinationAddress
}
+ contractDate := ppmShipment.ExpectedDepartureDate
+ totalSITCost, err := models.CalculatePPMSITCost(appCtx.DB(), ppmShipment.ID, sitAddress.ID, isOrigin, contractDate, ppmShipment.SITEstimatedWeight.Int(), additionalDaysInSIT)
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("failed to calculate PPM SIT incentive: %w", err)
}
-
- logger.Debug(fmt.Sprintf("Price of service item %s %d", serviceItem.ReService.Code, *price))
- totalPrice += *price
+ return (*unit.Cents)(&totalSITCost.TotalSITCost), nil
}
-
- return &totalPrice, nil
}
func CalculateSITCostBreakdown(appCtx appcontext.AppContext, ppmShipment *models.PPMShipment, contract models.ReContract) (*models.PPMSITEstimatedCostInfo, error) {
@@ -736,7 +884,7 @@ func CalculateSITCostBreakdown(appCtx appcontext.AppContext, ppmShipment *models
additionalDaysInSIT := additionalDaysInSIT(*ppmShipment.SITEstimatedEntryDate, *ppmShipment.SITEstimatedDepartureDate)
- serviceItemsToPrice := StorageServiceItems(ppmShipment.ShipmentID, *ppmShipment.SITLocation, additionalDaysInSIT)
+ serviceItemsToPrice := StorageServiceItems(*ppmShipment, *ppmShipment.SITLocation, additionalDaysInSIT)
totalPrice := unit.Cents(0)
for _, serviceItem := range serviceItemsToPrice {
@@ -750,8 +898,12 @@ func CalculateSITCostBreakdown(appCtx appcontext.AppContext, ppmShipment *models
switch serviceItemPricer := pricer.(type) {
case services.DomesticOriginFirstDaySITPricer, services.DomesticDestinationFirstDaySITPricer:
price, ppmSITEstimatedCostInfoData, err = calculateFirstDaySITCostBreakdown(appCtx, serviceItemPricer, serviceItem, ppmShipment, contract, ppmSITEstimatedCostInfoData, logger)
+ case services.IntlOriginFirstDaySITPricer, services.IntlDestinationFirstDaySITPricer:
+ price, ppmSITEstimatedCostInfoData, err = calculateIntlFirstDaySITCostBreakdown(appCtx, serviceItemPricer, serviceItem, ppmShipment, contract, ppmSITEstimatedCostInfoData, logger)
case services.DomesticOriginAdditionalDaysSITPricer, services.DomesticDestinationAdditionalDaysSITPricer:
price, ppmSITEstimatedCostInfoData, err = calculateAdditionalDaySITCostBreakdown(appCtx, serviceItemPricer, serviceItem, ppmShipment, contract, additionalDaysInSIT, ppmSITEstimatedCostInfoData, logger)
+ case services.IntlOriginAdditionalDaySITPricer, services.IntlDestinationAdditionalDaySITPricer:
+ price, ppmSITEstimatedCostInfoData, err = calculateIntlAdditionalDaySITCostBreakdown(appCtx, serviceItemPricer, serviceItem, ppmShipment, contract, additionalDaysInSIT, ppmSITEstimatedCostInfoData, logger)
default:
return nil, fmt.Errorf("unknown SIT pricer type found for service item code %s", serviceItem.ReService.Code)
}
@@ -795,6 +947,33 @@ func calculateFirstDaySITCostBreakdown(appCtx appcontext.AppContext, serviceItem
return price, ppmSITEstimatedCostInfoData, nil
}
+func calculateIntlFirstDaySITCostBreakdown(appCtx appcontext.AppContext, serviceItemPricer services.ParamsPricer, serviceItem models.MTOServiceItem, ppmShipment *models.PPMShipment, contract models.ReContract, ppmSITEstimatedCostInfoData *models.PPMSITEstimatedCostInfo, logger *zap.Logger) (*unit.Cents, *models.PPMSITEstimatedCostInfo, error) {
+ price, priceParams, err := priceFirstDaySIT(appCtx, serviceItemPricer, serviceItem, ppmShipment, contract)
+ if err != nil {
+ return nil, nil, err
+ }
+ ppmSITEstimatedCostInfoData.PriceFirstDaySIT = price
+ for _, param := range priceParams {
+ switch param.Key {
+ case models.ServiceItemParamNameServiceAreaOrigin:
+ ppmSITEstimatedCostInfoData.ParamsFirstDaySIT.ServiceAreaOrigin = param.Value
+ case models.ServiceItemParamNameServiceAreaDest:
+ ppmSITEstimatedCostInfoData.ParamsFirstDaySIT.ServiceAreaDestination = param.Value
+ case models.ServiceItemParamNameIsPeak:
+ ppmSITEstimatedCostInfoData.ParamsFirstDaySIT.IsPeak = param.Value
+ case models.ServiceItemParamNameContractYearName:
+ ppmSITEstimatedCostInfoData.ParamsFirstDaySIT.ContractYearName = param.Value
+ case models.ServiceItemParamNamePriceRateOrFactor:
+ ppmSITEstimatedCostInfoData.ParamsFirstDaySIT.PriceRateOrFactor = param.Value
+ case models.ServiceItemParamNameEscalationCompounded:
+ ppmSITEstimatedCostInfoData.ParamsFirstDaySIT.EscalationCompounded = param.Value
+ default:
+ logger.Debug(fmt.Sprintf("Unexpected ServiceItemParam in PPM First Day SIT: %s, %s", param.Key, param.Value))
+ }
+ }
+ return price, ppmSITEstimatedCostInfoData, nil
+}
+
func calculateAdditionalDaySITCostBreakdown(appCtx appcontext.AppContext, serviceItemPricer services.ParamsPricer, serviceItem models.MTOServiceItem, ppmShipment *models.PPMShipment, contract models.ReContract, additionalDaysInSIT int, ppmSITEstimatedCostInfoData *models.PPMSITEstimatedCostInfo, logger *zap.Logger) (*unit.Cents, *models.PPMSITEstimatedCostInfo, error) {
price, priceParams, err := priceAdditionalDaySIT(appCtx, serviceItemPricer, serviceItem, ppmShipment, additionalDaysInSIT, contract)
if err != nil {
@@ -824,56 +1003,119 @@ func calculateAdditionalDaySITCostBreakdown(appCtx appcontext.AppContext, servic
return price, ppmSITEstimatedCostInfoData, nil
}
-func priceFirstDaySIT(appCtx appcontext.AppContext, pricer services.ParamsPricer, serviceItem models.MTOServiceItem, ppmShipment *models.PPMShipment, contract models.ReContract) (*unit.Cents, services.PricingDisplayParams, error) {
- firstDayPricer, ok := pricer.(services.DomesticFirstDaySITPricer)
- if !ok {
- return nil, nil, errors.New("ppm estimate pricer for SIT service item does not implement the first day pricer interface")
- }
-
- // Need to declare if origin or destination for the serviceAreaLookup, otherwise we already have it
- serviceAreaPostalCode := ppmShipment.PickupAddress.PostalCode
- serviceAreaKey := models.ServiceItemParamNameServiceAreaOrigin
- if serviceItem.ReService.Code == models.ReServiceCodeDDFSIT {
- serviceAreaPostalCode = ppmShipment.DestinationAddress.PostalCode
- serviceAreaKey = models.ServiceItemParamNameServiceAreaDest
- }
-
- serviceAreaLookup := serviceparamvaluelookups.ServiceAreaLookup{
- Address: models.Address{PostalCode: serviceAreaPostalCode},
- }
- serviceArea, err := serviceAreaLookup.ParamValue(appCtx, contract.Code)
+func calculateIntlAdditionalDaySITCostBreakdown(appCtx appcontext.AppContext, serviceItemPricer services.ParamsPricer, serviceItem models.MTOServiceItem, ppmShipment *models.PPMShipment, contract models.ReContract, additionalDaysInSIT int, ppmSITEstimatedCostInfoData *models.PPMSITEstimatedCostInfo, logger *zap.Logger) (*unit.Cents, *models.PPMSITEstimatedCostInfo, error) {
+ price, priceParams, err := priceAdditionalDaySIT(appCtx, serviceItemPricer, serviceItem, ppmShipment, additionalDaysInSIT, contract)
if err != nil {
return nil, nil, err
}
-
- serviceAreaParam := services.PricingDisplayParam{
- Key: serviceAreaKey,
- Value: serviceArea,
+ ppmSITEstimatedCostInfoData.PriceAdditionalDaySIT = price
+ for _, param := range priceParams {
+ switch param.Key {
+ case models.ServiceItemParamNameServiceAreaOrigin:
+ ppmSITEstimatedCostInfoData.ParamsAdditionalDaySIT.ServiceAreaOrigin = param.Value
+ case models.ServiceItemParamNameServiceAreaDest:
+ ppmSITEstimatedCostInfoData.ParamsAdditionalDaySIT.ServiceAreaDestination = param.Value
+ case models.ServiceItemParamNameIsPeak:
+ ppmSITEstimatedCostInfoData.ParamsAdditionalDaySIT.IsPeak = param.Value
+ case models.ServiceItemParamNameContractYearName:
+ ppmSITEstimatedCostInfoData.ParamsAdditionalDaySIT.ContractYearName = param.Value
+ case models.ServiceItemParamNamePriceRateOrFactor:
+ ppmSITEstimatedCostInfoData.ParamsAdditionalDaySIT.PriceRateOrFactor = param.Value
+ case models.ServiceItemParamNameEscalationCompounded:
+ ppmSITEstimatedCostInfoData.ParamsAdditionalDaySIT.EscalationCompounded = param.Value
+ case models.ServiceItemParamNameNumberDaysSIT:
+ ppmSITEstimatedCostInfoData.ParamsAdditionalDaySIT.NumberDaysSIT = param.Value
+ default:
+ logger.Debug(fmt.Sprintf("Unexpected ServiceItemParam in PPM Additional Day SIT: %s, %s", param.Key, param.Value))
+ }
}
+ return price, ppmSITEstimatedCostInfoData, nil
+}
- // Since this function may be ran before closeout, we need to account for if there's no actual move date yet.
- if ppmShipment.ActualMoveDate != nil {
- price, pricingParams, err := firstDayPricer.Price(appCtx, contract.Code, *ppmShipment.ActualMoveDate, *ppmShipment.SITEstimatedWeight, serviceArea, true)
+func priceFirstDaySIT(appCtx appcontext.AppContext, pricer services.ParamsPricer, serviceItem models.MTOServiceItem, ppmShipment *models.PPMShipment, contract models.ReContract) (*unit.Cents, services.PricingDisplayParams, error) {
+ if serviceItem.ReService.Code == models.ReServiceCodeIOFSIT || serviceItem.ReService.Code == models.ReServiceCodeIDFSIT {
+ var addressID uuid.UUID
+ if serviceItem.ReService.Code == models.ReServiceCodeIOFSIT {
+ addressID = *ppmShipment.PickupAddressID
+ } else {
+ addressID = *ppmShipment.DestinationAddressID
+ }
+ reServiceID, _ := models.FetchReServiceByCode(appCtx.DB(), serviceItem.ReService.Code)
+ intlOtherPrice, _ := models.FetchReIntlOtherPrice(appCtx.DB(), addressID, reServiceID.ID, contract.ID, &ppmShipment.ExpectedDepartureDate)
+ firstDayPricer, ok := pricer.(services.IntlOriginFirstDaySITPricer)
+ if !ok {
+ return nil, nil, errors.New("ppm estimate pricer for SIT service item does not implement the first day pricer interface")
+ }
+ if ppmShipment.ActualMoveDate != nil {
+ price, pricingParams, err := firstDayPricer.Price(appCtx, contract.Code, *ppmShipment.ActualMoveDate, *ppmShipment.SITEstimatedWeight, intlOtherPrice.PerUnitCents.Int())
+ if err != nil {
+ return nil, nil, err
+ }
+
+ appCtx.Logger().Debug(fmt.Sprintf("Pricing params for first day SIT %+v", pricingParams), zap.String("shipmentId", ppmShipment.ShipmentID.String()))
+
+ return &price, pricingParams, nil
+ }
+
+ price, pricingParams, err := firstDayPricer.Price(appCtx, contract.Code, ppmShipment.ExpectedDepartureDate, *ppmShipment.SITEstimatedWeight, intlOtherPrice.PerUnitCents.Int())
if err != nil {
return nil, nil, err
}
- pricingParams = append(pricingParams, serviceAreaParam)
-
appCtx.Logger().Debug(fmt.Sprintf("Pricing params for first day SIT %+v", pricingParams), zap.String("shipmentId", ppmShipment.ShipmentID.String()))
return &price, pricingParams, nil
- }
- price, pricingParams, err := firstDayPricer.Price(appCtx, contract.Code, ppmShipment.ExpectedDepartureDate, *ppmShipment.SITEstimatedWeight, serviceArea, true)
- if err != nil {
- return nil, nil, err
- }
+ } else {
+ firstDayPricer, ok := pricer.(services.DomesticFirstDaySITPricer)
+ if !ok {
+ return nil, nil, errors.New("ppm estimate pricer for SIT service item does not implement the first day pricer interface")
+ }
+
+ // Need to declare if origin or destination for the serviceAreaLookup, otherwise we already have it
+ serviceAreaPostalCode := ppmShipment.PickupAddress.PostalCode
+ serviceAreaKey := models.ServiceItemParamNameServiceAreaOrigin
+ if serviceItem.ReService.Code == models.ReServiceCodeDDFSIT {
+ serviceAreaPostalCode = ppmShipment.DestinationAddress.PostalCode
+ serviceAreaKey = models.ServiceItemParamNameServiceAreaDest
+ }
+
+ serviceAreaLookup := serviceparamvaluelookups.ServiceAreaLookup{
+ Address: models.Address{PostalCode: serviceAreaPostalCode},
+ }
+ serviceArea, err := serviceAreaLookup.ParamValue(appCtx, contract.Code)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ serviceAreaParam := services.PricingDisplayParam{
+ Key: serviceAreaKey,
+ Value: serviceArea,
+ }
+
+ // Since this function may be ran before closeout, we need to account for if there's no actual move date yet.
+ if ppmShipment.ActualMoveDate != nil {
+ price, pricingParams, err := firstDayPricer.Price(appCtx, contract.Code, *ppmShipment.ActualMoveDate, *ppmShipment.SITEstimatedWeight, serviceArea, true)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ pricingParams = append(pricingParams, serviceAreaParam)
+
+ appCtx.Logger().Debug(fmt.Sprintf("Pricing params for first day SIT %+v", pricingParams), zap.String("shipmentId", ppmShipment.ShipmentID.String()))
+
+ return &price, pricingParams, nil
+ }
+ price, pricingParams, err := firstDayPricer.Price(appCtx, contract.Code, ppmShipment.ExpectedDepartureDate, *ppmShipment.SITEstimatedWeight, serviceArea, true)
+ if err != nil {
+ return nil, nil, err
+ }
- pricingParams = append(pricingParams, serviceAreaParam)
+ pricingParams = append(pricingParams, serviceAreaParam)
- appCtx.Logger().Debug(fmt.Sprintf("Pricing params for first day SIT %+v", pricingParams), zap.String("shipmentId", ppmShipment.ShipmentID.String()))
+ appCtx.Logger().Debug(fmt.Sprintf("Pricing params for first day SIT %+v", pricingParams), zap.String("shipmentId", ppmShipment.ShipmentID.String()))
- return &price, pricingParams, nil
+ return &price, pricingParams, nil
+ }
}
func additionalDaysInSIT(sitEntryDate time.Time, sitDepartureDate time.Time) int {
@@ -887,69 +1129,116 @@ func additionalDaysInSIT(sitEntryDate time.Time, sitDepartureDate time.Time) int
}
func priceAdditionalDaySIT(appCtx appcontext.AppContext, pricer services.ParamsPricer, serviceItem models.MTOServiceItem, ppmShipment *models.PPMShipment, additionalDaysInSIT int, contract models.ReContract) (*unit.Cents, services.PricingDisplayParams, error) {
- additionalDaysPricer, ok := pricer.(services.DomesticAdditionalDaysSITPricer)
- if !ok {
- return nil, nil, errors.New("ppm estimate pricer for SIT service item does not implement the additional days pricer interface")
- }
+ // international shipment logic
+ if serviceItem.ReService.Code == models.ReServiceCodeIOASIT || serviceItem.ReService.Code == models.ReServiceCodeIDASIT {
+ // address we need for the per_unit_cents is dependent on if it's origin/destination SIT
+ var addressID uuid.UUID
+ if serviceItem.ReService.Code == models.ReServiceCodeIOASIT {
+ addressID = *ppmShipment.PickupAddressID
+ } else {
+ addressID = *ppmShipment.DestinationAddressID
+ }
- // Need to declare if origin or destination for the serviceAreaLookup, otherwise we already have it
- serviceAreaPostalCode := ppmShipment.PickupAddress.PostalCode
- serviceAreaKey := models.ServiceItemParamNameServiceAreaOrigin
- if serviceItem.ReService.Code == models.ReServiceCodeDDASIT {
- serviceAreaPostalCode = ppmShipment.DestinationAddress.PostalCode
- serviceAreaKey = models.ServiceItemParamNameServiceAreaDest
- }
- serviceAreaLookup := serviceparamvaluelookups.ServiceAreaLookup{
- Address: models.Address{PostalCode: serviceAreaPostalCode},
- }
+ var moveDate time.Time
+ if ppmShipment.ActualMoveDate != nil {
+ moveDate = *ppmShipment.ActualMoveDate
+ } else {
+ moveDate = ppmShipment.ExpectedDepartureDate
+ }
- serviceArea, err := serviceAreaLookup.ParamValue(appCtx, contract.Code)
- if err != nil {
- return nil, nil, err
- }
+ reServiceID, _ := models.FetchReServiceByCode(appCtx.DB(), serviceItem.ReService.Code)
+ intlOtherPrice, _ := models.FetchReIntlOtherPrice(appCtx.DB(), addressID, reServiceID.ID, contract.ID, &moveDate)
- serviceAreaParam := services.PricingDisplayParam{
- Key: serviceAreaKey,
- Value: serviceArea,
- }
+ sitDaysParam := services.PricingDisplayParam{
+ Key: models.ServiceItemParamNameNumberDaysSIT,
+ Value: strconv.Itoa(additionalDaysInSIT),
+ }
- sitDaysParam := services.PricingDisplayParam{
- Key: models.ServiceItemParamNameNumberDaysSIT,
- Value: strconv.Itoa(additionalDaysInSIT),
- }
+ additionalDayPricer, ok := pricer.(services.IntlOriginAdditionalDaySITPricer)
+ if !ok {
+ return nil, nil, errors.New("ppm estimate pricer for SIT service item does not implement the first day pricer interface")
+ }
- // Since this function may be ran before closeout, we need to account for if there's no actual move date yet.
- if ppmShipment.ActualMoveDate != nil {
- price, pricingParams, err := additionalDaysPricer.Price(appCtx, contract.Code, *ppmShipment.ActualMoveDate, *ppmShipment.SITEstimatedWeight, serviceArea, additionalDaysInSIT, true)
+ price, pricingParams, err := additionalDayPricer.Price(appCtx, contract.Code, moveDate, additionalDaysInSIT, *ppmShipment.SITEstimatedWeight, intlOtherPrice.PerUnitCents.Int())
if err != nil {
return nil, nil, err
}
- pricingParams = append(pricingParams, serviceAreaParam, sitDaysParam)
+ pricingParams = append(pricingParams, sitDaysParam)
appCtx.Logger().Debug(fmt.Sprintf("Pricing params for additional day SIT %+v", pricingParams), zap.String("shipmentId", ppmShipment.ShipmentID.String()))
return &price, pricingParams, nil
- }
- price, pricingParams, err := additionalDaysPricer.Price(appCtx, contract.Code, ppmShipment.ExpectedDepartureDate, *ppmShipment.SITEstimatedWeight, serviceArea, additionalDaysInSIT, true)
- if err != nil {
- return nil, nil, err
- }
+ } else {
+ // domestic PPMs
+ additionalDaysPricer, ok := pricer.(services.DomesticAdditionalDaysSITPricer)
+ if !ok {
+ return nil, nil, errors.New("ppm estimate pricer for SIT service item does not implement the additional days pricer interface")
+ }
- pricingParams = append(pricingParams, serviceAreaParam, sitDaysParam)
+ // Need to declare if origin or destination for the serviceAreaLookup, otherwise we already have it
+ serviceAreaPostalCode := ppmShipment.PickupAddress.PostalCode
+ serviceAreaKey := models.ServiceItemParamNameServiceAreaOrigin
+ if serviceItem.ReService.Code == models.ReServiceCodeDDASIT {
+ serviceAreaPostalCode = ppmShipment.DestinationAddress.PostalCode
+ serviceAreaKey = models.ServiceItemParamNameServiceAreaDest
+ }
+ serviceAreaLookup := serviceparamvaluelookups.ServiceAreaLookup{
+ Address: models.Address{PostalCode: serviceAreaPostalCode},
+ }
- appCtx.Logger().Debug(fmt.Sprintf("Pricing params for additional day SIT %+v", pricingParams), zap.String("shipmentId", ppmShipment.ShipmentID.String()))
+ serviceArea, err := serviceAreaLookup.ParamValue(appCtx, contract.Code)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ serviceAreaParam := services.PricingDisplayParam{
+ Key: serviceAreaKey,
+ Value: serviceArea,
+ }
+
+ sitDaysParam := services.PricingDisplayParam{
+ Key: models.ServiceItemParamNameNumberDaysSIT,
+ Value: strconv.Itoa(additionalDaysInSIT),
+ }
+
+ // Since this function may be ran before closeout, we need to account for if there's no actual move date yet.
+ if ppmShipment.ActualMoveDate != nil {
+ price, pricingParams, err := additionalDaysPricer.Price(appCtx, contract.Code, *ppmShipment.ActualMoveDate, *ppmShipment.SITEstimatedWeight, serviceArea, additionalDaysInSIT, true)
+ if err != nil {
+ return nil, nil, err
+ }
- return &price, pricingParams, nil
+ pricingParams = append(pricingParams, serviceAreaParam, sitDaysParam)
+
+ appCtx.Logger().Debug(fmt.Sprintf("Pricing params for additional day SIT %+v", pricingParams), zap.String("shipmentId", ppmShipment.ShipmentID.String()))
+
+ return &price, pricingParams, nil
+ }
+ price, pricingParams, err := additionalDaysPricer.Price(appCtx, contract.Code, ppmShipment.ExpectedDepartureDate, *ppmShipment.SITEstimatedWeight, serviceArea, additionalDaysInSIT, true)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ pricingParams = append(pricingParams, serviceAreaParam, sitDaysParam)
+
+ appCtx.Logger().Debug(fmt.Sprintf("Pricing params for additional day SIT %+v", pricingParams), zap.String("shipmentId", ppmShipment.ShipmentID.String()))
+
+ return &price, pricingParams, nil
+ }
}
// 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) {
+ ppmShipment.Shipment.PPMShipment = &ppmShipment
+ ppmShipment.Shipment.ShipmentType = models.MTOShipmentTypePPM
ppmShipment.Shipment.ActualPickupDate = &ppmShipment.ExpectedDepartureDate
ppmShipment.Shipment.RequestedPickupDate = &ppmShipment.ExpectedDepartureDate
+ ppmShipment.Shipment.PickupAddress = ppmShipment.PickupAddress
ppmShipment.Shipment.PickupAddress = &models.Address{PostalCode: ppmShipment.PickupAddress.PostalCode}
+ ppmShipment.Shipment.DestinationAddress = ppmShipment.DestinationAddress
ppmShipment.Shipment.DestinationAddress = &models.Address{PostalCode: ppmShipment.DestinationAddress.PostalCode}
ppmShipment.Shipment.PrimeActualWeight = ppmShipment.EstimatedWeight
@@ -986,9 +1275,13 @@ func MapPPMShipmentMaxIncentiveFields(appCtx appcontext.AppContext, ppmShipment
// expect to find them on the MTOShipment model. This is only in-memory and shouldn't get saved to the database.
func MapPPMShipmentFinalFields(ppmShipment models.PPMShipment, totalWeight unit.Pound) models.MTOShipment {
+ ppmShipment.Shipment.PPMShipment = &ppmShipment
+ ppmShipment.Shipment.ShipmentType = models.MTOShipmentTypePPM
ppmShipment.Shipment.ActualPickupDate = ppmShipment.ActualMoveDate
ppmShipment.Shipment.RequestedPickupDate = ppmShipment.ActualMoveDate
+ ppmShipment.Shipment.PickupAddress = ppmShipment.PickupAddress
ppmShipment.Shipment.PickupAddress = &models.Address{PostalCode: *ppmShipment.ActualPickupPostalCode}
+ ppmShipment.Shipment.DestinationAddress = ppmShipment.DestinationAddress
ppmShipment.Shipment.DestinationAddress = &models.Address{PostalCode: *ppmShipment.ActualDestinationPostalCode}
ppmShipment.Shipment.PrimeActualWeight = &totalWeight
@@ -997,19 +1290,35 @@ func MapPPMShipmentFinalFields(ppmShipment models.PPMShipment, totalWeight unit.
// baseServiceItems returns a list of the MTOServiceItems that makeup the price of the estimated incentive. These
// are the same non-accesorial service items that get auto-created and approved when the TOO approves an HHG shipment.
-func BaseServiceItems(mtoShipmentID uuid.UUID) []models.MTOServiceItem {
- return []models.MTOServiceItem{
- {ReService: models.ReService{Code: models.ReServiceCodeDLH}, MTOShipmentID: &mtoShipmentID},
- {ReService: models.ReService{Code: models.ReServiceCodeFSC}, MTOShipmentID: &mtoShipmentID},
- {ReService: models.ReService{Code: models.ReServiceCodeDOP}, MTOShipmentID: &mtoShipmentID},
- {ReService: models.ReService{Code: models.ReServiceCodeDDP}, MTOShipmentID: &mtoShipmentID},
- {ReService: models.ReService{Code: models.ReServiceCodeDPK}, MTOShipmentID: &mtoShipmentID},
- {ReService: models.ReService{Code: models.ReServiceCodeDUPK}, MTOShipmentID: &mtoShipmentID},
+func BaseServiceItems(ppmShipment models.PPMShipment) []models.MTOServiceItem {
+ mtoShipmentID := ppmShipment.ShipmentID
+ isInternationalShipment := ppmShipment.Shipment.MarketCode == models.MarketCodeInternational
+
+ if isInternationalShipment {
+ return []models.MTOServiceItem{
+ {ReService: models.ReService{Code: models.ReServiceCodeFSC}, MTOShipmentID: &mtoShipmentID},
+ {ReService: models.ReService{Code: models.ReServiceCodeIHPK}, MTOShipmentID: &mtoShipmentID},
+ {ReService: models.ReService{Code: models.ReServiceCodeIHUPK}, MTOShipmentID: &mtoShipmentID},
+ {ReService: models.ReService{Code: models.ReServiceCodeISLH}, MTOShipmentID: &mtoShipmentID},
+ }
+ } else {
+ return []models.MTOServiceItem{
+ {ReService: models.ReService{Code: models.ReServiceCodeDLH}, MTOShipmentID: &mtoShipmentID},
+ {ReService: models.ReService{Code: models.ReServiceCodeFSC}, MTOShipmentID: &mtoShipmentID},
+ {ReService: models.ReService{Code: models.ReServiceCodeDOP}, MTOShipmentID: &mtoShipmentID},
+ {ReService: models.ReService{Code: models.ReServiceCodeDDP}, MTOShipmentID: &mtoShipmentID},
+ {ReService: models.ReService{Code: models.ReServiceCodeDPK}, MTOShipmentID: &mtoShipmentID},
+ {ReService: models.ReService{Code: models.ReServiceCodeDUPK}, MTOShipmentID: &mtoShipmentID},
+ }
}
}
-func StorageServiceItems(mtoShipmentID uuid.UUID, locationType models.SITLocationType, additionalDaysInSIT int) []models.MTOServiceItem {
- if locationType == models.SITLocationTypeOrigin {
+func StorageServiceItems(ppmShipment models.PPMShipment, locationType models.SITLocationType, additionalDaysInSIT int) []models.MTOServiceItem {
+ mtoShipmentID := ppmShipment.ShipmentID
+ isInternationalShipment := ppmShipment.Shipment.MarketCode == models.MarketCodeInternational
+
+ // domestic shipments
+ if locationType == models.SITLocationTypeOrigin && !isInternationalShipment {
if additionalDaysInSIT > 0 {
return []models.MTOServiceItem{
{ReService: models.ReService{Code: models.ReServiceCodeDOFSIT}, MTOShipmentID: &mtoShipmentID},
@@ -1020,15 +1329,41 @@ func StorageServiceItems(mtoShipmentID uuid.UUID, locationType models.SITLocatio
{ReService: models.ReService{Code: models.ReServiceCodeDOFSIT}, MTOShipmentID: &mtoShipmentID}}
}
- if additionalDaysInSIT > 0 {
+ if locationType == models.SITLocationTypeDestination && !isInternationalShipment {
+ if additionalDaysInSIT > 0 {
+ return []models.MTOServiceItem{
+ {ReService: models.ReService{Code: models.ReServiceCodeDDFSIT}, MTOShipmentID: &mtoShipmentID},
+ {ReService: models.ReService{Code: models.ReServiceCodeDDASIT}, MTOShipmentID: &mtoShipmentID},
+ }
+ }
+ return []models.MTOServiceItem{
+ {ReService: models.ReService{Code: models.ReServiceCodeDDFSIT}, MTOShipmentID: &mtoShipmentID}}
+ }
+
+ // international shipments
+ if locationType == models.SITLocationTypeOrigin && isInternationalShipment {
+ if additionalDaysInSIT > 0 {
+ return []models.MTOServiceItem{
+ {ReService: models.ReService{Code: models.ReServiceCodeIOFSIT}, MTOShipmentID: &mtoShipmentID},
+ {ReService: models.ReService{Code: models.ReServiceCodeIOASIT}, MTOShipmentID: &mtoShipmentID},
+ }
+ }
return []models.MTOServiceItem{
- {ReService: models.ReService{Code: models.ReServiceCodeDDFSIT}, MTOShipmentID: &mtoShipmentID},
- {ReService: models.ReService{Code: models.ReServiceCodeDDASIT}, MTOShipmentID: &mtoShipmentID},
+ {ReService: models.ReService{Code: models.ReServiceCodeIOFSIT}, MTOShipmentID: &mtoShipmentID}}
+ }
+
+ if locationType == models.SITLocationTypeDestination && isInternationalShipment {
+ if additionalDaysInSIT > 0 {
+ return []models.MTOServiceItem{
+ {ReService: models.ReService{Code: models.ReServiceCodeIDFSIT}, MTOShipmentID: &mtoShipmentID},
+ {ReService: models.ReService{Code: models.ReServiceCodeIDASIT}, MTOShipmentID: &mtoShipmentID},
+ }
}
+ return []models.MTOServiceItem{
+ {ReService: models.ReService{Code: models.ReServiceCodeIDFSIT}, MTOShipmentID: &mtoShipmentID}}
}
- return []models.MTOServiceItem{
- {ReService: models.ReService{Code: models.ReServiceCodeDDFSIT}, MTOShipmentID: &mtoShipmentID}}
+ return nil
}
// paramsForServiceCode filters the list of all service params for service items, to only those matching the service
diff --git a/pkg/services/ppmshipment/ppm_estimator_test.go b/pkg/services/ppmshipment/ppm_estimator_test.go
index d4ca213d2ac..f384223e3ba 100644
--- a/pkg/services/ppmshipment/ppm_estimator_test.go
+++ b/pkg/services/ppmshipment/ppm_estimator_test.go
@@ -696,6 +696,7 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() {
},
},
}, nil)
+ setupPricerData()
newPPM := oldPPMShipment
newPPM.HasProGear = models.BoolPointer(false)
@@ -1559,6 +1560,7 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() {
})
suite.Run("Final Incentive - does not change when required fields are the same", func() {
+ setupPricerData()
oldPPMShipment := factory.BuildPPMShipment(suite.DB(), []factory.Customization{
{
Model: models.PPMShipment{
@@ -1607,6 +1609,7 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() {
})
suite.Run("Final Incentive - set to nil when missing info", func() {
+ setupPricerData()
oldPPMShipment := factory.BuildPPMShipment(suite.DB(), []factory.Customization{
{
Model: models.PPMShipment{
@@ -1698,7 +1701,7 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() {
},
},
{
- Model: &models.Address{
+ Model: models.Address{
StreetAddress1: "987 Other Avenue",
StreetAddress2: models.StringPointer("P.O. Box 1234"),
StreetAddress3: models.StringPointer("c/o Another Person"),
@@ -1710,7 +1713,7 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() {
Type: &factory.Addresses.PickupAddress,
},
{
- Model: &models.Address{
+ Model: models.Address{
StreetAddress1: "987 Other Avenue",
StreetAddress2: models.StringPointer("P.O. Box 12345"),
StreetAddress3: models.StringPointer("c/o Another Person"),
@@ -1760,30 +1763,6 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() {
SITEstimatedDepartureDate: &entryDate,
},
},
- {
- Model: models.Address{
- StreetAddress1: "987 Other Avenue",
- StreetAddress2: models.StringPointer("P.O. Box 1234"),
- StreetAddress3: models.StringPointer("c/o Another Person"),
- City: "Des Moines",
- State: "IA",
- PostalCode: "50309",
- County: models.StringPointer("POLK"),
- },
- Type: &factory.Addresses.PickupAddress,
- },
- {
- Model: models.Address{
- StreetAddress1: "987 Other Avenue",
- StreetAddress2: models.StringPointer("P.O. Box 12345"),
- StreetAddress3: models.StringPointer("c/o Another Person"),
- City: "Fort Eisenhower",
- State: "GA",
- PostalCode: "50309",
- County: models.StringPointer("COLUMBIA"),
- },
- Type: &factory.Addresses.DeliveryAddress,
- },
{
Model: mtoShipment,
LinkOnly: true,
@@ -2049,3 +2028,623 @@ func (suite *PPMShipmentSuite) TestPPMEstimator() {
})
})
}
+
+func (suite *PPMShipmentSuite) TestInternationalPPMEstimator() {
+ planner := &mocks.Planner{}
+ paymentRequestHelper := &prhelpermocks.Helper{}
+ ppmEstimator := NewEstimatePPM(planner, paymentRequestHelper)
+
+ setupPricerData := func() {
+ contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{})
+ 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{
+ Contract: contract,
+ ContractID: contract.ID,
+ StartDate: startDate,
+ EndDate: endDate,
+ Escalation: 1.0,
+ EscalationCompounded: 1.0,
+ },
+ })
+ }
+
+ suite.Run("Estimated Incentive", func() {
+ suite.Run("Estimated Incentive - Success using estimated weight and not db authorized weight for CONUS -> OCONUS", func() {
+ ppm := factory.BuildPPMShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.MTOShipment{
+ MarketCode: models.MarketCodeInternational,
+ },
+ },
+ {
+ Model: models.Address{
+ StreetAddress1: "Tester Address",
+ City: "Tulsa",
+ State: "OK",
+ PostalCode: "74133",
+ },
+ Type: &factory.Addresses.PickupAddress,
+ },
+ {
+ Model: models.Address{
+ StreetAddress1: "JBER",
+ City: "JBER",
+ State: "AK",
+ PostalCode: "99505",
+ IsOconus: models.BoolPointer(true),
+ },
+ Type: &factory.Addresses.DeliveryAddress,
+ },
+ }, nil)
+
+ setupPricerData()
+
+ estimatedWeight := unit.Pound(5000)
+ newPPM := ppm
+ newPPM.EstimatedWeight = &estimatedWeight
+
+ planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"),
+ "74133", "98421", true).Return(3000, nil)
+
+ ppmEstimate, _, err := ppmEstimator.EstimateIncentiveWithDefaultChecks(suite.AppContextForTest(), ppm, &newPPM)
+ suite.NilOrNoVerrs(err)
+ suite.NotNil(ppmEstimate)
+
+ // it should've called from the pickup -> port and NOT pickup -> dest
+ planner.AssertCalled(suite.T(), "ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"),
+ "74133", "98421", true)
+ suite.Equal(unit.Cents(459178), *ppmEstimate)
+ })
+
+ suite.Run("Estimated Incentive - Success using estimated weight and not db authorized weight for OCONUS -> CONUS", func() {
+ ppm := factory.BuildPPMShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.MTOShipment{
+ MarketCode: models.MarketCodeInternational,
+ },
+ },
+ {
+ Model: models.Address{
+ StreetAddress1: "Tester Address",
+ City: "Tulsa",
+ State: "OK",
+ PostalCode: "74133",
+ },
+ Type: &factory.Addresses.DeliveryAddress,
+ },
+ {
+ Model: models.Address{
+ StreetAddress1: "JBER",
+ City: "JBER",
+ State: "AK",
+ PostalCode: "99505",
+ IsOconus: models.BoolPointer(true),
+ },
+ Type: &factory.Addresses.PickupAddress,
+ },
+ }, nil)
+
+ setupPricerData()
+
+ estimatedWeight := unit.Pound(5000)
+ newPPM := ppm
+ newPPM.EstimatedWeight = &estimatedWeight
+
+ planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"),
+ "98421", "74133", true).Return(3000, nil)
+
+ ppmEstimate, _, err := ppmEstimator.EstimateIncentiveWithDefaultChecks(suite.AppContextForTest(), ppm, &newPPM)
+ suite.NilOrNoVerrs(err)
+ suite.NotNil(ppmEstimate)
+
+ // it should've called from the pickup -> port and NOT pickup -> dest
+ planner.AssertCalled(suite.T(), "ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"),
+ "98421", "74133", true)
+ suite.Equal(unit.Cents(423178), *ppmEstimate)
+ })
+ })
+
+ suite.Run("Max Incentive", func() {
+ suite.Run("Max Incentive - Success using db authorized weight and not estimated for CONUS -> OCONUS", func() {
+ oconusAddress := factory.BuildAddress(suite.DB(), []factory.Customization{
+ {
+ Model: models.Address{
+ StreetAddress1: "JBER",
+ City: "JBER",
+ State: "AK",
+ PostalCode: "99505",
+ IsOconus: models.BoolPointer(true),
+ },
+ },
+ }, nil)
+ destDutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{
+ {
+ Model: models.DutyLocation{
+ Name: "Test OCONUS Duty Location",
+ AddressID: oconusAddress.ID,
+ },
+ },
+ }, nil)
+ order := factory.BuildOrder(suite.DB(), []factory.Customization{
+ {
+ Model: models.Order{
+ NewDutyLocationID: destDutyLocation.ID,
+ },
+ },
+ }, nil)
+ // when the PPM shipment is in draft, we use the estimated weight and not the db authorized weight
+ ppm := factory.BuildPPMShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.Move{
+ OrdersID: order.ID,
+ },
+ },
+ {
+ Model: models.MTOShipment{
+ MarketCode: models.MarketCodeInternational,
+ },
+ },
+ {
+ Model: models.Address{
+ StreetAddress1: "Tester Address",
+ City: "Tulsa",
+ State: "OK",
+ PostalCode: "74133",
+ },
+ Type: &factory.Addresses.PickupAddress,
+ },
+ {
+ Model: models.Address{
+ StreetAddress1: "JBER",
+ City: "JBER",
+ State: "AK",
+ PostalCode: "99505",
+ IsOconus: models.BoolPointer(true),
+ },
+ Type: &factory.Addresses.DeliveryAddress,
+ },
+ }, nil)
+
+ setupPricerData()
+
+ estimatedWeight := unit.Pound(5000)
+ newPPM := ppm
+ newPPM.EstimatedWeight = &estimatedWeight
+
+ // DTOD will be called to get the distance between the origin duty location & the Tacoma Port ZIP
+ planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"),
+ "50309", "98421", true).Return(3000, nil)
+
+ ppmMaxIncentive, err := ppmEstimator.MaxIncentive(suite.AppContextForTest(), ppm, &newPPM)
+ suite.NilOrNoVerrs(err)
+ suite.NotNil(ppmMaxIncentive)
+
+ // it should've called from the pickup -> port and NOT pickup -> dest
+ planner.AssertCalled(suite.T(), "ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"),
+ "50309", "98421", true)
+ suite.Equal(unit.Cents(656532), *ppmMaxIncentive)
+ })
+
+ suite.Run("Max Incentive - Success using db authorized weight and not estimated for OCONUS -> CONUS", func() {
+ oconusAddress := factory.BuildAddress(suite.DB(), []factory.Customization{
+ {
+ Model: models.Address{
+ StreetAddress1: "JBER",
+ City: "JBER",
+ State: "AK",
+ PostalCode: "99505",
+ IsOconus: models.BoolPointer(true),
+ },
+ },
+ }, nil)
+ pickupDutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{
+ {
+ Model: models.DutyLocation{
+ Name: "Test OCONUS Duty Location",
+ AddressID: oconusAddress.ID,
+ },
+ },
+ }, nil)
+ order := factory.BuildOrder(suite.DB(), []factory.Customization{
+ {
+ Model: models.Order{
+ OriginDutyLocationID: &pickupDutyLocation.ID,
+ },
+ },
+ }, nil)
+ // when the PPM shipment is in draft, we use the estimated weight and not the db authorized weight
+ ppm := factory.BuildPPMShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.Move{
+ OrdersID: order.ID,
+ },
+ },
+ {
+ Model: models.MTOShipment{
+ MarketCode: models.MarketCodeInternational,
+ },
+ },
+ {
+ Model: models.Address{
+ StreetAddress1: "Tester Address",
+ City: "Tulsa",
+ State: "OK",
+ PostalCode: "74133",
+ },
+ Type: &factory.Addresses.PickupAddress,
+ },
+ {
+ Model: models.Address{
+ StreetAddress1: "JBER",
+ City: "JBER",
+ State: "AK",
+ PostalCode: "99505",
+ IsOconus: models.BoolPointer(true),
+ },
+ Type: &factory.Addresses.DeliveryAddress,
+ },
+ }, nil)
+
+ setupPricerData()
+
+ estimatedWeight := unit.Pound(5000)
+ newPPM := ppm
+ newPPM.EstimatedWeight = &estimatedWeight
+
+ // DTOD will be called to get the distance between the origin duty location & the Tacoma Port ZIP
+ planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"),
+ "98421", "30813", true).Return(3000, nil)
+
+ ppmMaxIncentive, err := ppmEstimator.MaxIncentive(suite.AppContextForTest(), ppm, &newPPM)
+ suite.NilOrNoVerrs(err)
+ suite.NotNil(ppmMaxIncentive)
+
+ // it should've called from the pickup -> port and NOT pickup -> dest
+ planner.AssertCalled(suite.T(), "ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"),
+ "98421", "30813", true)
+ suite.Equal(unit.Cents(676692), *ppmMaxIncentive)
+ })
+ })
+
+ suite.Run("Final Incentive", func() {
+ suite.Run("Final Incentive - Success using estimated weight for CONUS -> OCONUS", func() {
+ updatedMoveDate := time.Date(2020, time.March, 15, 0, 0, 0, 0, time.UTC)
+ ppm := factory.BuildPPMShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.PPMShipment{
+ ActualMoveDate: models.TimePointer(updatedMoveDate),
+ Status: models.PPMShipmentStatusWaitingOnCustomer,
+ EstimatedWeight: models.PoundPointer(4000),
+ },
+ },
+ {
+ Model: models.MTOShipment{
+ MarketCode: models.MarketCodeInternational,
+ },
+ },
+ {
+ Model: models.Address{
+ StreetAddress1: "Tester Address",
+ City: "Tulsa",
+ State: "OK",
+ PostalCode: "74133",
+ },
+ Type: &factory.Addresses.PickupAddress,
+ },
+ {
+ Model: models.Address{
+ StreetAddress1: "JBER",
+ City: "JBER",
+ State: "AK",
+ PostalCode: "99505",
+ IsOconus: models.BoolPointer(true),
+ },
+ Type: &factory.Addresses.DeliveryAddress,
+ },
+ }, nil)
+
+ newPPM := ppm
+ newFullWeight := unit.Pound(8000)
+ newEmptyWeight := unit.Pound(3000)
+ newPPM.WeightTickets = models.WeightTickets{
+ factory.BuildWeightTicket(suite.DB(), []factory.Customization{
+ {
+ Model: models.WeightTicket{
+ FullWeight: &newFullWeight,
+ EmptyWeight: &newEmptyWeight,
+ },
+ },
+ }, nil),
+ }
+
+ setupPricerData()
+
+ planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"),
+ "74133", "98421", true).Return(3000, nil)
+
+ ppmFinalIncentive, err := ppmEstimator.FinalIncentiveWithDefaultChecks(suite.AppContextForTest(), ppm, &newPPM)
+ suite.NilOrNoVerrs(err)
+ suite.NotNil(ppmFinalIncentive)
+
+ // it should've called from the pickup -> port and NOT pickup -> dest
+ planner.AssertCalled(suite.T(), "ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"),
+ "74133", "98421", true)
+ suite.Equal(unit.Cents(459178), *ppmFinalIncentive)
+ })
+
+ suite.Run("Final Incentive - Success using estimated weight for OCONUS -> CONUS", func() {
+ updatedMoveDate := time.Date(2020, time.March, 15, 0, 0, 0, 0, time.UTC)
+ ppm := factory.BuildPPMShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.PPMShipment{
+ ActualMoveDate: models.TimePointer(updatedMoveDate),
+ Status: models.PPMShipmentStatusWaitingOnCustomer,
+ EstimatedWeight: models.PoundPointer(4000),
+ },
+ },
+ {
+ Model: models.MTOShipment{
+ MarketCode: models.MarketCodeInternational,
+ },
+ },
+ {
+ Model: models.Address{
+ StreetAddress1: "Tester Address",
+ City: "Tulsa",
+ State: "OK",
+ PostalCode: "74133",
+ },
+ Type: &factory.Addresses.DeliveryAddress,
+ },
+ {
+ Model: models.Address{
+ StreetAddress1: "JBER",
+ City: "JBER",
+ State: "AK",
+ PostalCode: "99505",
+ IsOconus: models.BoolPointer(true),
+ },
+ Type: &factory.Addresses.PickupAddress,
+ },
+ }, nil)
+
+ newPPM := ppm
+ newFullWeight := unit.Pound(8000)
+ newEmptyWeight := unit.Pound(3000)
+ newPPM.WeightTickets = models.WeightTickets{
+ factory.BuildWeightTicket(suite.DB(), []factory.Customization{
+ {
+ Model: models.WeightTicket{
+ FullWeight: &newFullWeight,
+ EmptyWeight: &newEmptyWeight,
+ },
+ },
+ }, nil),
+ }
+
+ setupPricerData()
+
+ planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"),
+ "98421", "74133", true).Return(3000, nil)
+
+ ppmFinalIncentive, err := ppmEstimator.FinalIncentiveWithDefaultChecks(suite.AppContextForTest(), ppm, &newPPM)
+ suite.NilOrNoVerrs(err)
+ suite.NotNil(ppmFinalIncentive)
+
+ // it should've called from the pickup -> port and NOT pickup -> dest
+ planner.AssertCalled(suite.T(), "ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"),
+ "98421", "74133", true)
+ suite.Equal(unit.Cents(423178), *ppmFinalIncentive)
+ })
+ })
+
+ suite.Run("SIT Costs for OCONUS PPMs", func() {
+ suite.Run("CalculateSITCost - Success using estimated weight for CONUS -> OCONUS", func() {
+ originLocation := models.SITLocationTypeOrigin
+ entryDate := time.Date(2020, time.March, 15, 0, 0, 0, 0, time.UTC)
+ ppm := factory.BuildPPMShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.PPMShipment{
+ EstimatedWeight: models.PoundPointer(4000),
+ SITExpected: models.BoolPointer(true),
+ SITLocation: &originLocation,
+ SITEstimatedWeight: models.PoundPointer(unit.Pound(2000)),
+ SITEstimatedEntryDate: &entryDate,
+ SITEstimatedDepartureDate: models.TimePointer(entryDate.Add(time.Hour * 24 * 30)),
+ },
+ },
+ {
+ Model: models.MTOShipment{
+ MarketCode: models.MarketCodeInternational,
+ },
+ },
+ {
+ Model: models.Address{
+ StreetAddress1: "Tester Address",
+ City: "Tulsa",
+ State: "OK",
+ PostalCode: "74133",
+ },
+ Type: &factory.Addresses.PickupAddress,
+ },
+ {
+ Model: models.Address{
+ StreetAddress1: "JBER",
+ City: "JBER",
+ State: "AK",
+ PostalCode: "99505",
+ IsOconus: models.BoolPointer(true),
+ },
+ Type: &factory.Addresses.DeliveryAddress,
+ },
+ }, nil)
+
+ newPPM := ppm
+ newEstimatedWeight := models.PoundPointer(5500)
+ newPPM.SITEstimatedWeight = newEstimatedWeight
+ setupPricerData()
+
+ _, estimatedSITCost, err := ppmEstimator.EstimateIncentiveWithDefaultChecks(suite.AppContextForTest(), ppm, &newPPM)
+ suite.NilOrNoVerrs(err)
+ suite.NotNil(estimatedSITCost)
+ suite.Equal(unit.Cents(24360), *estimatedSITCost)
+ })
+
+ suite.Run("CalculateSITCost - Success using estimated weight for CONUS -> OCONUS", func() {
+ originLocation := models.SITLocationTypeDestination
+ entryDate := time.Date(2020, time.March, 15, 0, 0, 0, 0, time.UTC)
+ ppm := factory.BuildPPMShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.PPMShipment{
+ EstimatedWeight: models.PoundPointer(4000),
+ SITExpected: models.BoolPointer(true),
+ SITLocation: &originLocation,
+ SITEstimatedWeight: models.PoundPointer(unit.Pound(2000)),
+ SITEstimatedEntryDate: &entryDate,
+ SITEstimatedDepartureDate: models.TimePointer(entryDate.Add(time.Hour * 24 * 30)),
+ },
+ },
+ {
+ Model: models.MTOShipment{
+ MarketCode: models.MarketCodeInternational,
+ },
+ },
+ {
+ Model: models.Address{
+ StreetAddress1: "Tester Address",
+ City: "Tulsa",
+ State: "OK",
+ PostalCode: "74133",
+ },
+ Type: &factory.Addresses.PickupAddress,
+ },
+ {
+ Model: models.Address{
+ StreetAddress1: "JBER",
+ City: "JBER",
+ State: "AK",
+ PostalCode: "99505",
+ IsOconus: models.BoolPointer(true),
+ },
+ Type: &factory.Addresses.DeliveryAddress,
+ },
+ }, nil)
+
+ newPPM := ppm
+ newEstimatedWeight := models.PoundPointer(5500)
+ newPPM.SITEstimatedWeight = newEstimatedWeight
+ setupPricerData()
+
+ _, estimatedSITCost, err := ppmEstimator.EstimateIncentiveWithDefaultChecks(suite.AppContextForTest(), ppm, &newPPM)
+ suite.NilOrNoVerrs(err)
+ suite.NotNil(estimatedSITCost)
+ suite.Equal(unit.Cents(41080), *estimatedSITCost)
+ })
+
+ suite.Run("CalculatePPMSITEstimatedCost - Success for OCONUS PPM", func() {
+ originLocation := models.SITLocationTypeDestination
+ entryDate := time.Date(2020, time.March, 15, 0, 0, 0, 0, time.UTC)
+ ppm := factory.BuildPPMShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.PPMShipment{
+ EstimatedWeight: models.PoundPointer(4000),
+ SITExpected: models.BoolPointer(true),
+ SITLocation: &originLocation,
+ SITEstimatedWeight: models.PoundPointer(unit.Pound(2000)),
+ SITEstimatedEntryDate: &entryDate,
+ SITEstimatedDepartureDate: models.TimePointer(entryDate.Add(time.Hour * 24 * 30)),
+ },
+ },
+ {
+ Model: models.MTOShipment{
+ MarketCode: models.MarketCodeInternational,
+ },
+ },
+ {
+ Model: models.Address{
+ StreetAddress1: "Tester Address",
+ City: "Tulsa",
+ State: "OK",
+ PostalCode: "74133",
+ },
+ Type: &factory.Addresses.PickupAddress,
+ },
+ {
+ Model: models.Address{
+ StreetAddress1: "JBER",
+ City: "JBER",
+ State: "AK",
+ PostalCode: "99505",
+ IsOconus: models.BoolPointer(true),
+ },
+ Type: &factory.Addresses.DeliveryAddress,
+ },
+ }, nil)
+
+ newPPM := ppm
+ newEstimatedWeight := models.PoundPointer(5500)
+ newPPM.SITEstimatedWeight = newEstimatedWeight
+ setupPricerData()
+
+ estimatedSITCost, err := ppmEstimator.CalculatePPMSITEstimatedCost(suite.AppContextForTest(), &ppm)
+ suite.NilOrNoVerrs(err)
+ suite.NotNil(estimatedSITCost)
+ suite.Equal(unit.Cents(20540), *estimatedSITCost)
+ })
+
+ suite.Run("CalculatePPMSITEstimatedCostBreakdown - Success for OCONUS PPM", func() {
+ originLocation := models.SITLocationTypeDestination
+ entryDate := time.Date(2020, time.March, 15, 0, 0, 0, 0, time.UTC)
+ ppm := factory.BuildPPMShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.PPMShipment{
+ EstimatedWeight: models.PoundPointer(4000),
+ SITExpected: models.BoolPointer(true),
+ SITLocation: &originLocation,
+ SITEstimatedWeight: models.PoundPointer(unit.Pound(2000)),
+ SITEstimatedEntryDate: &entryDate,
+ SITEstimatedDepartureDate: models.TimePointer(entryDate.Add(time.Hour * 24 * 30)),
+ },
+ },
+ {
+ Model: models.MTOShipment{
+ MarketCode: models.MarketCodeInternational,
+ },
+ },
+ {
+ Model: models.Address{
+ StreetAddress1: "Tester Address",
+ City: "Tulsa",
+ State: "OK",
+ PostalCode: "74133",
+ },
+ Type: &factory.Addresses.PickupAddress,
+ },
+ {
+ Model: models.Address{
+ StreetAddress1: "JBER",
+ City: "JBER",
+ State: "AK",
+ PostalCode: "99505",
+ IsOconus: models.BoolPointer(true),
+ },
+ Type: &factory.Addresses.DeliveryAddress,
+ },
+ }, nil)
+
+ newPPM := ppm
+ newEstimatedWeight := models.PoundPointer(5500)
+ newPPM.SITEstimatedWeight = newEstimatedWeight
+ setupPricerData()
+
+ sitCosts, err := ppmEstimator.CalculatePPMSITEstimatedCostBreakdown(suite.AppContextForTest(), &ppm)
+ suite.NilOrNoVerrs(err)
+ suite.NotNil(sitCosts)
+ suite.Equal(unit.Cents(20540), *sitCosts.EstimatedSITCost)
+ suite.Equal(unit.Cents(12140), *sitCosts.PriceFirstDaySIT)
+ suite.Equal(unit.Cents(8400), *sitCosts.PriceAdditionalDaySIT)
+ })
+ })
+}
diff --git a/pkg/services/ppmshipment/ppm_shipment_updater.go b/pkg/services/ppmshipment/ppm_shipment_updater.go
index f8cc99b8d30..c52936e9e9f 100644
--- a/pkg/services/ppmshipment/ppm_shipment_updater.go
+++ b/pkg/services/ppmshipment/ppm_shipment_updater.go
@@ -117,66 +117,6 @@ func (f *ppmShipmentUpdater) updatePPMShipment(appCtx appcontext.AppContext, ppm
}
transactionError := appCtx.NewTransaction(func(txnAppCtx appcontext.AppContext) error {
- // This potentially updates the MTOShipment.Distance field so include it in the transaction
- estimatedIncentive, estimatedSITCost, err := f.estimator.EstimateIncentiveWithDefaultChecks(appCtx, *oldPPMShipment, updatedPPMShipment)
- if err != nil {
- return err
- }
-
- updatedPPMShipment.EstimatedIncentive = estimatedIncentive
- updatedPPMShipment.SITEstimatedCost = estimatedSITCost
-
- // 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
- }
-
- if appCtx.Session() != nil {
- if appCtx.Session().IsOfficeUser() {
- edited := models.PPMAdvanceStatusEdited
- if oldPPMShipment.HasRequestedAdvance != nil && updatedPPMShipment.HasRequestedAdvance != nil {
- if !*oldPPMShipment.HasRequestedAdvance && *updatedPPMShipment.HasRequestedAdvance {
- updatedPPMShipment.AdvanceStatus = &edited
- } else if *oldPPMShipment.HasRequestedAdvance && !*updatedPPMShipment.HasRequestedAdvance {
- updatedPPMShipment.AdvanceStatus = &edited
- }
- }
- if oldPPMShipment.AdvanceAmountRequested != nil && updatedPPMShipment.AdvanceAmountRequested != nil {
- if *oldPPMShipment.AdvanceAmountRequested != *updatedPPMShipment.AdvanceAmountRequested {
- updatedPPMShipment.AdvanceStatus = &edited
- }
- }
- }
- if appCtx.Session().IsMilApp() {
- if isPrimeCounseled && updatedPPMShipment.HasRequestedAdvance != nil {
- received := models.PPMAdvanceStatusReceived
- notReceived := models.PPMAdvanceStatusNotReceived
-
- if updatedPPMShipment.HasReceivedAdvance != nil && *updatedPPMShipment.HasRequestedAdvance {
- if *updatedPPMShipment.HasReceivedAdvance {
- updatedPPMShipment.AdvanceStatus = &received
- }
- if !*updatedPPMShipment.HasReceivedAdvance {
- updatedPPMShipment.AdvanceStatus = ¬Received
- }
- }
- }
- }
- }
-
- finalIncentive, err := f.estimator.FinalIncentiveWithDefaultChecks(appCtx, *oldPPMShipment, updatedPPMShipment)
- if err != nil {
- return err
- }
- updatedPPMShipment.FinalIncentive = finalIncentive
-
if updatedPPMShipment.W2Address != nil {
var updatedAddress *models.Address
var createOrUpdateErr error
@@ -282,6 +222,69 @@ func (f *ppmShipmentUpdater) updatePPMShipment(appCtx appcontext.AppContext, ppm
updatedPPMShipment.TertiaryDestinationAddress = updatedAddress
}
+ // if the actual move date is being provided, we no longer need to calculate the estimate - it has already happened
+ if updatedPPMShipment.ActualMoveDate == nil {
+ estimatedIncentive, estimatedSITCost, err := f.estimator.EstimateIncentiveWithDefaultChecks(appCtx, *oldPPMShipment, updatedPPMShipment)
+ if err != nil {
+ return err
+ }
+ updatedPPMShipment.EstimatedIncentive = estimatedIncentive
+ updatedPPMShipment.SITEstimatedCost = estimatedSITCost
+ }
+
+ // 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
+ }
+
+ if appCtx.Session() != nil {
+ if appCtx.Session().IsOfficeUser() {
+ edited := models.PPMAdvanceStatusEdited
+ if oldPPMShipment.HasRequestedAdvance != nil && updatedPPMShipment.HasRequestedAdvance != nil {
+ if !*oldPPMShipment.HasRequestedAdvance && *updatedPPMShipment.HasRequestedAdvance {
+ updatedPPMShipment.AdvanceStatus = &edited
+ } else if *oldPPMShipment.HasRequestedAdvance && !*updatedPPMShipment.HasRequestedAdvance {
+ updatedPPMShipment.AdvanceStatus = &edited
+ }
+ }
+ if oldPPMShipment.AdvanceAmountRequested != nil && updatedPPMShipment.AdvanceAmountRequested != nil {
+ if *oldPPMShipment.AdvanceAmountRequested != *updatedPPMShipment.AdvanceAmountRequested {
+ updatedPPMShipment.AdvanceStatus = &edited
+ }
+ }
+ }
+ if appCtx.Session().IsMilApp() {
+ if isPrimeCounseled && updatedPPMShipment.HasRequestedAdvance != nil {
+ received := models.PPMAdvanceStatusReceived
+ notReceived := models.PPMAdvanceStatusNotReceived
+
+ if updatedPPMShipment.HasReceivedAdvance != nil && *updatedPPMShipment.HasRequestedAdvance {
+ if *updatedPPMShipment.HasReceivedAdvance {
+ updatedPPMShipment.AdvanceStatus = &received
+ }
+ if !*updatedPPMShipment.HasReceivedAdvance {
+ updatedPPMShipment.AdvanceStatus = ¬Received
+ }
+ }
+ }
+ }
+ }
+
+ if updatedPPMShipment.ActualMoveDate != nil {
+ finalIncentive, err := f.estimator.FinalIncentiveWithDefaultChecks(appCtx, *oldPPMShipment, updatedPPMShipment)
+ if err != nil {
+ return err
+ }
+ updatedPPMShipment.FinalIncentive = finalIncentive
+ }
+
verrs, err := appCtx.DB().ValidateAndUpdate(updatedPPMShipment)
if verrs != nil && verrs.HasAny() {
return apperror.NewInvalidInputError(updatedPPMShipment.ID, err, verrs, "Invalid input found while updating the PPMShipments.")
diff --git a/pkg/services/requested_office_users.go b/pkg/services/requested_office_users.go
index 574d5915c83..ad917128256 100644
--- a/pkg/services/requested_office_users.go
+++ b/pkg/services/requested_office_users.go
@@ -1,6 +1,7 @@
package services
import (
+ "github.com/gobuffalo/pop/v6"
"github.com/gobuffalo/validate/v3"
"github.com/gofrs/uuid"
@@ -13,7 +14,7 @@ import (
//
//go:generate mockery --name RequestedOfficeUserListFetcher
type RequestedOfficeUserListFetcher interface {
- FetchRequestedOfficeUsersList(appCtx appcontext.AppContext, filters []QueryFilter, associations QueryAssociations, pagination Pagination, ordering QueryOrder) (models.OfficeUsers, error)
+ FetchRequestedOfficeUsersList(appCtx appcontext.AppContext, filterFuncs []func(*pop.Query), pagination Pagination, ordering QueryOrder) (models.OfficeUsers, int, error)
FetchRequestedOfficeUsersCount(appCtx appcontext.AppContext, filters []QueryFilter) (int, error)
}
diff --git a/pkg/services/requested_office_users/requested_office_user_list_fetcher_test.go b/pkg/services/requested_office_users/requested_office_user_list_fetcher_test.go
index b3271f90a9c..9192bf73d6e 100644
--- a/pkg/services/requested_office_users/requested_office_user_list_fetcher_test.go
+++ b/pkg/services/requested_office_users/requested_office_user_list_fetcher_test.go
@@ -1,26 +1,17 @@
package adminuser
import (
- "errors"
- "reflect"
-
- "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/models/roles"
"github.com/transcom/mymove/pkg/services"
"github.com/transcom/mymove/pkg/services/pagination"
"github.com/transcom/mymove/pkg/services/query"
)
type testRequestedOfficeUsersListQueryBuilder struct {
- fakeFetchMany func(appCtx appcontext.AppContext, model interface{}) error
- fakeCount func(appCtx appcontext.AppContext, model interface{}) (int, error)
-}
-
-func (t *testRequestedOfficeUsersListQueryBuilder) FetchMany(appCtx appcontext.AppContext, model interface{}, _ []services.QueryFilter, _ services.QueryAssociations, _ services.Pagination, _ services.QueryOrder) error {
- m := t.fakeFetchMany(appCtx, model)
- return m
+ fakeCount func(appCtx appcontext.AppContext, model interface{}) (int, error)
}
func (t *testRequestedOfficeUsersListQueryBuilder) Count(appCtx appcontext.AppContext, model interface{}, _ []services.QueryFilter) (int, error) {
@@ -33,50 +24,119 @@ func defaultPagination() services.Pagination {
return pagination.NewPagination(&page, &perPage)
}
-func defaultAssociations() services.QueryAssociations {
- return query.NewQueryAssociations([]services.QueryAssociation{})
-}
-
func defaultOrdering() services.QueryOrder {
return query.NewQueryOrder(nil, nil)
}
func (suite *RequestedOfficeUsersServiceSuite) TestFetchRequestedOfficeUserList() {
suite.Run("if the users are successfully fetched, they should be returned", func() {
- id, err := uuid.NewV4()
+ requestedStatus := models.OfficeUserStatusREQUESTED
+ officeUser1 := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{
+ {
+ Model: models.OfficeUser{
+ Status: &requestedStatus,
+ },
+ },
+ }, []roles.RoleType{roles.RoleTypeTOO})
+ builder := &testRequestedOfficeUsersListQueryBuilder{}
+
+ fetcher := NewRequestedOfficeUsersListFetcher(builder)
+
+ requestedOfficeUsers, _, err := fetcher.FetchRequestedOfficeUsersList(suite.AppContextForTest(), nil, defaultPagination(), defaultOrdering())
+
suite.NoError(err)
- fakeFetchMany := func(_ appcontext.AppContext, model interface{}) error {
- value := reflect.ValueOf(model).Elem()
- requestedStatus := models.OfficeUserStatusREQUESTED
- value.Set(reflect.Append(value, reflect.ValueOf(models.OfficeUser{ID: id, Status: &requestedStatus})))
- return nil
- }
- builder := &testRequestedOfficeUsersListQueryBuilder{
- fakeFetchMany: fakeFetchMany,
- }
+ suite.Equal(officeUser1.ID, requestedOfficeUsers[0].ID)
+ })
+
+ suite.Run("if there are no requested office users, we don't receive any requested office users", func() {
+ builder := &testRequestedOfficeUsersListQueryBuilder{}
fetcher := NewRequestedOfficeUsersListFetcher(builder)
- requestedOfficeUsers, err := fetcher.FetchRequestedOfficeUsersList(suite.AppContextForTest(), nil, defaultAssociations(), defaultPagination(), defaultOrdering())
+ requestedOfficeUsers, _, err := fetcher.FetchRequestedOfficeUsersList(suite.AppContextForTest(), nil, defaultPagination(), defaultOrdering())
suite.NoError(err)
- suite.Equal(id, requestedOfficeUsers[0].ID)
+ suite.Equal(models.OfficeUsers(nil), requestedOfficeUsers)
})
- suite.Run("if there is an error, we get it with no requested office users", func() {
- fakeFetchMany := func(_ appcontext.AppContext, _ interface{}) error {
- return errors.New("Fetch error")
- }
- builder := &testRequestedOfficeUsersListQueryBuilder{
- fakeFetchMany: fakeFetchMany,
- }
+ suite.Run("should sort and order requested office users", func() {
+ requestedStatus := models.OfficeUserStatusREQUESTED
+ officeUser1 := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{
+ {
+ Model: models.OfficeUser{
+ FirstName: "Angelina",
+ LastName: "Jolie",
+ Email: "laraCroft@mail.mil",
+ Status: &requestedStatus,
+ },
+ },
+ {
+ Model: models.TransportationOffice{
+ Name: "PPPO Kirtland AFB - USAF",
+ },
+ },
+ }, []roles.RoleType{roles.RoleTypeTOO})
+ officeUser2 := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{
+ {
+ Model: models.OfficeUser{
+ FirstName: "Billy",
+ LastName: "Bob",
+ Email: "bigBob@mail.mil",
+ Status: &requestedStatus,
+ },
+ },
+ {
+ Model: models.TransportationOffice{
+ Name: "PPPO Fort Knox - USA",
+ },
+ },
+ }, []roles.RoleType{roles.RoleTypeTIO})
+ officeUser3 := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{
+ {
+ Model: models.OfficeUser{
+ FirstName: "Nick",
+ LastName: "Cage",
+ Email: "conAirKilluh@mail.mil",
+ Status: &requestedStatus,
+ },
+ },
+ {
+ Model: models.TransportationOffice{
+ Name: "PPPO Detroit Arsenal - USA",
+ },
+ },
+ }, []roles.RoleType{roles.RoleTypeServicesCounselor})
+
+ builder := &testRequestedOfficeUsersListQueryBuilder{}
fetcher := NewRequestedOfficeUsersListFetcher(builder)
- requestedOfficeUsers, err := fetcher.FetchRequestedOfficeUsersList(suite.AppContextForTest(), []services.QueryFilter{}, defaultAssociations(), defaultPagination(), defaultOrdering())
+ column := "transportation_office_id"
+ ordering := query.NewQueryOrder(&column, models.BoolPointer(true))
+
+ requestedOfficeUsers, _, err := fetcher.FetchRequestedOfficeUsersList(suite.AppContextForTest(), nil, defaultPagination(), ordering)
+
+ suite.NoError(err)
+ suite.Len(requestedOfficeUsers, 3)
+ suite.Equal(officeUser3.ID.String(), requestedOfficeUsers[0].ID.String())
+ suite.Equal(officeUser2.ID.String(), requestedOfficeUsers[1].ID.String())
+ suite.Equal(officeUser1.ID.String(), requestedOfficeUsers[2].ID.String())
+
+ ordering = query.NewQueryOrder(&column, models.BoolPointer(false))
+
+ requestedOfficeUsers, _, err = fetcher.FetchRequestedOfficeUsersList(suite.AppContextForTest(), nil, defaultPagination(), ordering)
+
+ suite.NoError(err)
+ suite.Len(requestedOfficeUsers, 3)
+ suite.Equal(officeUser1.ID.String(), requestedOfficeUsers[0].ID.String())
+ suite.Equal(officeUser2.ID.String(), requestedOfficeUsers[1].ID.String())
+ suite.Equal(officeUser3.ID.String(), requestedOfficeUsers[2].ID.String())
+
+ column = "unknown_column"
+
+ requestedOfficeUsers, _, err = fetcher.FetchRequestedOfficeUsersList(suite.AppContextForTest(), nil, defaultPagination(), ordering)
suite.Error(err)
- suite.Equal(err.Error(), "Fetch error")
- suite.Equal(models.OfficeUsers(nil), requestedOfficeUsers)
+ suite.Len(requestedOfficeUsers, 0)
})
}
diff --git a/pkg/services/requested_office_users/requested_office_users_list_fetcher.go b/pkg/services/requested_office_users/requested_office_users_list_fetcher.go
index 29004b8485e..3c883225b3b 100644
--- a/pkg/services/requested_office_users/requested_office_users_list_fetcher.go
+++ b/pkg/services/requested_office_users/requested_office_users_list_fetcher.go
@@ -1,13 +1,17 @@
package adminuser
import (
+ "fmt"
+ "sort"
+
+ "github.com/gobuffalo/pop/v6"
+
"github.com/transcom/mymove/pkg/appcontext"
"github.com/transcom/mymove/pkg/models"
"github.com/transcom/mymove/pkg/services"
)
type requestedOfficeUsersListQueryBuilder interface {
- FetchMany(appCtx appcontext.AppContext, model interface{}, filters []services.QueryFilter, associations services.QueryAssociations, pagination services.Pagination, ordering services.QueryOrder) error
Count(appCtx appcontext.AppContext, model interface{}, filters []services.QueryFilter) (int, error)
}
@@ -16,10 +20,57 @@ type requestedOfficeUserListFetcher struct {
}
// FetchAdminUserList uses the passed query builder to fetch a list of office users
-func (o *requestedOfficeUserListFetcher) FetchRequestedOfficeUsersList(appCtx appcontext.AppContext, filters []services.QueryFilter, associations services.QueryAssociations, pagination services.Pagination, ordering services.QueryOrder) (models.OfficeUsers, error) {
+func (o *requestedOfficeUserListFetcher) FetchRequestedOfficeUsersList(appCtx appcontext.AppContext, filterFuncs []func(*pop.Query), pagination services.Pagination, ordering services.QueryOrder) (models.OfficeUsers, int, error) {
+ var query *pop.Query
var requestedUsers models.OfficeUsers
- err := o.builder.FetchMany(appCtx, &requestedUsers, filters, associations, pagination, ordering)
- return requestedUsers, err
+
+ query = appCtx.DB().Q().EagerPreload(
+ "User.Roles",
+ "TransportationOffice").
+ 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").
+ Join("transportation_offices", "office_users.transportation_office_id = transportation_offices.id")
+
+ for _, filterFunc := range filterFuncs {
+ filterFunc(query)
+ }
+
+ query = query.Where("status = ?", models.OfficeUserStatusREQUESTED)
+ query.GroupBy("office_users.id")
+
+ var order = "desc"
+ if ordering.SortOrder() != nil && *ordering.SortOrder() {
+ order = "asc"
+ }
+
+ var orderTerm = "id"
+ if ordering.Column() != nil {
+ orderTerm = *ordering.Column()
+ }
+
+ query.Order(fmt.Sprintf("%s %s", orderTerm, order))
+ query.Select("office_users.*")
+
+ err := query.Paginate(pagination.Page(), pagination.PerPage()).All(&requestedUsers)
+ if err != nil {
+ return nil, 0, err
+ }
+
+ if orderTerm == "transportation_office_id" {
+ if order == "desc" {
+ sort.Slice(requestedUsers, func(i, j int) bool {
+ return requestedUsers[i].TransportationOffice.Name > requestedUsers[j].TransportationOffice.Name
+ })
+ } else {
+ sort.Slice(requestedUsers, func(i, j int) bool {
+ return requestedUsers[i].TransportationOffice.Name < requestedUsers[j].TransportationOffice.Name
+ })
+ }
+ }
+
+ count := query.Paginator.TotalEntriesSize
+ return requestedUsers, count, nil
}
// FetchAdminUserList uses the passed query builder to fetch a list of office users
diff --git a/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet.go b/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet.go
index a3ecc01ee96..90b187f0be0 100644
--- a/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet.go
+++ b/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet.go
@@ -93,7 +93,11 @@ func (SSWPPMComputer *SSWPPMComputer) FormatValuesShipmentSummaryWorksheet(shipm
A. the difference between the GTCC paid expenses and the GCC OR
B. the total member-paid expenses plus the member paid SIT
**/
- if shipmentSummaryFormData.IsActualExpenseReimbursement {
+ if shipmentSummaryFormData.IsActualExpenseReimbursement && isPaymentPacket {
+ if shipmentSummaryFormData.PPMShipment.FinalIncentive == nil {
+ return services.Page1Values{}, services.Page2Values{}, services.Page3Values{}, fmt.Errorf("missing FinalIncentive: required for actual expense reimbursement (Order ID: %s)", shipmentSummaryFormData.Order.ID)
+ }
+
floatFinalIncentive := shipmentSummaryFormData.PPMShipment.FinalIncentive.ToDollarFloatNoRound() // FinalIncentive == ActualGCC
if sumGTCC < floatFinalIncentive { // There are funds left over after GTCC to pay out member expenses
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 ae83dae1b9f..7eca297f3a6 100644
--- a/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet_test.go
+++ b/pkg/services/shipment_summary_worksheet/shipment_summary_worksheet_test.go
@@ -1452,7 +1452,7 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestActualExpenseReimbursemen
// Also test for AOA instead of payment packet
page1Data, page2Data, Page3Data, err = sswPPMComputer.FormatValuesShipmentSummaryWorksheet(ssd, false)
suite.NoError(err)
- suite.Equal(expectedDisbursementString(20000, 0), page2Data.Disbursement)
+ suite.Equal("N/A", page2Data.Disbursement)
suite.Equal("$0.00", page2Data.PPMRemainingEntitlement)
// Check PDF generation again
@@ -1485,7 +1485,7 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestActualExpenseReimbursemen
page1Data, page2Data, Page3Data, err = sswPPMComputer.FormatValuesShipmentSummaryWorksheet(ssd, false)
suite.NoError(err)
- suite.Equal(expectedDisbursementString(11500, 8500), page2Data.Disbursement)
+ suite.Equal("N/A", page2Data.Disbursement)
suite.Equal("$0.00", page2Data.PPMRemainingEntitlement)
test, info, err = ppmGenerator.FillSSWPDFForm(page1Data, page2Data, Page3Data)
@@ -1517,7 +1517,7 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestActualExpenseReimbursemen
page1Data, page2Data, Page3Data, err = sswPPMComputer.FormatValuesShipmentSummaryWorksheet(ssd, false)
suite.NoError(err)
- suite.Equal(expectedDisbursementString(11500, 3000), page2Data.Disbursement)
+ suite.Equal("N/A", page2Data.Disbursement)
suite.Equal("$0.00", page2Data.PPMRemainingEntitlement)
test, info, err = ppmGenerator.FillSSWPDFForm(page1Data, page2Data, Page3Data)
@@ -1992,3 +1992,70 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatDisbursement() {
result = formatDisbursement(expensesMap, ppmRemainingEntitlement)
suite.Equal(expectedResult, result)
}
+
+func (suite *ShipmentSummaryWorksheetServiceSuite) TestAOAPaymentPacketWithNilFinalIncentive() {
+ mockPPMCloseoutFetcher := &mocks.PPMCloseoutFetcher{}
+ sswPPMComputer := NewSSWPPMComputer(mockPPMCloseoutFetcher)
+ fortGordon := factory.FetchOrBuildOrdersDutyLocation(suite.DB())
+ orderIssueDate := time.Date(2018, time.December, 21, 0, 0, 0, 0, time.UTC)
+ locator := "ABCDEF-01"
+
+ shipment := models.PPMShipment{
+ Shipment: models.MTOShipment{
+ ShipmentLocator: &locator,
+ },
+ IsActualExpenseReimbursement: models.BoolPointer(true),
+ }
+
+ order := models.Order{
+ IssueDate: orderIssueDate,
+ OrdersType: internalmessages.OrdersTypePERMANENTCHANGEOFSTATION,
+ OrdersNumber: models.StringPointer("012345"),
+ NewDutyLocationID: fortGordon.ID,
+ TAC: models.StringPointer("NTA4"),
+ SAC: models.StringPointer("SAC"),
+ HasDependents: true,
+ SpouseHasProGear: true,
+ }
+ storageExpense := models.MovingExpenseReceiptTypeStorage
+ contractedExpense := models.MovingExpenseReceiptTypeContractedExpense
+ movingExpenses := models.MovingExpenses{
+ {
+ MovingExpenseType: &contractedExpense,
+ PaidWithGTCC: models.BoolPointer(false),
+ },
+ {
+ MovingExpenseType: &contractedExpense,
+ PaidWithGTCC: models.BoolPointer(true),
+ },
+ {
+ MovingExpenseType: &storageExpense,
+ PaidWithGTCC: models.BoolPointer(false),
+ },
+ {
+ MovingExpenseType: &storageExpense,
+ PaidWithGTCC: models.BoolPointer(true),
+ },
+ }
+ signedCertType := models.SignedCertificationTypeCloseoutReviewedPPMPAYMENT
+ cert := models.SignedCertification{
+ CertificationType: &signedCertType,
+ CertificationText: "APPROVED",
+ Signature: "Firstname Lastname",
+ UpdatedAt: time.Now(),
+ PpmID: models.UUIDPointer(shipment.ID),
+ }
+ var certs []*models.SignedCertification
+ certs = append(certs, &cert)
+
+ ssd := models.ShipmentSummaryFormData{
+ Order: order,
+ MovingExpenses: movingExpenses,
+ PPMShipment: shipment,
+ SignedCertifications: certs,
+ IsActualExpenseReimbursement: true,
+ }
+ _, _, _, err := sswPPMComputer.FormatValuesShipmentSummaryWorksheet(ssd, true)
+ suite.Error(err)
+ suite.Contains(err.Error(), "missing FinalIncentive: required for actual expense reimbursement")
+}
diff --git a/pkg/services/transportation_office.go b/pkg/services/transportation_office.go
index 307f0d0fef5..7255991f376 100644
--- a/pkg/services/transportation_office.go
+++ b/pkg/services/transportation_office.go
@@ -9,8 +9,8 @@ import (
//go:generate mockery --name TransportationOfficesFetcher
type TransportationOfficesFetcher interface {
- GetTransportationOffices(appCtx appcontext.AppContext, search string, forPpm bool) (*models.TransportationOffices, error)
+ GetTransportationOffices(appCtx appcontext.AppContext, search string, forPpm bool, forAdminOfficeUserReqFilter bool) (*models.TransportationOffices, error)
GetTransportationOffice(appCtx appcontext.AppContext, transportationOfficeID uuid.UUID, includeOnlyPPMCloseoutOffices bool) (*models.TransportationOffice, error)
GetAllGBLOCs(appCtx appcontext.AppContext) (*models.GBLOCs, error)
- GetCounselingOffices(appCtx appcontext.AppContext, dutyLocationID uuid.UUID) (*models.TransportationOffices, error)
+ GetCounselingOffices(appCtx appcontext.AppContext, dutyLocationID uuid.UUID, serviceMemberID uuid.UUID) (*models.TransportationOffices, error)
}
diff --git a/pkg/services/transportation_office/transportation_office_fetcher.go b/pkg/services/transportation_office/transportation_office_fetcher.go
index be942890c75..8fde2e98093 100644
--- a/pkg/services/transportation_office/transportation_office_fetcher.go
+++ b/pkg/services/transportation_office/transportation_office_fetcher.go
@@ -46,8 +46,8 @@ func (o transportationOfficesFetcher) GetTransportationOffice(appCtx appcontext.
return &transportationOffice, nil
}
-func (o transportationOfficesFetcher) GetTransportationOffices(appCtx appcontext.AppContext, search string, forPpm bool) (*models.TransportationOffices, error) {
- officeList, err := FindTransportationOffice(appCtx, search, forPpm)
+func (o transportationOfficesFetcher) GetTransportationOffices(appCtx appcontext.AppContext, search string, forPpm bool, forAdminOfficeUserReqFilter bool) (*models.TransportationOffices, error) {
+ officeList, err := FindTransportationOffice(appCtx, search, forPpm, forAdminOfficeUserReqFilter)
if err != nil {
switch err {
@@ -61,9 +61,15 @@ func (o transportationOfficesFetcher) GetTransportationOffices(appCtx appcontext
return &officeList, nil
}
-func FindTransportationOffice(appCtx appcontext.AppContext, search string, forPpm bool) (models.TransportationOffices, error) {
+func FindTransportationOffice(appCtx appcontext.AppContext, search string, forPpm bool, forAdminOfficeUserReqFilter bool) (models.TransportationOffices, error) {
var officeList []models.TransportationOffice
+ // Changing return limit for Admin Requested Office Users Transportation Office Filter implementation
+ var limit = 5
+ if forAdminOfficeUserReqFilter {
+ limit = 50
+ }
+
// The % operator filters out strings that are below this similarity threshold
err := appCtx.DB().Q().RawQuery("SET pg_trgm.similarity_threshold = 0.03").Exec()
if err != nil {
@@ -80,13 +86,13 @@ func FindTransportationOffice(appCtx appcontext.AppContext, search string, forPp
}
sqlQuery += `
order by sim desc
- limit 5)
+ limit $2)
select office.*
from names n inner join transportation_offices office on n.transportation_office_id = office.id
group by office.id
order by max(n.sim) desc, office.name
- limit 5`
- query := appCtx.DB().Q().RawQuery(sqlQuery, search)
+ limit $2`
+ query := appCtx.DB().Q().RawQuery(sqlQuery, search, limit)
if err := query.All(&officeList); err != nil {
if errors.Cause(err).Error() != models.RecordNotFoundErrorString {
return officeList, err
@@ -127,8 +133,8 @@ func ListDistinctGBLOCs(appCtx appcontext.AppContext) (models.GBLOCs, error) {
return gblocList, err
}
-func (o transportationOfficesFetcher) GetCounselingOffices(appCtx appcontext.AppContext, dutyLocationID uuid.UUID) (*models.TransportationOffices, error) {
- officeList, err := findCounselingOffice(appCtx, dutyLocationID)
+func (o transportationOfficesFetcher) GetCounselingOffices(appCtx appcontext.AppContext, dutyLocationID uuid.UUID, serviceMemberID uuid.UUID) (*models.TransportationOffices, error) {
+ officeList, err := findCounselingOffice(appCtx, dutyLocationID, serviceMemberID)
if err != nil {
switch err {
@@ -143,7 +149,8 @@ func (o transportationOfficesFetcher) GetCounselingOffices(appCtx appcontext.App
}
// return all the transportation offices in the GBLOC of the given duty location where provides_services_counseling = true
-func findCounselingOffice(appCtx appcontext.AppContext, dutyLocationID uuid.UUID) (models.TransportationOffices, error) {
+// serviceMemberID is only provided when this function is called by the office handler
+func findCounselingOffice(appCtx appcontext.AppContext, dutyLocationID uuid.UUID, serviceMemberID uuid.UUID) (models.TransportationOffices, error) {
var officeList []models.TransportationOffice
duty_location, err := models.FetchDutyLocation(appCtx.DB(), dutyLocationID)
@@ -157,7 +164,7 @@ func findCounselingOffice(appCtx appcontext.AppContext, dutyLocationID uuid.UUID
// Find for oconus duty location
// ********************************
if *duty_location.Address.IsOconus {
- gblocDepartmentIndicator, err := findOconusGblocDepartmentIndicator(appCtx, duty_location)
+ gblocDepartmentIndicator, err := findOconusGblocDepartmentIndicator(appCtx, duty_location, serviceMemberID)
if err != nil {
return officeList, err
}
@@ -238,8 +245,8 @@ 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)
+func findOconusGblocDepartmentIndicator(appCtx appcontext.AppContext, dutyLocation models.DutyLocation, serviceMemberID uuid.UUID) (*oconusGblocDepartmentIndicator, error) {
+ serviceMember, err := models.FetchServiceMember(appCtx.DB(), serviceMemberID)
if err != nil {
return nil, err
}
diff --git a/pkg/services/transportation_office/transportation_office_fetcher_test.go b/pkg/services/transportation_office/transportation_office_fetcher_test.go
index cd8345a8aa6..64c377deb30 100644
--- a/pkg/services/transportation_office/transportation_office_fetcher_test.go
+++ b/pkg/services/transportation_office/transportation_office_fetcher_test.go
@@ -42,7 +42,7 @@ func (suite *TransportationOfficeServiceSuite) Test_SearchTransportationOffice()
},
},
}, nil)
- office, err := FindTransportationOffice(suite.AppContextForTest(), "LRC Fort Knox", true)
+ office, err := FindTransportationOffice(suite.AppContextForTest(), "LRC Fort Knox", true, false)
suite.NoError(err)
suite.Equal(transportationOffice.Name, office[0].Name)
@@ -53,7 +53,7 @@ func (suite *TransportationOfficeServiceSuite) Test_SearchTransportationOffice()
func (suite *TransportationOfficeServiceSuite) Test_SearchWithNoTransportationOffices() {
- office, err := FindTransportationOffice(suite.AppContextForTest(), "LRC Fort Knox", true)
+ office, err := FindTransportationOffice(suite.AppContextForTest(), "LRC Fort Knox", true, false)
suite.NoError(err)
suite.Len(office, 0)
}
@@ -87,7 +87,7 @@ func (suite *TransportationOfficeServiceSuite) Test_SortedTransportationOffices(
},
}, nil)
- office, err := FindTransportationOffice(suite.AppContextForTest(), "JPPSO", true)
+ office, err := FindTransportationOffice(suite.AppContextForTest(), "JPPSO", true, false)
suite.NoError(err)
suite.Equal(transportationOffice1.Name, office[0].Name)
@@ -180,7 +180,16 @@ func (suite *TransportationOfficeServiceSuite) Test_FindCounselingOffices() {
},
}, nil)
- offices, err := findCounselingOffice(suite.AppContextForTest(), origDutyLocation.ID)
+ armyAffliation := models.AffiliationARMY
+ serviceMember := factory.BuildServiceMember(suite.DB(), []factory.Customization{
+ {
+ Model: models.ServiceMember{
+ Affiliation: &armyAffliation,
+ },
+ },
+ }, nil)
+
+ offices, err := findCounselingOffice(suite.AppContextForTest(), origDutyLocation.ID, serviceMember.ID)
suite.NoError(err)
suite.Len(offices, 2)
@@ -294,7 +303,7 @@ func (suite *TransportationOfficeServiceSuite) Test_Oconus_AK_FindCounselingOffi
appCtx := suite.AppContextWithSessionForTest(&auth.Session{
ServiceMemberID: serviceMember.ID,
})
- departmentIndictor, err := findOconusGblocDepartmentIndicator(appCtx, dutylocation)
+ departmentIndictor, err := findOconusGblocDepartmentIndicator(appCtx, dutylocation, serviceMember.ID)
suite.NotNil(departmentIndictor)
suite.Nil(err)
suite.Nil(departmentIndictor.DepartmentIndicator)
@@ -320,7 +329,7 @@ func (suite *TransportationOfficeServiceSuite) Test_Oconus_AK_FindCounselingOffi
ServiceMemberID: serviceMember.ID,
})
suite.Nil(err)
- departmentIndictor, err := findOconusGblocDepartmentIndicator(appCtx, dutylocation)
+ departmentIndictor, err := findOconusGblocDepartmentIndicator(appCtx, dutylocation, appCtx.Session().ServiceMemberID)
suite.NotNil(departmentIndictor)
suite.Nil(err)
suite.NotNil(departmentIndictor.DepartmentIndicator)
@@ -336,7 +345,7 @@ func (suite *TransportationOfficeServiceSuite) Test_Oconus_AK_FindCounselingOffi
ServiceMemberID: uuid.Must(uuid.NewV4()),
})
- departmentIndictor, err := findOconusGblocDepartmentIndicator(appCtx, dutylocation)
+ departmentIndictor, err := findOconusGblocDepartmentIndicator(appCtx, dutylocation, appCtx.Session().ServiceMemberID)
suite.Nil(departmentIndictor)
suite.NotNil(err)
})
@@ -346,7 +355,7 @@ func (suite *TransportationOfficeServiceSuite) Test_Oconus_AK_FindCounselingOffi
ServiceMemberID: uuid.Must(uuid.NewV4()),
})
unknown_duty_location_id := uuid.Must(uuid.NewV4())
- offices, err := findCounselingOffice(appCtx, unknown_duty_location_id)
+ offices, err := findCounselingOffice(appCtx, unknown_duty_location_id, appCtx.Session().ServiceMemberID)
suite.Nil(offices)
suite.NotNil(err)
})
@@ -369,7 +378,7 @@ func (suite *TransportationOfficeServiceSuite) Test_Oconus_AK_FindCounselingOffi
})
suite.Nil(err)
- offices, err := findCounselingOffice(appCtx, dutylocation.ID)
+ offices, err := findCounselingOffice(appCtx, dutylocation.ID, appCtx.Session().ServiceMemberID)
suite.NotNil(offices)
suite.Nil(err)
suite.Equal(1, len(offices))
@@ -385,7 +394,7 @@ func (suite *TransportationOfficeServiceSuite) Test_Oconus_AK_FindCounselingOffi
},
},
}, nil)
- offices, err = findCounselingOffice(appCtx, dutylocation.ID)
+ offices, err = findCounselingOffice(appCtx, dutylocation.ID, appCtx.Session().ServiceMemberID)
suite.NotNil(offices)
suite.Nil(err)
suite.Equal(2, len(offices))
diff --git a/pkg/testdatagen/scenario/shared.go b/pkg/testdatagen/scenario/shared.go
index e380cd47da1..3eec4a8f5ee 100644
--- a/pkg/testdatagen/scenario/shared.go
+++ b/pkg/testdatagen/scenario/shared.go
@@ -4222,11 +4222,15 @@ func createHHGWithOriginSITServiceItems(
handlerConfig := handlers.Config{}
ppmEstimator := ppmshipment.NewEstimatePPM(handlerConfig.DTODPlanner(), &paymentrequesthelper.RequestPaymentHelper{})
mtoUpdater := movetaskorder.NewMoveTaskOrderUpdater(queryBuilder, serviceItemCreator, moveRouter, signedCertificationCreator, signedCertificationUpdater, ppmEstimator)
- _, approveErr := mtoUpdater.MakeAvailableToPrime(appCtx, move.ID, etag.GenerateEtag(move.UpdatedAt), true, true)
+ _, approveErr := mtoUpdater.ApproveMoveAndCreateServiceItems(appCtx, move.ID, etag.GenerateEtag(move.UpdatedAt), true, true)
if approveErr != nil {
logger.Fatal("Error approving move")
}
+ _, _, primeErr := mtoUpdater.MakeAvailableToPrime(appCtx, move.ID)
+ if primeErr != nil {
+ logger.Fatal("Error making move available to Prime")
+ }
// AvailableToPrimeAt is set to the current time when a move is approved, we need to update it to fall within the
// same contract as the rest of the timestamps on our move for pricing to work.
@@ -4493,7 +4497,11 @@ func createHHGWithDestinationSITServiceItems(appCtx appcontext.AppContext, prime
handlerConfig := handlers.Config{}
ppmEstimator := ppmshipment.NewEstimatePPM(handlerConfig.DTODPlanner(), &paymentrequesthelper.RequestPaymentHelper{})
mtoUpdater := movetaskorder.NewMoveTaskOrderUpdater(queryBuilder, serviceItemCreator, moveRouter, signedCertificationCreator, signedCertificationUpdater, ppmEstimator)
- _, approveErr := mtoUpdater.MakeAvailableToPrime(appCtx, move.ID, etag.GenerateEtag(move.UpdatedAt), true, true)
+ _, approveErr := mtoUpdater.ApproveMoveAndCreateServiceItems(appCtx, move.ID, etag.GenerateEtag(move.UpdatedAt), true, true)
+ _, _, primeErr := mtoUpdater.MakeAvailableToPrime(appCtx, move.ID)
+ if primeErr != nil {
+ logger.Fatal("Error making move available to Prime")
+ }
// AvailableToPrimeAt is set to the current time when a move is approved, we need to update it to fall within the
// same contract as the rest of the timestamps on our move for pricing to work.
@@ -4903,7 +4911,11 @@ func createHHGWithPaymentServiceItems(
handlerConfig := handlers.Config{}
ppmEstimator := ppmshipment.NewEstimatePPM(handlerConfig.DTODPlanner(), &paymentrequesthelper.RequestPaymentHelper{})
mtoUpdater := movetaskorder.NewMoveTaskOrderUpdater(queryBuilder, serviceItemCreator, moveRouter, signedCertificationCreator, signedCertificationUpdater, ppmEstimator)
- _, approveErr := mtoUpdater.MakeAvailableToPrime(appCtx, move.ID, etag.GenerateEtag(move.UpdatedAt), true, true)
+ _, approveErr := mtoUpdater.ApproveMoveAndCreateServiceItems(appCtx, move.ID, etag.GenerateEtag(move.UpdatedAt), true, true)
+ _, _, primeErr := mtoUpdater.MakeAvailableToPrime(appCtx, move.ID)
+ if primeErr != nil {
+ logger.Fatal("Error making move available to Prime")
+ }
// AvailableToPrimeAt is set to the current time when a move is approved, we need to update it to fall within the
// same contract as the rest of the timestamps on our move for pricing to work.
diff --git a/scripts/rds-snapshot-app-db b/scripts/rds-snapshot-app-db
index 6a3e1a704c6..ea2f0afd5c0 100755
--- a/scripts/rds-snapshot-app-db
+++ b/scripts/rds-snapshot-app-db
@@ -19,17 +19,35 @@ db_snapshot_identifier=$db_instance_identifier-$(date +%s)
readonly db_snapshot_identifier
readonly tags=("Key=Environment,Value=$environment" "Key=Tool,Value=$(basename "$0")")
+
echo
echo "Wait for concurrent database snapshots for ${db_instance_identifier} to complete before continuing ..."
-time aws rds wait db-snapshot-completed --db-instance-identifier "$db_instance_identifier"
+time aws rds wait db-snapshot-completed --db-instance-identifier "$db_instance_identifier"
+
echo
echo "Create database snapshot for ${db_instance_identifier} with identifier ${db_snapshot_identifier}"
-aws rds create-db-snapshot --cli-read-timeout 0 --cli-connect-timeout 0 --db-instance-identifier "$db_instance_identifier" --db-snapshot-identifier "$db_snapshot_identifier" --tags "${tags[@]}"
+
+
+aws rds create-db-snapshot --db-instance-identifier "$db_instance_identifier" --db-snapshot-identifier "$db_snapshot_identifier" --tags "${tags[@]}"
+
+#we want to loop while status is not available; check after initiating the create
+while true; do
+ db_description=$(aws rds describe-db-snapshots --db-snapshot-identifier "$db_snapshot_identifier")
+ db_status=$(echo "${db_description}" | jq -r ".DBSnapshots[].Status")
+ echo "${db_snapshot_identifier} -- ${db_status}"
+ if [[ "${db_status}" == "available" ]]; then
+ break
+ fi
+ sleep 15
+done
+
+
+#unnecessary confirm but will leave in
echo
echo "Wait for current database snapshot ${db_snapshot_identifier} to complete before continuing ..."
-time aws rds wait db-snapshot-completed --db-snapshot-identifier "$db_snapshot_identifier"
+time aws rds wait db-snapshot-completed --db-snapshot-identifier "$db_snapshot_identifier"
echo
echo "Describe the database snapshot ${db_snapshot_identifier}"
diff --git a/scripts/run-server-test b/scripts/run-server-test
index 0ead8674b81..45e8ab87afe 100755
--- a/scripts/run-server-test
+++ b/scripts/run-server-test
@@ -97,8 +97,15 @@ function server_report_cleanup()
# https://unix.stackexchange.com/questions/305190/remove-last-character-from-string-captured-with-awk
percent=$(grep '(statements)' "${test_dir}/go-coverage.txt" | awk '{print substr($NF, 1, length($NF)-1)}')
goal_percent=50
+
+ #check if gitlab uses python3
+ if [[ "${GITLAB:-}" == "1" ]]; then
+ python_exe="python3"
+ else
+ python_exe="python"
+ fi
# using a oneline python function to test if percent is less than goal and return a proper exit code
- if python -c "exit(1) if ${percent} < ${goal_percent} else exit()"; then
+ if exec "$python_exe" -c "exit(1) if ${percent} < ${goal_percent} else exit()"; then
# coverage is good
echo "total coverage is ${percent}%"
else
diff --git a/src/components/Customer/MtoShipmentForm/MtoShipmentForm.jsx b/src/components/Customer/MtoShipmentForm/MtoShipmentForm.jsx
index e1fb7f72263..d8e04da7494 100644
--- a/src/components/Customer/MtoShipmentForm/MtoShipmentForm.jsx
+++ b/src/components/Customer/MtoShipmentForm/MtoShipmentForm.jsx
@@ -314,12 +314,14 @@ class MtoShipmentForm extends Component {