diff --git a/.circleci/config.yml b/.circleci/config.yml index 8744b04c2ac..c19ae164ca7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -53,7 +53,7 @@ references: # set integration-mtls-ignore-branch to the branch if you want to # IGNORE mtls integration tests, or `placeholder_branch_name` if you # do want to run them - integration-mtls-ignore-branch: &integration-mtls-ignore-branch placeholder_branch_name + integration-mtls-ignore-branch: &integration-mtls-ignore-branch integrationTesting # set client-ignore-branch to the branch if you want to IGNORE # client tests, or `placeholder_branch_name` if you do want to run @@ -897,8 +897,8 @@ commands: export FEATURE_FLAG_MANAGE_SUPPORTING_DOCS=false export FEATURE_FLAG_THIRD_ADDRESS_AVAILABLE=false export FEATURE_FLAG_QUEUE_MANAGEMENT=false - export FEATURE_FLAG_UNACCOMPANIED_BAGGAGE=false export FEATURE_FLAG_ENABLE_ALASKA=false + export FEATURE_FLAG_UNACCOMPANIED_BAGGAGE=false export FEATURE_FLAG_ENABLE_HAWAII=false export FEATURE_FLAG_BULK_ASSIGNMENT=false @@ -945,6 +945,7 @@ commands: FEATURE_FLAG_UNACCOMPANIED_BAGGAGE: 'false' FEATURE_FLAG_ENABLE_ALASKA: 'false' FEATURE_FLAG_BULK_ASSIGNMENT: 'false' + command: | SHARD=$((${CIRCLE_NODE_INDEX}+1)) PLAYWRIGHT_JUNIT_OUTPUT_NAME=playwright-results.xml \ @@ -1546,7 +1547,7 @@ jobs: # # The trailing hyphen in restore_cache seems important # according to the page linked above - - v11-server-tests-coverage- + - v1-server-tests-coverage-integration - run: name: Ensure Test Coverage Increasing command: | @@ -1585,7 +1586,7 @@ jobs: - when: condition: and: - - equal: [main, << pipeline.git.branch >>] + - equal: [integrationTesting, << pipeline.git.branch >>] - when: always steps: - run: @@ -1602,7 +1603,7 @@ jobs: # Use the BuildNum to update the cache key so that the # coverage cache is always updated - save_cache: - key: v11-server-tests-coverage-{{ .BuildNum }} + key: v1-server-tests-coverage-integration{{ .BuildNum }} paths: - ~/transcom/mymove/tmp/baseline-go-coverage when: always @@ -1664,8 +1665,12 @@ jobs: - checkout - attach_workspace: at: . - - restore_cache: - # + - when: + condition: + equal: [integrationTesting, << pipeline.git.branch >>] + steps: + - restore_cache: + # # https://circleci.com/docs/caching/#restoring-cache # # restore the latest version of the test coverage in the @@ -1682,8 +1687,8 @@ jobs: # # The trailing hyphen in restore_cache seems important # according to the page linked above - keys: - - v8-client-tests-coverage- + keys: + - v1-client-tests-coverage-integration - run: name: Ensure Test Coverage Increasing command: | @@ -1720,7 +1725,7 @@ jobs: - when: condition: and: - - equal: [main, << pipeline.git.branch >>] + - equal: [integrationTesting, << pipeline.git.branch >>] steps: - run: name: 'Copy coverage to baseline' @@ -1735,7 +1740,7 @@ jobs: # Use the BuildNum to update the cache key so that the # coverage cache is always updated - save_cache: - key: v8-client-tests-coverage-{{ .BuildNum }} + key: v1-client-tests-coverage-integration{{ .BuildNum }} paths: - ~/transcom/mymove/tmp/baseline-jest-coverage when: always @@ -1752,7 +1757,6 @@ jobs: --value "${coverage}" \ --timestamp "${timestamp}" when: always - # Compile the server side of the app once and persist the relevant # build artifacts to the workspace. # This way we don't have to re-run the build and since all necessary @@ -2780,4 +2784,4 @@ experimental: notify: branches: only: - - main + - main \ No newline at end of file diff --git a/.envrc b/.envrc index 32fd448864a..d833ec770e4 100644 --- a/.envrc +++ b/.envrc @@ -220,9 +220,9 @@ export DEVLOCAL_AUTH=true export DOD_CA_PACKAGE="${MYMOVE_DIR}/config/tls/milmove-cert-bundle.p7b" # MyMove client certificate -# All of our DoD-signed certs are currently signed by DOD SW CA-75 +# All of our DoD-signed certs are currently signed by DOD SW CA-66 # This cannot be changed unless our certs are all resigned -MOVE_MIL_DOD_CA_CERT=$(cat "${MYMOVE_DIR}"/config/tls/dod-sw-ca-75.pem) +MOVE_MIL_DOD_CA_CERT=$(cat "${MYMOVE_DIR}"/config/tls/dod-sw-ca-66.pem) require MOVE_MIL_DOD_TLS_CERT "See 'DISABLE_AWS_VAULT_WRAPPER=1 AWS_REGION=us-gov-west-1 aws-vault exec transcom-gov-dev -- chamber read app-devlocal move_mil_dod_tls_cert'" require MOVE_MIL_DOD_TLS_KEY "See 'DISABLE_AWS_VAULT_WRAPPER=1 AWS_REGION=us-gov-west-1 aws-vault exec transcom-gov-dev -- chamber read app-devlocal move_mil_dod_tls_key'" export MOVE_MIL_DOD_CA_CERT @@ -232,19 +232,22 @@ export TZ="UTC" # AWS development access # -# To use S3/SES for local builds, you'll need to uncomment the following. +# To use S3/SES or SNS & SQS for local builds, you'll need to uncomment the following. # Do not commit the change: # # export STORAGE_BACKEND=s3 # export EMAIL_BACKEND=ses +# export RECEIVER_BACKEND=sns_sqs # # Instructions for using S3 storage backend here: https://dp3.atlassian.net/wiki/spaces/MT/pages/1470955567/How+to+test+storing+data+in+S3+locally # Instructions for using SES email backend here: https://dp3.atlassian.net/wiki/spaces/MT/pages/1467973894/How+to+test+sending+email+locally +# Instructions for using SNS&SQS backend here: https://dp3.atlassian.net/wiki/spaces/MT/pages/2793242625/How+to+test+notifications+receiver+locally # # The default and equivalent to not being set is: # # export STORAGE_BACKEND=local # export EMAIL_BACKEND=local +# export RECEIVER_BACKEND=local # # Setting region and profile conditionally while we migrate from com to govcloud. if [ "$STORAGE_BACKEND" == "s3" ]; then @@ -258,6 +261,13 @@ export AWS_S3_KEY_NAMESPACE=$USER export AWS_SES_DOMAIN="devlocal.dp3.us" export AWS_SES_REGION="us-gov-west-1" +if [ "$RECEIVER_BACKEND" == "sns_sqs" ]; then + export SNS_TAGS_UPDATED_TOPIC="app_s3_tag_events" + export SNS_REGION="us-gov-west-1" +# cleanup flag false by default, only used at server startup to wipe receiver artifacts from previous runs +# export RECEIVER_CLEANUP_ON_START=false +fi + # To use s3 links aws-bucketname/xx/user/ for local builds, # you'll need to add the following to your .envrc.local: # @@ -444,4 +454,4 @@ then fi # Check that all required environment variables are set -check_required_variables \ No newline at end of file +check_required_variables diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c0be88a7dfa..3554334587e 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -6,4 +6,39 @@ # owners will be requested for a review. # Add language specific code owners if it becomes relevant -* @transcom/codeowners +# database team +/migrations/ @transcom/cacidatabaseteam +/pkg/assets/sql_scripts/ @transcom/cacidatabaseteam + +# backend team +/pkg/ @transcom/cacibackendteam +/swagger-def/ @transcom/cacibackendteam +*.go @transcom/cacibackendteam + + +# frontend team +/src/ @transcom/cacifrontendteam +/playwright/tests/ @transcom/cacifrontendteam + + +## not required for team specific review as prime UI is not deployed to production app +src/components/PrimeUI/ +src/pages/PrimeUI/ + + +# Require tech lead review for changes to the following files +.golangci.yml @transcom/codeowners +.pre-commit-config.yaml @transcom/codeowners +.eslintrc.js @transcom/codeowners +/eslint-plugin-ato/ @transcom/codeowners +/pkg/ato-linter/ @transcom/codeowners +/scripts/ @transcom/codeowners +/.circleci/config.yml @transcom/codeowners +/config/ @transcom/codeowners +Dockerfile* @transcom/codeowners +Brewfile* @transcom/codeowners +/Makefile @transcom/codeowners +package.json @transcom/codeowners +go.mod @transcom/codeowners +/github/ @transcom/codeowners +docker-compose* @transcom/codeowners \ No newline at end of file diff --git a/.github/workflows/happo-tests.yml b/.github/workflows/happo-tests.yml index 574ce9ba248..434dafcf738 100644 --- a/.github/workflows/happo-tests.yml +++ b/.github/workflows/happo-tests.yml @@ -2,7 +2,7 @@ name: Happo CI on: pull_request: - branches: [main] + branches: [main,integrationTesting] jobs: changes: diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f83d084326b..0154965696a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -8,7 +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 @@ -30,16 +30,16 @@ variables: GOLANGCI_LINT_VERBOSE: "-v" # Specify the environment: loadtest, demo, exp - DP3_ENV: &dp3_env placeholder_env + DP3_ENV: &dp3_env loadtest # Specify the branch to deploy TODO: this might be not needed. So far useless - DP3_BRANCH: &dp3_branch placeholder_branch_name + DP3_BRANCH: &dp3_branch integrationTesting # Ignore branches for integration tests - INTEGRATION_IGNORE_BRANCH: &integration_ignore_branch placeholder_branch_name - INTEGRATION_MTLS_IGNORE_BRANCH: &integration_mtls_ignore_branch placeholder_branch_name - CLIENT_IGNORE_BRANCH: &client_ignore_branch placeholder_branch_name - SERVER_IGNORE_BRANCH: &server_ignore_branch placeholder_branch_name + INTEGRATION_IGNORE_BRANCH: &integration_ignore_branch integrationTesting + INTEGRATION_MTLS_IGNORE_BRANCH: &integration_mtls_ignore_branch integrationTesting + CLIENT_IGNORE_BRANCH: &client_ignore_branch integrationTesting + SERVER_IGNORE_BRANCH: &server_ignore_branch integrationTesting OTEL_IMAGE_TAG: &otel_image_tag "git-$OTEL_VERSION-$CI_COMMIT_SHORT_SHA" @@ -2043,4 +2043,4 @@ deploy_app_prd: after_script: - *announce_failure rules: - - *check_main \ No newline at end of file + - *check_main diff --git a/Dockerfile b/Dockerfile index 293ea88db39..a786a03f4db 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,4 +30,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 225bb958ab1..b63c28920d1 100644 --- a/Dockerfile.local +++ b/Dockerfile.local @@ -27,7 +27,8 @@ COPY --from=builder --chown=root:root /home/circleci/project/bin/rds-ca-2019-roo COPY --from=builder --chown=root:root /home/circleci/project/bin/milmove /bin/milmove COPY config/tls/milmove-cert-bundle.p7b /config/tls/milmove-cert-bundle.p7b -COPY config/tls/dod-sw-ca-66.pem /config/tls/dod-sw-ca-75.pem +COPY config/tls/dod-sw-ca-75.pem /config/tls/dod-sw-ca-75.pem +COPY config/tls/dod-sw-ca-66.pem /config/tls/dod-sw-ca-66.pem # While it's ok to have these certs copied locally, they should never be copied into Dockerfile. COPY config/tls/devlocal-ca.key /config/tls/devlocal-ca.key diff --git a/artifacts/accessibilityReport.html b/artifacts/accessibilityReport.html index e924f8add49..ad29b4c03da 100644 --- a/artifacts/accessibilityReport.html +++ b/artifacts/accessibilityReport.html @@ -87,7 +87,7 @@

-
axe-core found 4 violations
+
axe-core found 14 violations
@@ -106,10 +106,34 @@
axe-core found 4 violations
- + + + + + + + + + + + + + + + + + + + + + + + + + @@ -172,7 +196,7 @@

Element location

#root > .TitleAnnouncer_HiddenTitleAnnouncer__IxWhC[aria-live="polite"]

Element source

-
<div id="title-announcer" aria-live="polite" class="TitleAnnouncer_HiddenTitleAnnouncer__IxWhC" style="">my.move.mil - Move review</div>
+
<div id="title-announcer" aria-live="polite" class="TitleAnnouncer_HiddenTitleAnnouncer__IxWhC" style="">my.move.mil - Move - Eba7814f B197 45ce 953f 441d108c1fcd</div>
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
duplicate-id WCAG 2 Level A, WCAG 4.1.1 minor18
2Heading levels should only increase by oneheading-orderBest practicemoderate1
3Links must be distinguishable without relying on colorlink-in-text-blockWCAG 2 Level A, WCAG 1.4.1serious1
4Page should contain a level-one headingpage-has-heading-oneBest practicemoderate1
5 All page content should be contained by landmarks region Best practice
@@ -185,6 +209,360 @@
#app-root > .TitleAnnouncer_HiddenTitleAnnouncer__IxWhC[aria-live="polite"]
2 +

Element location

+
div[data-testid="stepContainer1"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > defs > filter
+

Element source

+
<filter id="filter-1"><feColorMatrix in="SourceGraphic" type="matrix" values="0 0 0 0 1.000000 0 0 0 0 1.000000 0 0 0 0 1.000000 0 0 0 1.000000 0"></feColorMatrix></filter>
+
+
+

Fix any of the following:

+
    +
  • Document has multiple static elements with the same id attribute: filter-1
  • +
+
+

Related node:

+
.Step_step-amended-orders__E06Yy > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > defs > filter
+
div[data-testid="stepContainer3"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > defs > filter
+
div[data-testid="stepContainer4"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > defs > filter
+
3 +

Element location

+
div[data-testid="stepContainer1"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"]
+

Element source

+
<g id="circle-checked" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+
+
+

Fix any of the following:

+
    +
  • Document has multiple static elements with the same id attribute: circle-checked
  • +
+
+

Related node:

+
.Step_step-amended-orders__E06Yy > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"]
+
div[data-testid="stepContainer3"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"]
+
div[data-testid="stepContainer4"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"]
+
4 +

Element location

+
div[data-testid="stepContainer1"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"]
+

Element source

+
<g id="Checklist_check" transform="translate(1.000000, 1.000000)">
+
+
+

Fix any of the following:

+
    +
  • Document has multiple static elements with the same id attribute: Checklist_check
  • +
+
+

Related node:

+
.Step_step-amended-orders__E06Yy > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"]
+
div[data-testid="stepContainer3"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"]
+
div[data-testid="stepContainer4"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"]
+
5 +

Element location

+
div[data-testid="stepContainer1"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > circle
+

Element source

+
<circle id="Oval" fill="#162E51" cx="65.5" cy="65.5" r="65.5"></circle>
+
+
+

Fix any of the following:

+
    +
  • Document has multiple static elements with the same id attribute: Oval
  • +
+
+

Related node:

+
.Step_step-amended-orders__E06Yy > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > circle
+
div[data-testid="stepContainer3"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > circle
+
div[data-testid="stepContainer4"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > circle
+
6 +

Element location

+
div[data-testid="stepContainer1"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"]
+

Element source

+
<g id="Group" transform="translate(32.750000, 32.750000)" stroke-linecap="round" stroke-linejoin="round">
+
+
+

Fix any of the following:

+
    +
  • Document has multiple static elements with the same id attribute: Group
  • +
+
+

Related node:

+
.Step_step-amended-orders__E06Yy > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"]
+
div[data-testid="stepContainer3"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"]
+
div[data-testid="stepContainer4"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"]
+
7 +

Element location

+
div[data-testid="stepContainer1"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"] > g[filter="url(#filter-1)"]
+

Element source

+
<g filter="url(#filter-1)" id="icon-/-check-copy-2"><g><polyline id="Path" stroke="#000000" stroke-width="8.25" points="55.0950521 21.7480469 27.1850586 49.2955729 14.4986979 36.7739702"></polyline></g></g>
+
+
+

Fix any of the following:

+
    +
  • Document has multiple static elements with the same id attribute: icon-/-check-copy-2
  • +
+
+

Related node:

+
.Step_step-amended-orders__E06Yy > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"] > g[filter="url(#filter-1)"]
+
div[data-testid="stepContainer3"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"] > g[filter="url(#filter-1)"]
+
div[data-testid="stepContainer4"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"] > g[filter="url(#filter-1)"]
+
8 +

Element location

+
div[data-testid="stepContainer1"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"] > g[filter="url(#filter-1)"] > g > polyline
+

Element source

+
<polyline id="Path" stroke="#000000" stroke-width="8.25" points="55.0950521 21.7480469 27.1850586 49.2955729 14.4986979 36.7739702"></polyline>
+
+
+

Fix any of the following:

+
    +
  • Document has multiple static elements with the same id attribute: Path
  • +
+
+

Related node:

+
.Step_step-amended-orders__E06Yy > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"] > g[filter="url(#filter-1)"] > g > polyline
+
div[data-testid="stepContainer3"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"] > g[filter="url(#filter-1)"] > g > polyline
+
div[data-testid="stepContainer4"] > .Step_step-header-container__hgkRC > .Step_accept__IIjh5[width="133px"][height="133px"] > g[stroke="none"][stroke-width="1"][fill="none"] > g[transform="translate(1.000000, 1.000000)"] > g[stroke-linecap="round"][stroke-linejoin="round"] > g[filter="url(#filter-1)"] > g > polyline
+
+ + + +
+
+
+
+ 2. Heading levels should only increase by one +
+ Learn more +
+
+
heading-order
+
+ Best practice +
+
+
+

Ensures the order of headings is semantically correct

+
+ moderate +
+
+
+
+ Issue Tags: + cat.semantics + + best-practice +
+
+
+ + + + + + + + + + + + + + + +
#Issue Description + To solve this violation, you need to... +
1 +

Element location

+
h6
+

Element source

+
<h6 class="Contact_contactHeader__v48ze">Contacts</h6>
+
+
+

Fix any of the following:

+
    +
  • Heading order invalid
  • +
+
+
+
+
+
+
+
+
+
+ 3. Links must be distinguishable without relying on color +
+ Learn more +
+
+
link-in-text-block
+
+ WCAG 2 Level A, WCAG 1.4.1 +
+
+
+

Ensure links are distinguished from surrounding text in a way that does not rely on color

+
+ serious +
+
+
+
+ Issue Tags: + cat.color + + wcag2a + + wcag141 +
+
+
+ + + + + + + + + + + + + + + +
#Issue Description + To solve this violation, you need to... +
1 +

Element location

+
p:nth-child(3) > .usa-link[target="_blank"][rel="noopener noreferrer"]
+

Element source

+
<a class="usa-link" target="_blank" rel="noopener noreferrer" href="https://www.militaryonesource.mil/moving-housing/moving/planning-your-move/customer-service-contacts-for-military-pcs/">directory of PCS-related contacts</a>
+
+
+

Fix any of the following:

+
    +
  • The link has insufficient color contrast of 2.57:1 with the surrounding text. (Minimum contrast is 3:1, link text: #0050d8, surrounding text: #1b1b1b)
  • +
  • The link has no styling (such as underline) to distinguish it from the surrounding text
  • +
+
+

Related node:

+
.Contact_contactContainer__jYtTz > p:nth-child(3)
+
.Contact_contactContainer__jYtTz > p:nth-child(3)
+
+
+
+
+
+
+
+
+ 4. Page should contain a level-one heading +
+ Learn more +
+
+
page-has-heading-one
+
+ Best practice +
+
+
+

Ensure that the page, or at least one of its frames contains a level-one heading

+
+ moderate +
+
+
+
+ Issue Tags: + cat.semantics + + best-practice +
+
+
+ + + + + + + + + + + + + +
#Issue Description + To solve this violation, you need to... +
1 +

Element location

+
html
+

Element source

+
<html lang="en">
+
+
+

Fix all of the following:

+
    +
  • Page must have a level-one heading
  • +
+
+
@@ -194,7 +572,7 @@
- 2. All page content should be contained by landmarks + 5. All page content should be contained by landmarks
github.com/transcom/pdfcpu v0.0.0-20250131173611-4b416bd62126 +replace github.com/pdfcpu/pdfcpu => github.com/transcom/pdfcpu v0.0.0-20250204205540-1a00605a1039 diff --git a/go.sum b/go.sum index f6cf27c293a..545ff6c98bc 100644 --- a/go.sum +++ b/go.sum @@ -82,6 +82,10 @@ github.com/aws/aws-sdk-go-v2/service/s3 v1.59.0 h1:Cso4Ev/XauMVsbwdhYEoxg8rxZWw4 github.com/aws/aws-sdk-go-v2/service/s3 v1.59.0/go.mod h1:BSPI0EfnYUuNHPS0uqIo5VrRwzie+Fp+YhQOUs16sKI= github.com/aws/aws-sdk-go-v2/service/ses v1.25.3 h1:wcfUsE2nqsXhEj68gxr7MnGXNPcBPKx0RW2DzBVgVlM= github.com/aws/aws-sdk-go-v2/service/ses v1.25.3/go.mod h1:6Ul/Ir8oOCsI3dFN0prULK9fvpxP+WTYmlHDkFzaAVA= +github.com/aws/aws-sdk-go-v2/service/sns v1.31.8 h1:vRSk062d1SmaEVbiqFePkvYuhCTnW2JnPkUdt19nqeY= +github.com/aws/aws-sdk-go-v2/service/sns v1.31.8/go.mod h1:wjhxA9hlVu75dCL/5Wcx8Cwmszvu6t0i8WEDypcB4+s= +github.com/aws/aws-sdk-go-v2/service/sqs v1.34.6 h1:DbjODDHumQBdJ3T+EO7AXVoFUeUhAsJYOdjStH5Ws4A= +github.com/aws/aws-sdk-go-v2/service/sqs v1.34.6/go.mod h1:7idt3XszF6sE9WPS1GqZRiDJOxw4oPtlRBXodWnCGjU= github.com/aws/aws-sdk-go-v2/service/ssm v1.52.8 h1:7cjN4Wp3U3cud17TsnUxSomTwKzKQGUWdq/N1aWqgMk= github.com/aws/aws-sdk-go-v2/service/ssm v1.52.8/go.mod h1:nUSNPaG8mv5rIu7EclHnFqZOjhreEUwRKENtKTtJ9aw= github.com/aws/aws-sdk-go-v2/service/sso v1.22.7 h1:pIaGg+08llrP7Q5aiz9ICWbY8cqhTkyy+0SHvfzQpTc= @@ -629,8 +633,8 @@ github.com/tiaguinho/gosoap v1.4.4 h1:4XZlaqf/y2UAbCPFGcZS4uLKrEvnMr+5pccIyQAUVg github.com/tiaguinho/gosoap v1.4.4/go.mod h1:4vv86Jl19UkOeoJW/aawihXYNJ/Iy2NHkhgmBUJ2Ibk= github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ= github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM= -github.com/transcom/pdfcpu v0.0.0-20250131173611-4b416bd62126 h1:XbLtbZvPTc5bY6DuXF2ZHPLPmE3GVe3T/o8PzfmITCA= -github.com/transcom/pdfcpu v0.0.0-20250131173611-4b416bd62126/go.mod h1:8EAma3IBIS7ssMiPlcNIPWwISTuP31WToXfGvc327vI= +github.com/transcom/pdfcpu v0.0.0-20250204205540-1a00605a1039 h1:3D+dYz0iOcIcHF1+Q25B1TO53iBWHWtJQedY6mfXN1A= +github.com/transcom/pdfcpu v0.0.0-20250204205540-1a00605a1039/go.mod h1:8EAma3IBIS7ssMiPlcNIPWwISTuP31WToXfGvc327vI= github.com/urfave/cli v1.22.10 h1:p8Fspmz3iTctJstry1PYS3HVdllxnEzTEsgIgtxTrCk= github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/vektra/mockery/v2 v2.45.1 h1:6HpdnKiLCjVtzlRLQPUNIM0u7yrvAoZ7VWF1TltJvTM= @@ -723,8 +727,8 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= @@ -831,8 +835,8 @@ golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -849,8 +853,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= -golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= +golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/migrations/app/migrations_manifest.txt b/migrations/app/migrations_manifest.txt index 020752483cd..277c0f23ca6 100644 --- a/migrations/app/migrations_manifest.txt +++ b/migrations/app/migrations_manifest.txt @@ -1079,7 +1079,7 @@ 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 +20250110201339_add_payment_params_for_intl_crating_uncrating.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 @@ -1089,3 +1089,12 @@ 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 +20250123210535_update_re_intl_transit_times_for_ak_hhg.up.sql +20250127143137_insert_nsra_re_intl_transit_times.up.sql +20250204162411_updating_create_accessorial_service_item_proc_for_crating.up.sql +20250206173204_add_hawaii_data.up.sql +20250207153450_add_fetch_documents_func.up.sql +20250210175754_B22451_update_dest_queue_to_consider_sit_extensions.up.sql +20250213151815_fix_spacing_fetch_documents.up.sql +20250213214427_drop_received_by_gex_payment_request_status_type.up.sql diff --git a/migrations/app/schema/20241007162933_addPaymentRequestEdiFiles.up.sql b/migrations/app/schema/20241007162933_addPaymentRequestEdiFiles.up.sql index ae0d2d1ae60..e7bc2cbb582 100644 --- a/migrations/app/schema/20241007162933_addPaymentRequestEdiFiles.up.sql +++ b/migrations/app/schema/20241007162933_addPaymentRequestEdiFiles.up.sql @@ -1,8 +1,13 @@ -CREATE TABLE payment_request_edi_files ( - id UUID PRIMARY KEY, - payment_request_number TEXT NOT NULL, - edi_string TEXT NOT NULL, - file_name TEXT NOT NULL, - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP -); +CREATE TABLE + IF NOT EXISTS payment_request_edi_files ( + id UUID PRIMARY KEY, + payment_request_number TEXT NOT NULL, + edi_string TEXT NOT NULL, + file_name TEXT NOT NULL, + created_at TIMESTAMP + WITH + TIME ZONE DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP + WITH + TIME ZONE DEFAULT CURRENT_TIMESTAMP + ); \ No newline at end of file diff --git a/migrations/app/schema/20250106202424_update_duty_locs.up.sql b/migrations/app/schema/20250106202424_update_duty_locs.up.sql index d3bc09560e5..2ecfaf6a399 100644 --- a/migrations/app/schema/20250106202424_update_duty_locs.up.sql +++ b/migrations/app/schema/20250106202424_update_duty_locs.up.sql @@ -24,11 +24,11 @@ BEGIN 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) + 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) + + 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 $$; @@ -42,7 +42,7 @@ BEGIN 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) + 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 $$; diff --git a/migrations/app/schema/20250110201339_add_payment_params_for_intl_crating_uncrating.up.sql b/migrations/app/schema/20250110201339_add_payment_params_for_intl_crating_uncrating.up.sql new file mode 100644 index 00000000000..dedc7e9637d --- /dev/null +++ b/migrations/app/schema/20250110201339_add_payment_params_for_intl_crating_uncrating.up.sql @@ -0,0 +1,28 @@ +-- adding ExternalCrate param key for intl crating +INSERT INTO service_item_param_keys +(id,key,description,type,origin,created_at,updated_at) +VALUES +('7bb4a8eb-7fff-4e02-8809-f2def00af455','ExternalCrate', 'True if this an external crate', 'BOOLEAN', 'PRIME', now(), now()); + + +-- ICRT +INSERT INTO service_params +(id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) +VALUES +('2ee4d131-041f-498e-b921-cc77970341e9', (SELECT id FROM re_services WHERE code='ICRT'), (SELECT id FROM service_item_param_keys WHERE key='ContractCode'), now(), now(), 'false'), +('bd36234a-090e-4c06-a478-8194c3a78f82', (SELECT id FROM re_services WHERE code='ICRT'), (SELECT id FROM service_item_param_keys WHERE key='ContractYearName'), now(), now(), 'false'), +('bdcda078-6007-48d3-9c1a-16a1ae54dc69', (SELECT id FROM re_services WHERE code='ICRT'), (SELECT id FROM service_item_param_keys WHERE key='EscalationCompounded'), now(), now(), 'false'), +('c6f982f5-d603-43e7-94ed-15ae6e703f86', (SELECT id FROM re_services WHERE code='ICRT'), (SELECT id FROM service_item_param_keys WHERE key='PriceRateOrFactor'), now(), now(), 'false'), +('b323a481-3591-4609-84a5-5a1e8a56a51a', (SELECT id FROM re_services WHERE code='ICRT'), (SELECT id FROM service_item_param_keys WHERE key='StandaloneCrate'), now(), now(), 'true'), +('7fb5a389-bfd7-44d5-a8ff-ef784d37a6a1', (SELECT id FROM re_services WHERE code='ICRT'), (SELECT id FROM service_item_param_keys WHERE key='StandaloneCrateCap'), now(), now(), 'true'), +('3ca76951-c612-491f-ac9a-ad73c1129c99', (SELECT id FROM re_services WHERE code='ICRT'), (SELECT id FROM service_item_param_keys WHERE key='UncappedRequestTotal'), now(), now(), 'true'), +('d486a522-3fa3-45b2-9749-827c40b002b0', (SELECT id FROM re_services WHERE code='ICRT'), (SELECT id FROM service_item_param_keys where key='ExternalCrate'), now(), now(), 'true'); + +-- IUCRT +INSERT INTO service_params +(id,service_id,service_item_param_key_id,created_at,updated_at) +VALUES +('b4619dc8-d1ba-4f85-a198-e985ae80e614', (SELECT id FROM re_services WHERE code='IUCRT'), (SELECT id FROM service_item_param_keys WHERE key='ContractCode'), now(), now()), +('d5d7fc34-2b48-4f99-b053-9d118171f202', (SELECT id FROM re_services WHERE code='IUCRT'), (SELECT id FROM service_item_param_keys WHERE key='ContractYearName'), now(), now()), +('2272b490-ffd0-4b24-8ae1-38019dc5c67d', (SELECT id FROM re_services WHERE code='IUCRT'), (SELECT id FROM service_item_param_keys WHERE key='EscalationCompounded'), now(), now()), +('5fd0739b-695a-4d96-9c5a-f048dfaa8f0c', (SELECT id FROM re_services WHERE code='IUCRT'), (SELECT id FROM service_item_param_keys WHERE key='PriceRateOrFactor'), now(), now()); \ No newline at end of file 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 index a5679a89757..ebd5e787269 100644 --- a/migrations/app/schema/20250121184450_upd_duty_loc_B-22242.up.sql +++ b/migrations/app/schema/20250121184450_upd_duty_loc_B-22242.up.sql @@ -1,30 +1,30 @@ 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 $$; @@ -34,9 +34,9 @@ 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; @@ -45,45 +45,45 @@ 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 $$; @@ -99,13 +99,13 @@ BEGIN VALUES('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); END IF; - + IF NOT EXISTS (SELECT 1 FROM duty_locations WHERE id = '109ac405-47fb-4e1e-9efb-58290453ac09') THEN INSERT INTO public.duty_locations (id, "name", affiliation, address_id, created_at, updated_at, transportation_office_id, provides_services_counseling) VALUES('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); - + END IF; IF NOT EXISTS (SELECT 1 FROM duty_locations WHERE id = '693781f4-d011-4925-a492-aa1185f3f1fe') THEN @@ -113,9 +113,9 @@ BEGIN INSERT INTO public.duty_locations (id, "name", affiliation, address_id, created_at, updated_at, transportation_office_id, provides_services_counseling) VALUES('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); - + END IF; - + END $$; --associate duty loc Yuma, AZ 85365 to transportation office PPPO DMO MCAS Yuma - USMC diff --git a/migrations/app/schema/20250123173216_add_destination_queue_db_func_and_gbloc_view.up.sql b/migrations/app/schema/20250123173216_add_destination_queue_db_func_and_gbloc_view.up.sql new file mode 100644 index 00000000000..5148f0f57d7 --- /dev/null +++ b/migrations/app/schema/20250123173216_add_destination_queue_db_func_and_gbloc_view.up.sql @@ -0,0 +1,502 @@ +--insert USMC gbloc +insert into jppso_regions values ('85b324ae-1a0d-4f7d-971f-ea509dcc73d7', 'USMC','USMC',now(),now()); + +--insert USMC AORs +INSERT INTO gbloc_aors (id,jppso_regions_id,oconus_rate_area_id,department_indicator,shipment_type,is_active,created_at,updated_at) VALUES + ('a8eb35e2-275e-490b-9945-1971b954b958'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'e418be11-2b6f-4714-b026-e293528c50bd'::uuid,'MARINES',NULL,true,now(),now()), + ('ada1b48d-d2e0-481a-8a2e-a265a824647d'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'b41c5636-96dd-4f0d-a18e-eebb17f97ea5'::uuid,'MARINES',NULL,true,now(),now()), + ('588af482-7cd7-42ea-8e05-49dce645ecbe'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'02f32a6a-0338-4545-8437-059862892d2c'::uuid,'MARINES',NULL,true,now(),now()), + ('1ff3bed7-bbf3-432d-9da3-d76264d72913'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'0f128476-d52a-418c-8ba0-c8bfd1c32629'::uuid,'MARINES',NULL,true,now(),now()), + ('0853a854-98b2-4363-a2b1-db14c44dde2f'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'081f84c3-17ec-4ff6-97ce-d5c44a8e4a28'::uuid,'MARINES',NULL,true,now(),now()), + ('21c1ba40-2533-4196-9eb5-6ffddff3a794'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'ce7cdd91-e323-43a6-a604-daaa6bf8be06'::uuid,'MARINES',NULL,true,now(),now()), + ('19ca4073-8736-453e-bb0d-9b13e3b557b0'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'655050d4-711a-43b7-b06d-828ec990c35e'::uuid,'MARINES',NULL,true,now(),now()), + ('a9c2131e-09c3-480d-ba3e-0c144de18aa5'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'455a34af-30a8-4a98-a62b-6f40fd7f047b'::uuid,'MARINES',NULL,true,now(),now()), + ('649e72f8-cac8-483f-a9ed-c9659e37545b'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'1bf1daee-51bb-4c28-aac8-a126ef596486'::uuid,'MARINES',NULL,true,now(),now()), + ('7173f871-f948-4eed-86ae-5e977b16c426'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'15b1d852-0dde-4e1b-b3c6-e08bbc714db3'::uuid,'MARINES',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 + ('4f626e2a-cad0-4b4d-baa8-3101275bac23'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'d63de5bb-379b-4f3b-b4c1-554b9746d311'::uuid,'MARINES',NULL,true,now(),now()), + ('de59c604-9119-48fc-bf3f-883de17b7ee6'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'95d142f8-b50a-4108-b5e2-fbd3b7022d3b'::uuid,'MARINES',NULL,true,now(),now()), + ('1cec884c-9e34-42fe-8887-1e8b8fa1cd2e'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'922bb7da-d0e9-431c-a493-95a861dca929'::uuid,'MARINES',NULL,true,now(),now()), + ('0f24b453-e3bc-47ec-86b5-8d8937f65504'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'79aef0ca-9065-4c0e-a134-2889b250cc38'::uuid,'MARINES',NULL,true,now(),now()), + ('2d55560a-7d0a-474f-a0f9-b31c5be8d80e'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'1f181025-e410-41ac-935b-4b5147353f84'::uuid,'MARINES',NULL,true,now(),now()), + ('abcc37f6-9209-4639-8fa1-c8d5f6e4e77d'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'d9ede620-c3f1-4f8d-b60b-eb93664382f7'::uuid,'MARINES',NULL,true,now(),now()), + ('3ac990d2-5df4-4889-a943-2710f818e75a'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'bd27700b-3094-46cc-807b-f18472cbfaf0'::uuid,'MARINES',NULL,true,now(),now()), + ('f3084090-680f-4656-947a-eb2e773e4076'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'2f4a1689-ee65-45fa-9d0c-debd9344f8b9'::uuid,'MARINES',NULL,true,now(),now()), + ('a35ac50b-09a1-46ef-969e-17569717ee10'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'20c52934-2588-4d8f-b3ed-c046d771f4e9'::uuid,'MARINES',NULL,true,now(),now()), + ('107c1479-e6d9-44cb-8342-ac934055074d'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'15fe364b-add5-4ade-bdd9-38fe442616fb'::uuid,'MARINES',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 + ('843db089-67cb-463d-a255-1d198f4f7aaa'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'e5a1248e-3870-4a4c-9c9d-2056234eb64a'::uuid,'MARINES',NULL,true,now(),now()), + ('ac820ac9-d380-4c11-9103-172795658e1f'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'efc92db1-4b06-49ab-a295-a98f3f6c9c04'::uuid,'MARINES',NULL,true,now(),now()), + ('dca7ccd2-e438-4f82-8b76-9b61fbf2a593'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'13474ce5-8839-4af7-b975-c3f70ccdab7b'::uuid,'MARINES',NULL,true,now(),now()), + ('def1095b-2a5c-4f7c-8889-9fde15a7ec06'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'d3b05c5e-6faa-4fa9-b725-0904f8a4f3d7'::uuid,'MARINES',NULL,true,now(),now()), + ('d5fbc738-ee31-4c51-8fd4-bbb8db941dc1'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'e5f849fe-5672-4d0d-881c-078e56eea33d'::uuid,'MARINES',NULL,true,now(),now()), + ('ac8c75fe-f637-429d-80fa-913321a65372'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'fca7c60e-fbd9-4885-afdb-ae41c521b560'::uuid,'MARINES',NULL,true,now(),now()), + ('c33eb1f8-b0fc-4670-af5e-5bd423eca6e7'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'0d52a0f0-f39c-4d34-9387-2df45a5810d4'::uuid,'MARINES',NULL,true,now(),now()), + ('188ea995-b8b7-4ce0-97a9-f553f3b72c2f'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'af008e75-81d5-4211-8204-4964c78e70d9'::uuid,'MARINES',NULL,true,now(),now()), + ('4f13c1a6-059b-4aa2-9250-4316e60da2a7'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'dd689e55-af29-4c76-b7e0-c2429ea4833c'::uuid,'MARINES',NULL,true,now(),now()), + ('67e1ff6f-b79b-45cf-b250-3d4ec89bebae'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'60e08330-6869-4586-8198-35f7a4ae9ea7'::uuid,'MARINES',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 + ('3a7c679c-0439-4030-8f7e-f6d8e92720d7'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'f7478e79-dbfe-46c8-b337-1e7c46df79dc'::uuid,'MARINES',NULL,true,now(),now()), + ('db91f133-a301-4c87-af9f-6c10584063e6'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'486d7dd4-f51f-4b13-88fc-830b5b15f0a8'::uuid,'MARINES',NULL,true,now(),now()), + ('6b83cc22-36a9-470e-9f43-e65ade5e8a66'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'f66edf62-ba5e-47f8-8264-b6dc9e7dd9ba'::uuid,'MARINES',NULL,true,now(),now()), + ('3df30221-acd3-4428-890c-3a5ef5296cb1'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'0052c55a-d9d7-46b0-9328-58d3911f61b4'::uuid,'MARINES',NULL,true,now(),now()), + ('bae4c9c6-94d8-4ad0-bfc0-7642f5353199'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'16a51fd1-04ed-432a-a8d7-9f17c1de095d'::uuid,'MARINES',NULL,true,now(),now()), + ('1cd873ff-d170-4f48-8f5b-5c5146052d68'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'64b4756f-437d-4aa5-a95e-c396f0cafcbb'::uuid,'MARINES',NULL,true,now(),now()), + ('eb4d870b-8d66-4135-b294-8992e56ad76f'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'f2de1da5-a737-4493-b3f7-7700944a5b62'::uuid,'MARINES',NULL,true,now(),now()), + ('e8c0709c-1f08-4d3e-b848-72ab5b524677'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'7a9c2adb-0562-42c1-a5f1-81710dd19590'::uuid,'MARINES',NULL,true,now(),now()), + ('3c53df36-ee6b-4bc0-a5ca-cedc1dc3c32e'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'e713eed6-35a6-456d-bfb2-0e0646078ab8'::uuid,'MARINES',NULL,true,now(),now()), + ('7cbc7e6c-4da7-47a1-ac93-171b89dba1e0'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'a4594957-0ec6-4edc-85c2-68871f4f6359'::uuid,'MARINES',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 + ('d22cb1d2-79b4-45c9-bb6f-8acef54a67b0'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'acd4d2f9-9a49-4a73-be14-3515f19ba0d1'::uuid,'MARINES',NULL,true,now(),now()), + ('5b5e7a5a-d027-44f2-9b4f-f25c1a91bc00'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'c8300ab6-519c-4bef-9b81-25e7334773ca'::uuid,'MARINES',NULL,true,now(),now()), + ('21307477-912d-40ca-a399-6dfebcc322ea'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'67eca2ce-9d88-44ca-bb17-615f3199415c'::uuid,'MARINES',NULL,true,now(),now()), + ('82282efb-7fef-4a6d-a260-a18d2f21fa8d'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'149c3a94-abb1-4af0-aabf-3af019d5e243'::uuid,'MARINES',NULL,true,now(),now()), + ('3bdad313-d414-4842-8e6e-3675e20d78eb'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'87c09b47-058e-47ea-9684-37a8ac1f7120'::uuid,'MARINES',NULL,true,now(),now()), + ('db80b591-045d-4907-9b77-6694fe34e3ed'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'0ba81e85-f175-435a-a7c2-a22d0c44cc7b'::uuid,'MARINES',NULL,true,now(),now()), + ('05970a8a-28aa-454f-a28e-31327a2415dd'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'946256dc-1572-4201-b5ff-464da876f5ff'::uuid,'MARINES',NULL,true,now(),now()), + ('c1b3e0be-0463-4dfc-ab22-016037b41a05'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'5d381518-6beb-422e-8c57-4682b87ff1fe'::uuid,'MARINES',NULL,true,now(),now()), + ('1a92f4b0-4060-4f1b-9b45-b3fc47c5f08d'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'b07fb701-2c9b-4847-a459-76b1f47aa872'::uuid,'MARINES',NULL,true,now(),now()), + ('73794c53-a915-41af-b93d-5ada2e174409'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'b6c51584-f3e8-4c9e-9bdf-f7cac0433319'::uuid,'MARINES',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 + ('c03994c2-9e2a-4888-a5c2-c81ee05eba31'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'6097d01d-78ef-40d5-8699-7f8a8e48f4e7'::uuid,'MARINES',NULL,true,now(),now()), + ('ae6a55fd-2171-425a-9716-56d3fb9452e3'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'e48a3602-7fdd-4527-a8a7-f244fb331228'::uuid,'MARINES',NULL,true,now(),now()), + ('f052acf4-7d4a-4061-a2f4-ba19ff17ec4d'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'df47ef29-e902-4bd3-a32d-88d6187399b3'::uuid,'MARINES',NULL,true,now(),now()), + ('10b47428-bef7-4cfe-8564-2ccb654d514a'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'0f4eddf9-b727-4725-848e-3d9329553823'::uuid,'MARINES',NULL,true,now(),now()), + ('4a547501-6aae-4886-be5c-e5a0fad05441'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'d16ebe80-2195-48cf-ba61-b5efb8212754'::uuid,'MARINES',NULL,true,now(),now()), + ('d99cbe3a-84a0-4a2c-8e05-41ce066570ea'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'093268d0-597b-40bd-882a-8f385480bc68'::uuid,'MARINES',NULL,true,now(),now()), + ('ad09f13c-9dd9-468b-ab92-ad3e2d77c905'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'edb8b022-9534-44e7-87ea-e93f5451f057'::uuid,'MARINES',NULL,true,now(),now()), + ('362482c6-7770-49f4-86c9-89e2990e5345'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'4c62bb35-4d2a-4995-9c4f-8c665f2f6d3e'::uuid,'MARINES',NULL,true,now(),now()), + ('45994e2f-1aa4-4aa1-8631-a347a4463bc2'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'58ccfcc5-ded6-4f91-8cb7-8687bc56c4c6'::uuid,'MARINES',NULL,true,now(),now()), + ('b4fd2bf2-05b2-4429-afcb-ebbae512fd2b'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'dbc839c6-7b56-45b0-ab36-a0e77b0d538c'::uuid,'MARINES',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 + ('5aa5f596-a9bb-4f37-af0b-6e0cfbfbc711'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'c77e434a-1bf9-44c6-92aa-d377d72d1d44'::uuid,'MARINES',NULL,true,now(),now()), + ('c7bc79d4-525e-496d-93bd-2ea76b32baf4'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'89533190-e613-4c36-99df-7ee3871bb071'::uuid,'MARINES',NULL,true,now(),now()), + ('5bc99ccb-7010-4d2f-99a0-9277eda982ba'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'c86cb50a-99f6-41ed-8e9d-f096bd5cadca'::uuid,'MARINES',NULL,true,now(),now()), + ('dc97dd55-213b-4212-8c1a-aaee7b92fc55'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'b65bd7c4-6251-4682-aa07-77f761c363af'::uuid,'MARINES',NULL,true,now(),now()), + ('3a4daddc-a377-440f-bb5f-d33024374e3e'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'88df8618-798a-4a12-9b14-a7267a6cfd7f'::uuid,'MARINES',NULL,true,now(),now()), + ('e729c30a-0bec-424a-919a-45cbe31998e9'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'15894c4f-eb1a-4b6b-8b3f-e3abc4eeee8d'::uuid,'MARINES',NULL,true,now(),now()), + ('74b6df4f-08ea-48fb-9628-15c3f47f3a27'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'83fd3eb7-6269-46e2-84e1-f6180f40b9e8'::uuid,'MARINES',NULL,true,now(),now()), + ('643112a7-71a9-41d1-97f6-92aee969478a'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'b0f95e07-e3d1-4403-a402-50be9a542cf9'::uuid,'MARINES',NULL,true,now(),now()), + ('73b35029-077b-4d30-8f5a-34c3785f6e96'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'cef18ff2-8f48-47c0-8062-a40f9dec641c'::uuid,'MARINES',NULL,true,now(),now()), + ('9d8862ec-4358-497a-8154-65a83c676261'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'15eefe71-55ae-40b0-8bb5-f1952fcf45c8'::uuid,'MARINES',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 + ('8d5591d4-fcc4-44a9-babd-b575672ad6a9'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'d3c5d0a7-0477-44f6-8279-f6b9fa7b3436'::uuid,'MARINES',NULL,true,now(),now()), + ('c1057fc7-1c8b-44e2-a175-131ff0d7429f'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'8b89e7d7-cb36-40bd-ba14-699f1e4b1806'::uuid,'MARINES',NULL,true,now(),now()), + ('ae7949e2-9504-4874-92be-98460e8126da'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'5b833661-8fda-4f2a-9f2b-e1f4c21749a4'::uuid,'MARINES',NULL,true,now(),now()), + ('acf2cacd-0d4f-4de1-afdc-2eb1e72eeb80'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'38bed277-7990-460d-ad27-a69559638f42'::uuid,'MARINES',NULL,true,now(),now()), + ('c66c3d59-def9-4e08-8bee-5b2bacfc5cfd'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'67081e66-7e84-4bac-9c31-aa3e45bbba23'::uuid,'MARINES',NULL,true,now(),now()), + ('1f8cd63a-8dfe-4357-9ac5-2bef71f9d564'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'99e3f894-595f-403a-83f5-f7e035dd1f20'::uuid,'MARINES',NULL,true,now(),now()), + ('3e8030da-8316-4330-abea-35452e39fa61'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'1c0a5988-492f-4bc2-8409-9a143a494248'::uuid,'MARINES',NULL,true,now(),now()), + ('ce3c740a-b0a2-4a55-9abe-c2426fb8d821'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'63e10b54-5348-4362-932e-6613a8db4d42'::uuid,'MARINES',NULL,true,now(),now()), + ('9e913f22-287f-4e42-9544-46d9c6741db7'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'437e9930-0625-4448-938d-ba93a0a98ab5'::uuid,'MARINES',NULL,true,now(),now()), + ('d0ae21fe-07c0-40ee-9aa3-877d6d6a6bb9'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'87d3cb47-1026-4f42-bff6-899ff0fa7660'::uuid,'MARINES',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 + ('6f8d7c90-7682-41b7-b5cc-9d4aeb2e22ef'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'0115586e-be8c-4808-b65a-d417fad19238'::uuid,'MARINES',NULL,true,now(),now()), + ('cf7f39c0-8f6b-4598-85db-f465241e66f4'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'6638a77d-4b91-48f6-8241-c87fbdddacd1'::uuid,'MARINES',NULL,true,now(),now()), + ('296a8951-19b1-4868-9937-aef61bb73106'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'97c16bc3-e174-410b-9a09-a0db31420dbc'::uuid,'MARINES',NULL,true,now(),now()), + ('cdcec10e-5300-443f-9aae-7d8ce07142b0'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'3c6c5e35-1281-45fc-83ee-e6d656e155b6'::uuid,'MARINES',NULL,true,now(),now()), + ('e638701f-f8fc-48c0-b2e0-db134b9ece1f'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'6af248be-e5a8-49e9-a9e2-516748279ab5'::uuid,'MARINES',NULL,true,now(),now()), + ('ed4f4905-b1cf-4e57-beac-bc0a2d167c71'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'3580abe3-84da-4b46-af7b-d4379e6cff46'::uuid,'MARINES',NULL,true,now(),now()), + ('2a8b16c9-99f8-43ca-a759-c4884f8f7b24'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'f3bb397e-04e6-4b37-9bf3-b3ebab79a9b6'::uuid,'MARINES',NULL,true,now(),now()), + ('70530105-a4ab-4af3-ba02-9e4cf81237fa'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'f9bfe297-4ee0-4f76-a4bd-64b3a514af5d'::uuid,'MARINES',NULL,true,now(),now()), + ('61e17c04-ed7b-4da9-816a-1b6343086d94'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'70f11a71-667b-422d-ae37-8f25539f7782'::uuid,'MARINES',NULL,true,now(),now()), + ('19d5158f-96a6-48ef-8dd9-b831c582c9c4'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'baf66a9a-3c8a-49e7-83ea-841b9960e184'::uuid,'MARINES',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 + ('91043072-657f-4b2a-b5d1-42d8f6a7ba38'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'e96d46b1-3ab7-4b29-b798-ec3b728dd6a1'::uuid,'MARINES',NULL,true,now(),now()), + ('a3dc835e-3989-4476-b560-006745a884bc'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'e4171b5b-d26c-43b3-b41c-68b43bcbb079'::uuid,'MARINES',NULL,true,now(),now()), + ('0cf1dc14-0c9a-4262-9cab-db73c64f6e36'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'a0651bec-1258-4e36-9c76-155247e42c0a'::uuid,'MARINES',NULL,true,now(),now()), + ('e03b53cb-386f-4e1e-ac93-cd1a9260c6b4'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'737a8e63-af19-4902-a4b5-8f80e2268e4b'::uuid,'MARINES',NULL,true,now(),now()), + ('8b3f1142-f6d4-4c2d-9e75-9ab3568742f7'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'870df26e-2c50-4512-aa2e-61094cfbc3e1'::uuid,'MARINES',NULL,true,now(),now()), + ('5f5383b8-f29e-4b9f-8798-99575440c888'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'09dc6547-d346-40c3-93fb-fb7be1fa1b3e'::uuid,'MARINES',NULL,true,now(),now()), + ('1f7a198c-5f62-4f8c-bf91-19d7cdef9bae'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'43d7d081-bb32-4544-84f1-419fe0cb76e1'::uuid,'MARINES',NULL,true,now(),now()), + ('56c17bab-a124-4710-a047-0d67d30a9610'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'d7e57942-9c83-4138-baa0-70e8b5f08598'::uuid,'MARINES',NULL,true,now(),now()), + ('aad1440d-acaf-4ba8-9564-da97ab9ba651'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'c2f06691-5989-41a3-a848-19c9f0fec5df'::uuid,'MARINES',NULL,true,now(),now()), + ('6cc24224-2298-4023-888f-5e624e585171'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'5263a9ed-ff4d-42cc-91d5-dbdefeef54d1'::uuid,'MARINES',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 + ('88cd8184-7e3a-48cf-bb72-a9e1c3666cc4'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'54ab3a49-6d78-4922-bac1-94a722b9859a'::uuid,'MARINES',NULL,true,now(),now()), + ('11fdac1c-7ae9-49ea-bee4-90d4582f7c6d'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'3b6fe2e9-9116-4a70-8eef-96205205b0e3'::uuid,'MARINES',NULL,true,now(),now()), + ('83c88ffb-5182-42b3-93f4-635556d8caaf'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'ba0017c8-5d48-4efe-8802-361d2f2bc16d'::uuid,'MARINES',NULL,true,now(),now()), + ('802f7a5f-8ce3-4c40-975a-83cf0ec502fc'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'af65234a-8577-4f6d-a346-7d486e963287'::uuid,'MARINES',NULL,true,now(),now()), + ('5197eed4-f6ae-4f70-b640-b67714b73f87'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'25f62695-ba9b-40c3-a7e4-0c078731123d'::uuid,'MARINES',NULL,true,now(),now()), + ('50d675ab-4064-4edb-8f1d-5739b0318ed9'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'b0422b13-4afe-443e-901f-fe652cde23a4'::uuid,'MARINES',NULL,true,now(),now()), + ('a3733567-2b57-4f12-9390-967e04bc1453'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'ecbc1d89-9cf6-4f52-b453-3ba473a0ff4e'::uuid,'MARINES',NULL,true,now(),now()), + ('6a321494-cdd8-4372-90a1-6a6c67f4e220'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'4e85cb86-e9dd-4c7c-9677-e0a327ac895c'::uuid,'MARINES',NULL,true,now(),now()), + ('99d37d3c-be31-4d3e-9c5e-c9d816a46014'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'7f9dd10d-100e-4252-9786-706349f456ca'::uuid,'MARINES',NULL,true,now(),now()), + ('e9b4c884-ad7f-4c1a-8815-05353834f5c3'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'5451f8d7-60c5-4e22-bbf6-d9af8e6ace54'::uuid,'MARINES',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 + ('cfaa046e-6be5-4178-a451-d368317ecb86'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'9e9cba85-7c39-4836-809d-70b54baf392e'::uuid,'MARINES',NULL,true,now(),now()), + ('9b509efb-12d9-42aa-a85a-ffb026866b56'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'f7470914-cf48-43be-b431-a3ca2fe5b290'::uuid,'MARINES',NULL,true,now(),now()), + ('7e54d995-b7bd-457c-bd97-0fd76891402e'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'5a0d3cc1-b866-4bde-b67f-78d565facf3e'::uuid,'MARINES',NULL,true,now(),now()), + ('1357aadd-b420-42c4-8a39-bab8027fa910'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'e629b95a-ec5b-4fc4-897f-e0d1050e1ec6'::uuid,'MARINES',NULL,true,now(),now()), + ('56fbbf0f-e819-41dd-802d-4e677aecd1c9'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'d68a626f-935a-4eb1-ba9b-6829feeff91c'::uuid,'MARINES',NULL,true,now(),now()), + ('bfc2dc53-896f-4f29-92c7-9a7a392b22f2'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'ed496f6e-fd34-48d1-8586-63a1d305c49c'::uuid,'MARINES',NULL,true,now(),now()), + ('d015e8b1-86ea-489f-979d-458ae35ae8d6'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'153b62b2-b1b8-4b9d-afa5-53df4150aba4'::uuid,'MARINES',NULL,true,now(),now()), + ('1f32e712-8b5e-4ae4-b409-c5c92337aed8'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'0020441b-bc0c-436e-be05-b997ca6a853c'::uuid,'MARINES',NULL,true,now(),now()), + ('a8a85e00-e657-41a2-8f32-84bdd9c92ec8'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'a8ae9bb9-e9ac-49b4-81dc-336b8a0dcb54'::uuid,'MARINES',NULL,true,now(),now()), + ('4ad1a57f-0e9e-4405-9a08-0ffa211fc8ce'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'1aa43046-8d6b-4249-88dc-b259d86c0cb8'::uuid,'MARINES',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 + ('dd765820-ffa5-4673-a347-fbe3464cd2d8'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'84b50723-95fc-41d1-8115-a734c7e53f66'::uuid,'MARINES',NULL,true,now(),now()), + ('6eff7586-59bd-4638-809b-5cc346646dc9'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'63dc3f78-235e-4b1c-b1db-459d7f5ae25f'::uuid,'MARINES',NULL,true,now(),now()), + ('428b0d7a-3848-4882-a5cc-80d5ae3500d6'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'4bd4c579-c163-4b1b-925a-d852d5a12642'::uuid,'MARINES',NULL,true,now(),now()), + ('9f8311f3-e191-4383-8fb8-2b58cd545dd4'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'ce7f1fc9-5b94-43cb-b398-b31cb6350d6a'::uuid,'MARINES',NULL,true,now(),now()), + ('81e3e6fe-7db3-49ff-b18c-8b078e3d129e'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'4a6a3260-e1ec-4d78-8c07-d89f1405ca16'::uuid,'MARINES',NULL,true,now(),now()), + ('4cf1c40f-60f0-4a47-a351-471720ba0fd3'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'658274b5-720c-4a70-ba9d-249614d85ebc'::uuid,'MARINES',NULL,true,now(),now()), + ('0754978b-d11e-4f2c-b59c-3252d2735b26'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'41f0736c-6d26-4e93-9668-860e9f0e222a'::uuid,'MARINES',NULL,true,now(),now()), + ('c9b8305d-2e16-46a7-9b7c-b3edeb6f8e93'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'c2a8e8c3-dddc-4c0f-a23a-0b4e2c20af0d'::uuid,'MARINES',NULL,true,now(),now()), + ('a8768e6d-1a6d-449a-9f2e-2e198dcd6e00'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'0f1e1c87-0497-4ee2-970d-21ac2d2155db'::uuid,'MARINES',NULL,true,now(),now()), + ('cf292129-d543-4632-9cb3-b074279e42be'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'e40b411d-55f0-4470-83d0-0bbe11fa77dd'::uuid,'MARINES',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 + ('3f07cedf-ad90-465e-95a5-ce44a2f088b8'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'2632b4e5-c6cb-4e64-8924-0b7e4b1115ec'::uuid,'MARINES',NULL,true,now(),now()), + ('42a2d93b-9dea-4c63-b0a7-c39364aacf75'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'1336deb9-5c87-409d-8051-4ab9f211eb29'::uuid,'MARINES',NULL,true,now(),now()), + ('64fe0d14-98f7-4f73-9aa3-a19617b2d8c3'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'7a6d3b5b-81a6-4db5-b2ab-ecbfd6bd7941'::uuid,'MARINES',NULL,true,now(),now()), + ('3ab04740-9f13-47f8-a80e-b63ab5b67590'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'91b69254-5976-4839-a31d-972e9958d9cf'::uuid,'MARINES',NULL,true,now(),now()), + ('3be8e483-6bce-4a7d-a3bd-fc1485e79818'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'166f3629-79b9-451a-90a3-c43680929a2f'::uuid,'MARINES',NULL,true,now(),now()), + ('ddf69dcb-345e-47c1-a585-ce24d0854de5'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'4fe05eb4-1b1c-4d4a-a185-0b039ac64835'::uuid,'MARINES',NULL,true,now(),now()), + ('fe9e365f-98a5-4658-a58d-5f8279ff3e5a'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'ca8aecda-4642-45c7-96ed-309c35c4b78f'::uuid,'MARINES',NULL,true,now(),now()), + ('2051a441-f4d0-4b6e-8614-74761de505e6'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'e4bc9404-5466-4a41-993e-09474266afc3'::uuid,'MARINES',NULL,true,now(),now()), + ('6797daed-f002-431c-829b-dab7c1b16ff2'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'d4a51d90-3945-4ad3-9cba-a18d8d7b34d7'::uuid,'MARINES',NULL,true,now(),now()), + ('2398bf11-1986-4914-8e47-6afac423283a'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'a5a60d63-d9a8-4bde-9081-f011784b2d31'::uuid,'MARINES',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 + ('f3a6c247-4c4c-4f45-9162-50307d4711f5'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'93842d74-1f3e-46cd-aca9-9f0dafbd20a1'::uuid,'MARINES',NULL,true,now(),now()), + ('0a82b196-7c24-4214-9155-05f0c5c2d7e9'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'1bc0dbda-f0ce-4b76-a551-78dbaaa9e3ec'::uuid,'MARINES',NULL,true,now(),now()), + ('af0a2db3-2e2e-4a78-804b-9a8b89b96e12'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'f1a7ef90-cfa6-4e0c-92c3-f8d70c07ba4d'::uuid,'MARINES',NULL,true,now(),now()), + ('5205a965-d424-469a-9526-17ef551685e6'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'9c5b4c4d-e05c-42ca-bd77-b61f5d8c7afc'::uuid,'MARINES',NULL,true,now(),now()), + ('f263fdb0-933b-42a7-925e-a9852c5804fa'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'27ec2576-78dd-4605-b1a8-0b9ca207fc26'::uuid,'MARINES',NULL,true,now(),now()), + ('b03c7ae3-4d6d-445c-b94a-73af723e5226'::uuid,'85b324ae-1a0d-4f7d-971f-ea509dcc73d7'::uuid,'12396ebc-59e9-430a-8475-759a38af6b7a'::uuid,'MARINES',NULL,true,now(),now()); + + +drop view move_to_gbloc; +CREATE OR REPLACE VIEW move_to_gbloc AS +SELECT move_id, gbloc FROM ( + SELECT DISTINCT ON (sh.move_id) sh.move_id, s.affiliation, + COALESCE(pctg.gbloc, coalesce(pctg_oconus_bos.gbloc, coalesce(pctg_oconus.gbloc, pctg_ppm.gbloc))) AS gbloc + FROM mto_shipments sh + JOIN moves m ON sh.move_id = m.id + JOIN orders o on m.orders_id = o.id + JOIN service_members s on o.service_member_id = s.id + LEFT JOIN ( SELECT a.id AS address_id, + pctg_1.gbloc, pctg_1.postal_code + FROM addresses a + JOIN postal_code_to_gblocs pctg_1 ON a.postal_code::text = pctg_1.postal_code::text) pctg ON pctg.address_id = sh.pickup_address_id + LEFT JOIN ( SELECT ppm.shipment_id, + pctg_1.gbloc + FROM ppm_shipments ppm + JOIN addresses ppm_address ON ppm.pickup_postal_address_id = ppm_address.id + JOIN postal_code_to_gblocs pctg_1 ON ppm_address.postal_code::text = pctg_1.postal_code::text) pctg_ppm ON pctg_ppm.shipment_id = sh.id + LEFT JOIN ( SELECT a.id AS address_id, + cast(jr.code as varchar) AS gbloc, ga.department_indicator + FROM addresses a + JOIN re_oconus_rate_areas ora ON a.us_post_region_cities_id = ora.us_post_region_cities_id + JOIN gbloc_aors ga ON ora.id = ga.oconus_rate_area_id + JOIN jppso_regions jr ON ga.jppso_regions_id = jr.id + ) pctg_oconus_bos ON pctg_oconus_bos.address_id = sh.pickup_address_id + and case when s.affiliation = 'AIR_FORCE' THEN 'AIR_AND_SPACE_FORCE' + when s.affiliation = 'SPACE_FORCE' THEN 'AIR_AND_SPACE_FORCE' + else s.affiliation + end = pctg_oconus_bos.department_indicator + LEFT JOIN ( SELECT a.id AS address_id, + cast(pctg_1.code as varchar) AS gbloc, ga.department_indicator + FROM addresses a + JOIN re_oconus_rate_areas ora ON a.us_post_region_cities_id = ora.us_post_region_cities_id + JOIN gbloc_aors ga ON ora.id = ga.oconus_rate_area_id + JOIN jppso_regions pctg_1 ON ga.jppso_regions_id = pctg_1.id + ) pctg_oconus ON pctg_oconus.address_id = sh.pickup_address_id and pctg_oconus.department_indicator is null + WHERE sh.deleted_at IS NULL + ORDER BY sh.move_id, sh.created_at) as m; + + +-- used for the destination queue +CREATE OR REPLACE VIEW move_to_dest_gbloc +AS +SELECT distinct move_id, gbloc FROM ( + SELECT sh.move_id, s.affiliation, + COALESCE(case when s.affiliation = 'MARINES' then 'USMC' else pctg.gbloc end, coalesce(pctg_oconus_bos.gbloc, coalesce(pctg_oconus.gbloc, pctg_ppm.gbloc))) AS gbloc + FROM mto_shipments sh + JOIN moves m ON sh.move_id = m.id + JOIN orders o on m.orders_id = o.id + JOIN service_members s on o.service_member_id = s.id + LEFT JOIN ( SELECT a.id AS address_id, + pctg_1.gbloc, pctg_1.postal_code + FROM addresses a + JOIN postal_code_to_gblocs pctg_1 ON a.postal_code::text = pctg_1.postal_code::text) pctg ON pctg.address_id = sh.destination_address_id + LEFT JOIN ( SELECT ppm.shipment_id, + pctg_1.gbloc + FROM ppm_shipments ppm + JOIN addresses ppm_address ON ppm.destination_postal_address_id = ppm_address.id + JOIN postal_code_to_gblocs pctg_1 ON ppm_address.postal_code::text = pctg_1.postal_code::text) pctg_ppm ON pctg_ppm.shipment_id = sh.id + LEFT JOIN ( SELECT a.id AS address_id, + cast(jr.code as varchar) AS gbloc, ga.department_indicator + FROM addresses a + JOIN re_oconus_rate_areas ora ON a.us_post_region_cities_id = ora.us_post_region_cities_id + JOIN gbloc_aors ga ON ora.id = ga.oconus_rate_area_id + JOIN jppso_regions jr ON ga.jppso_regions_id = jr.id + ) pctg_oconus_bos ON pctg_oconus_bos.address_id = sh.destination_address_id + and case when s.affiliation = 'AIR_FORCE' THEN 'AIR_AND_SPACE_FORCE' + when s.affiliation = 'SPACE_FORCE' THEN 'AIR_AND_SPACE_FORCE' + else s.affiliation + end = pctg_oconus_bos.department_indicator + LEFT JOIN ( SELECT a.id AS address_id, + cast(pctg_1.code as varchar) AS gbloc, ga.department_indicator + FROM addresses a + JOIN re_oconus_rate_areas ora ON a.us_post_region_cities_id = ora.us_post_region_cities_id + JOIN gbloc_aors ga ON ora.id = ga.oconus_rate_area_id + JOIN jppso_regions pctg_1 ON ga.jppso_regions_id = pctg_1.id + ) pctg_oconus ON pctg_oconus.address_id = sh.destination_address_id and pctg_oconus.department_indicator is null + WHERE sh.deleted_at IS NULL) as m; + +-- database function that returns a list of moves that have destination requests +-- this includes shipment address update requests, destination SIT, & destination shuttle +CREATE OR REPLACE FUNCTION get_destination_queue( + user_gbloc TEXT DEFAULT NULL, + customer_name TEXT DEFAULT NULL, + edipi TEXT DEFAULT NULL, + emplid TEXT DEFAULT NULL, + m_status TEXT[] DEFAULT NULL, + move_code TEXT DEFAULT NULL, + requested_move_date TIMESTAMP DEFAULT NULL, + date_submitted TIMESTAMP DEFAULT NULL, + branch TEXT DEFAULT NULL, + origin_duty_location TEXT DEFAULT NULL, + counseling_office TEXT DEFAULT NULL, + too_assigned_user TEXT DEFAULT NULL, + page INTEGER DEFAULT 1, + per_page INTEGER DEFAULT 20, + sort TEXT DEFAULT NULL, + sort_direction TEXT DEFAULT NULL +) +RETURNS TABLE ( + id UUID, + show BOOLEAN, + locator TEXT, + submitted_at TIMESTAMP WITH TIME ZONE, + orders_id UUID, + status TEXT, + locked_by UUID, + too_assigned_id UUID, + counseling_transportation_office_id UUID, + orders JSONB, + mto_shipments JSONB, + counseling_transportation_office JSONB, + too_assigned JSONB, + total_count BIGINT +) AS $$ +DECLARE + sql_query TEXT; + offset_value INTEGER; + sort_column TEXT; + sort_order TEXT; +BEGIN + IF page < 1 THEN + page := 1; + END IF; + + IF per_page < 1 THEN + per_page := 20; + END IF; + + -- OFFSET for pagination + offset_value := (page - 1) * per_page; + + sql_query := ' + SELECT + moves.id AS id, + moves.show AS show, + moves.locator::TEXT AS locator, + moves.submitted_at::TIMESTAMP WITH TIME ZONE AS submitted_at, + moves.orders_id AS orders_id, + moves.status::TEXT AS status, + moves.locked_by AS locked_by, + moves.too_assigned_id AS too_assigned_id, + moves.counseling_transportation_office_id AS counseling_transportation_office_id, + json_build_object( + ''id'', orders.id, + ''origin_duty_location_gbloc'', orders.gbloc, + ''service_member'', json_build_object( + ''id'', service_members.id, + ''first_name'', service_members.first_name, + ''last_name'', service_members.last_name, + ''edipi'', service_members.edipi, + ''emplid'', service_members.emplid, + ''affiliation'', service_members.affiliation + ), + ''origin_duty_location'', json_build_object( + ''name'', origin_duty_locations.name + ) + )::JSONB AS orders, + COALESCE( + ( + SELECT json_agg( + json_build_object( + ''id'', ms.id, + ''shipment_type'', ms.shipment_type, + ''status'', ms.status, + ''requested_pickup_date'', TO_CHAR(ms.requested_pickup_date, ''YYYY-MM-DD"T00:00:00Z"''), + ''scheduled_pickup_date'', TO_CHAR(ms.scheduled_pickup_date, ''YYYY-MM-DD"T00:00:00Z"''), + ''approved_date'', TO_CHAR(ms.approved_date, ''YYYY-MM-DD"T00:00:00Z"''), + ''prime_estimated_weight'', ms.prime_estimated_weight + ) + ) + FROM ( + SELECT DISTINCT ON (mto_shipments.id) mto_shipments.* + FROM mto_shipments + WHERE mto_shipments.move_id = moves.id + ) AS ms + ), + ''[]'' + )::JSONB AS mto_shipments, + json_build_object( + ''name'', counseling_offices.name + )::JSONB AS counseling_transportation_office, + json_build_object( + ''first_name'', too_user.first_name, + ''last_name'', too_user.last_name + )::JSONB AS too_assigned, + COUNT(*) OVER() AS total_count + FROM moves + JOIN orders ON moves.orders_id = orders.id + JOIN mto_shipments ON mto_shipments.move_id = moves.id + LEFT JOIN ppm_shipments ON ppm_shipments.shipment_id = mto_shipments.id + JOIN mto_service_items ON mto_shipments.id = mto_service_items.mto_shipment_id + JOIN re_services ON mto_service_items.re_service_id = re_services.id + JOIN service_members ON orders.service_member_id = service_members.id + JOIN duty_locations AS new_duty_locations ON orders.new_duty_location_id = new_duty_locations.id + JOIN duty_locations AS origin_duty_locations ON orders.origin_duty_location_id = origin_duty_locations.id + LEFT JOIN office_users AS too_user ON moves.too_assigned_id = too_user.id + LEFT JOIN office_users AS locked_user ON moves.locked_by = locked_user.id + LEFT JOIN transportation_offices AS counseling_offices + ON moves.counseling_transportation_office_id = counseling_offices.id + LEFT JOIN shipment_address_updates ON shipment_address_updates.shipment_id = mto_shipments.id + JOIN move_to_dest_gbloc ON move_to_dest_gbloc.move_id = moves.id + WHERE moves.show = TRUE + '; + + IF user_gbloc IS NOT NULL THEN + sql_query := sql_query || ' AND move_to_dest_gbloc.gbloc = $1 '; + END IF; + + IF customer_name IS NOT NULL THEN + sql_query := sql_query || ' AND ( + service_members.first_name || '' '' || service_members.last_name ILIKE ''%'' || $2 || ''%'' + OR service_members.last_name || '' '' || service_members.first_name ILIKE ''%'' || $2 || ''%'' + )'; + END IF; + + IF edipi IS NOT NULL THEN + sql_query := sql_query || ' AND service_members.edipi ILIKE ''%'' || $3 || ''%'' '; + END IF; + + IF emplid IS NOT NULL THEN + sql_query := sql_query || ' AND service_members.emplid ILIKE ''%'' || $4 || ''%'' '; + END IF; + + IF m_status IS NOT NULL THEN + sql_query := sql_query || ' AND moves.status = ANY($5) '; + END IF; + + IF move_code IS NOT NULL THEN + sql_query := sql_query || ' AND moves.locator ILIKE ''%'' || $6 || ''%'' '; + END IF; + + IF requested_move_date IS NOT NULL THEN + sql_query := sql_query || ' AND ( + mto_shipments.requested_pickup_date::DATE = $7::DATE + OR ppm_shipments.expected_departure_date::DATE = $7::DATE + OR (mto_shipments.shipment_type = ''HHG_OUTOF_NTS'' AND mto_shipments.requested_delivery_date::DATE = $7::DATE) + )'; + END IF; + + IF date_submitted IS NOT NULL THEN + sql_query := sql_query || ' AND moves.submitted_at::DATE = $8::DATE '; + END IF; + + IF branch IS NOT NULL THEN + sql_query := sql_query || ' AND service_members.affiliation ILIKE ''%'' || $9 || ''%'' '; + END IF; + + IF origin_duty_location IS NOT NULL THEN + sql_query := sql_query || ' AND origin_duty_locations.name ILIKE ''%'' || $10 || ''%'' '; + END IF; + + IF counseling_office IS NOT NULL THEN + sql_query := sql_query || ' AND counseling_offices.name ILIKE ''%'' || $11 || ''%'' '; + END IF; + + IF too_assigned_user IS NOT NULL THEN + sql_query := sql_query || ' AND (too_user.first_name || '' '' || too_user.last_name) ILIKE ''%'' || $12 || ''%'' '; + END IF; + + -- add destination queue-specific filters (pending dest address requests, dest SIT & dest shuttle service items) + sql_query := sql_query || ' + AND ( + shipment_address_updates.status = ''REQUESTED'' + OR ( + mto_service_items.status = ''SUBMITTED'' + AND re_services.code IN (''DDFSIT'', ''DDASIT'', ''DDDSIT'', ''DDSHUT'', ''DDSFSC'', ''IDFSIT'', ''IDASIT'', ''IDDSIT'', ''IDSHUT'') + ) + ) + '; + + -- default sorting values if none are provided (move.id) + sort_column := 'id'; + sort_order := 'ASC'; + + IF sort IS NOT NULL THEN + CASE sort + WHEN 'locator' THEN sort_column := 'moves.locator'; + WHEN 'status' THEN sort_column := 'moves.status'; + WHEN 'customerName' THEN sort_column := 'service_members.last_name'; + WHEN 'edipi' THEN sort_column := 'service_members.edipi'; + WHEN 'emplid' THEN sort_column := 'service_members.emplid'; + WHEN 'requestedMoveDate' THEN sort_column := 'COALESCE(mto_shipments.requested_pickup_date, ppm_shipments.expected_departure_date, mto_shipments.requested_delivery_date)'; + WHEN 'appearedInTooAt' THEN sort_column := 'COALESCE(moves.submitted_at, moves.approvals_requested_at)'; + WHEN 'branch' THEN sort_column := 'service_members.affiliation'; + WHEN 'originDutyLocation' THEN sort_column := 'origin_duty_locations.name'; + WHEN 'counselingOffice' THEN sort_column := 'counseling_offices.name'; + WHEN 'assignedTo' THEN sort_column := 'too_user.last_name'; + ELSE + sort_column := 'moves.id'; + END CASE; + END IF; + + IF sort_direction IS NOT NULL THEN + IF LOWER(sort_direction) = 'desc' THEN + sort_order := 'DESC'; + ELSE + sort_order := 'ASC'; + END IF; + END IF; + + sql_query := sql_query || ' + GROUP BY + moves.id, + moves.show, + moves.locator, + moves.submitted_at, + moves.orders_id, + moves.status, + moves.locked_by, + moves.too_assigned_id, + moves.counseling_transportation_office_id, + mto_shipments.requested_pickup_date, + mto_shipments.requested_delivery_date, + ppm_shipments.expected_departure_date, + orders.id, + service_members.id, + service_members.first_name, + service_members.last_name, + service_members.edipi, + service_members.emplid, + service_members.affiliation, + origin_duty_locations.name, + counseling_offices.name, + too_user.first_name, + too_user.last_name'; + sql_query := sql_query || format(' ORDER BY %s %s ', sort_column, sort_order); + sql_query := sql_query || ' LIMIT $13 OFFSET $14 '; + + RETURN QUERY EXECUTE sql_query + USING user_gbloc, customer_name, edipi, emplid, m_status, move_code, requested_move_date, date_submitted, + branch, origin_duty_location, counseling_office, too_assigned_user, per_page, offset_value; + +END; +$$ LANGUAGE plpgsql; diff --git a/migrations/app/schema/20250123210535_update_re_intl_transit_times_for_ak_hhg.up.sql b/migrations/app/schema/20250123210535_update_re_intl_transit_times_for_ak_hhg.up.sql new file mode 100644 index 00000000000..fb67d5fee8b --- /dev/null +++ b/migrations/app/schema/20250123210535_update_re_intl_transit_times_for_ak_hhg.up.sql @@ -0,0 +1,9 @@ +UPDATE re_intl_transit_times + SET hhg_transit_time = 10 +WHERE origin_rate_area_id IN ('b80a00d4-f829-4051-961a-b8945c62c37d','5a27e806-21d4-4672-aa5e-29518f10c0aa') + OR destination_rate_area_id IN ('b80a00d4-f829-4051-961a-b8945c62c37d','5a27e806-21d4-4672-aa5e-29518f10c0aa'); + +update re_intl_transit_times + SET hhg_transit_time = 20 +WHERE origin_rate_area_id IN ('9bb87311-1b29-4f29-8561-8a4c795654d4','635e4b79-342c-4cfc-8069-39c408a2decd') + OR destination_rate_area_id IN ('9bb87311-1b29-4f29-8561-8a4c795654d4','635e4b79-342c-4cfc-8069-39c408a2decd'); \ No newline at end of file diff --git a/migrations/app/schema/20250127143137_insert_nsra_re_intl_transit_times.up.sql b/migrations/app/schema/20250127143137_insert_nsra_re_intl_transit_times.up.sql new file mode 100644 index 00000000000..5610ce0c537 --- /dev/null +++ b/migrations/app/schema/20250127143137_insert_nsra_re_intl_transit_times.up.sql @@ -0,0 +1,918 @@ +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('3e9cbd63-3911-4f58-92af-fd0413832d06','899d79f7-8623-4442-a398-002178cf5d94','7ac1c0ec-0903-477c-89e0-88efe9249c98',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('f25802c1-20dd-4170-9c45-ea8ebb5bc774','3ec11db4-f821-409f-84ad-07fc8e64d60d','433334c3-59dd-404d-a193-10dd4172fc8f',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('d73cfe42-eb9c-41ed-8673-36a9f5fa45eb','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','433334c3-59dd-404d-a193-10dd4172fc8f',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('c14182c5-f8b6-4289-a5bc-40773b0e81f3','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','4a366bb4-5104-45ea-ac9e-1da8e14387c3',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('5b3c1b64-ee8a-449c-a1ba-d74865367be4','7ee486f1-4de8-4700-922b-863168f612a0','40ab17b2-9e79-429c-a75d-b6fcbbe27901',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('c50c6383-66cb-4794-afa5-3e57ce17cecf','3ec11db4-f821-409f-84ad-07fc8e64d60d','f18133b7-ef83-4b2b-beff-9c3b5f99e55a',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('e01213e8-23b4-45ec-ac4a-c5d851e57b23','4a366bb4-5104-45ea-ac9e-1da8e14387c3','c68492e9-c7d9-4394-8695-15f018ce6b90',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('75bf18e7-7ba6-402a-bee2-c46cf085b2ce','58dcc836-51e1-4633-9a89-73ac44eb2152','01d0be5d-aaec-483d-a841-6ab1301aa9bd',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('a0d65c1e-6397-4820-b9da-872256047c09','4a366bb4-5104-45ea-ac9e-1da8e14387c3','b194b7a9-a759-4c12-9482-b99e43a52294',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('3c0e46ef-dd9a-429e-8860-1e1e063d78c4','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','2a1b3667-e604-41a0-b741-ba19f1f56892',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('d9323e3a-ef4a-45b5-a834-270d776cc537','899d79f7-8623-4442-a398-002178cf5d94','c4c73fcb-be11-4b1a-986a-a73451d402a7',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('c109fe79-9b18-4e18-b1ea-2fe21beea057','4a366bb4-5104-45ea-ac9e-1da8e14387c3','dd6c2ace-2593-445b-9569-55328090de99',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('a89c0100-7449-4b36-90e2-1da201025173','899d79f7-8623-4442-a398-002178cf5d94','f42c9e51-5b7e-4ab3-847d-fd86b4e90dc1',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('9ca4cd23-556d-4d63-8781-406c45bcf57e','3ec11db4-f821-409f-84ad-07fc8e64d60d','03dd5854-8bc3-4b56-986e-eac513cc1ec0',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('dc2fd4a2-e551-427f-958a-df213ec004e2','dd6c2ace-2593-445b-9569-55328090de99','47cbf0b7-e249-4b7e-8306-e5a2d2b3f394',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('4fa5279e-5519-4aae-a392-dad3822cd2f6','3ec11db4-f821-409f-84ad-07fc8e64d60d','19ddeb7f-91c1-4bd0-83ef-264eb78a3f75',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('ebd11c4f-48bc-4511-b3c2-c04f06e2f163','58dcc836-51e1-4633-9a89-73ac44eb2152','a761a482-2929-4345-8027-3c6258f0c8dd',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('53750e06-5ad1-4fb6-a777-9d3891b4c547','899d79f7-8623-4442-a398-002178cf5d94','9a9da923-06ef-47ea-bc20-23cc85b51ad0',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('da5b3486-a289-405c-905e-f941f6699789','7ee486f1-4de8-4700-922b-863168f612a0','e4e467f2-449d-46e3-a59b-0f8714e4824a',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('1561bcb3-3525-4a46-8490-eab8d8aae126','dd6c2ace-2593-445b-9569-55328090de99','0ba534f5-0d24-4d7c-9216-d07f57cd8edd',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('2563d17e-c30e-40e6-be55-72513cafc4f4','3ec11db4-f821-409f-84ad-07fc8e64d60d','ea0fa1cc-7d80-4bd9-989e-f119c33fb881',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('3184b918-5058-45f8-97c4-d657ed4e8c5a','4a366bb4-5104-45ea-ac9e-1da8e14387c3','649f665a-7624-4824-9cd5-b992462eb97b',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('8c8a6e27-3b7c-4ef5-a1f3-c69118a824ae','4a366bb4-5104-45ea-ac9e-1da8e14387c3','def8c7af-d4fc-474e-974d-6fd00c251da8',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('674ae9cb-8595-4a6c-9475-c8f35512c4cc','899d79f7-8623-4442-a398-002178cf5d94','8abaed50-eac1-4f40-83db-c07d2c3a123a',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('8f7c2bc8-5a44-4b4d-ab09-9ec6a9984713','4a366bb4-5104-45ea-ac9e-1da8e14387c3','e3071ca8-bedf-4eff-bda0-e9ff27f0e34c',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('6b9725f1-db9d-44c5-8341-c14d9a1bb7fc','58dcc836-51e1-4633-9a89-73ac44eb2152','6f0e02be-08ad-48b1-8e23-eecaab34b4fe',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('4ba240fd-671a-4ef9-adf2-cb4d43cd2117','899d79f7-8623-4442-a398-002178cf5d94','4a239fdb-9ad7-4bbb-8685-528f3f861992',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('21114480-370e-46d9-b78c-f78074f13b41','4a366bb4-5104-45ea-ac9e-1da8e14387c3','243e6e83-ff11-4a30-af30-8751e8e63bd4',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('a8bb885c-e18e-49c2-b89a-50e247d3ba08','4a366bb4-5104-45ea-ac9e-1da8e14387c3','a761a482-2929-4345-8027-3c6258f0c8dd',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('b104c59e-c30d-4308-acbc-f5a7352fdaeb','7ee486f1-4de8-4700-922b-863168f612a0','cae0eb53-a023-434c-ac8c-d0641067d8d8',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('8e22473c-e37b-4d0e-b8b5-63c8541a7da7','dd6c2ace-2593-445b-9569-55328090de99','2b1d1842-15f8-491a-bdce-e5f9fea947e7',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('a456d31b-2ffb-474e-b1df-03b0cfd309f6','3ec11db4-f821-409f-84ad-07fc8e64d60d','46c16bc1-df71-4c6f-835b-400c8caaf984',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('3b44c23d-b4c9-483d-b3fc-38e891f7b920','7ee486f1-4de8-4700-922b-863168f612a0','e5d41d36-b355-4407-9ede-cd435da69873',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('a1b1e333-3a10-4ed6-b72d-f0146716221a','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','a2fad63c-b6cb-4b0d-9ced-1a81a6bc9985',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('3e35759c-6e53-4d89-b524-2184f7bf6425','58dcc836-51e1-4633-9a89-73ac44eb2152','19ddeb7f-91c1-4bd0-83ef-264eb78a3f75',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('1ce5756b-ef50-4ee7-9e39-e6048c7b64d1','3ec11db4-f821-409f-84ad-07fc8e64d60d','2124fcbf-be89-4975-9cc7-263ac14ad759',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('388ab9f7-16bd-4ebe-b841-c267112c37fd','899d79f7-8623-4442-a398-002178cf5d94','811a32c0-90d6-4744-9a57-ab4130091754',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('0d81e85a-a3ea-4936-ab7b-74730c693e7b','4a366bb4-5104-45ea-ac9e-1da8e14387c3','71755cc7-0844-4523-a0ac-da9a1e743ad1',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('0bf6e7bc-3c66-4e57-88a8-b1d59be11da0','4a366bb4-5104-45ea-ac9e-1da8e14387c3','c4c73fcb-be11-4b1a-986a-a73451d402a7',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('7b847282-6cdd-479f-b593-821964c30de8','3ec11db4-f821-409f-84ad-07fc8e64d60d','ba215fd2-cdfc-4b98-bd78-cfa667b1b371',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('ad75c486-c7cc-472a-b0d5-b35a2eb2a1e6','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','10644589-71f6-4baf-ba1c-dfb19d924b25',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('24680406-0639-4e1f-841a-bb8e0340a8ed','dd6c2ace-2593-445b-9569-55328090de99','f42c9e51-5b7e-4ab3-847d-fd86b4e90dc1',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('458a1183-121b-4960-a567-a2cc6f4575e4','4a366bb4-5104-45ea-ac9e-1da8e14387c3','46c16bc1-df71-4c6f-835b-400c8caaf984',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('d75bc773-eda0-4b73-b79a-80197b544a45','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','f79dd433-2808-4f20-91ef-6b5efca07350',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('099acf9a-4591-42d5-b850-48a8dfdaa8a7','dd6c2ace-2593-445b-9569-55328090de99','71755cc7-0844-4523-a0ac-da9a1e743ad1',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('4623c04d-6486-465f-a2be-1822caf8dba5','7ee486f1-4de8-4700-922b-863168f612a0','2a1b3667-e604-41a0-b741-ba19f1f56892',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('3c969330-b127-4c3d-93cc-3c77b2a05f4f','4a366bb4-5104-45ea-ac9e-1da8e14387c3','829d8b45-19c1-49a3-920c-cc0ae14e8698',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('5c3f248c-0909-49c8-b4cf-0af2ff206f1e','dd6c2ace-2593-445b-9569-55328090de99','4fb560d1-6bf5-46b7-a047-d381a76c4fef',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('de8abafb-09f1-4301-afb5-59efa79d603c','899d79f7-8623-4442-a398-002178cf5d94','3ece4e86-d328-4206-9f81-ec62bdf55335',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('48ac52dd-66fc-4e01-8121-8311faae6a75','dd6c2ace-2593-445b-9569-55328090de99','098488af-82c9-49c6-9daa-879eff3d3bee',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('4a702eb6-2f38-4019-aa1e-4305ca2b97eb','3ec11db4-f821-409f-84ad-07fc8e64d60d','01d0be5d-aaec-483d-a841-6ab1301aa9bd',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('bd3aebb2-38bf-4b07-a345-75c97e7fb349','4a366bb4-5104-45ea-ac9e-1da8e14387c3','e337daba-5509-4507-be21-ca13ecaced9b',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('6fe72bd1-83e1-4881-9ac2-6d5220505324','dd6c2ace-2593-445b-9569-55328090de99','ba215fd2-cdfc-4b98-bd78-cfa667b1b371',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('c2a179df-8cd4-4a11-8bf3-1c0eaa05f007','899d79f7-8623-4442-a398-002178cf5d94','3733db73-602a-4402-8f94-36eec2fdab15',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('786c8104-c9bd-45d2-8ea7-d55a208084da','7ee486f1-4de8-4700-922b-863168f612a0','5a27e806-21d4-4672-aa5e-29518f10c0aa',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('1f58daa7-a5fb-45b0-be43-4525d92321f6','3ec11db4-f821-409f-84ad-07fc8e64d60d','3ece4e86-d328-4206-9f81-ec62bdf55335',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('b19ab311-861b-4a48-9712-8542fa09a69c','4a366bb4-5104-45ea-ac9e-1da8e14387c3','508d9830-6a60-44d3-992f-3c48c507f9f6',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('b0856cf5-4745-433f-bf85-8b5820cd4ed1','3ec11db4-f821-409f-84ad-07fc8e64d60d','7d0fc5a1-719b-4070-a740-fe387075f0c3',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('68931cbe-990a-4f69-92f4-093aebd3ffc3','58dcc836-51e1-4633-9a89-73ac44eb2152','e5d41d36-b355-4407-9ede-cd435da69873',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('8b9f5bdc-bc1d-4065-a91c-4dab84332773','4a366bb4-5104-45ea-ac9e-1da8e14387c3','3320e408-93d8-4933-abb8-538a5d697b41',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('32379738-2852-4530-955c-df0b129aac48','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','4f16c772-1df4-4922-a9e1-761ca829bb85',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('92db755a-c4eb-4e43-af1a-033203093138','58dcc836-51e1-4633-9a89-73ac44eb2152','afb334ca-9466-44ec-9be1-4c881db6d060',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('69a96a54-da92-4da8-8ce9-ca7352e50d0d','7ee486f1-4de8-4700-922b-863168f612a0','649f665a-7624-4824-9cd5-b992462eb97b',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('251af243-a73d-4f66-9e31-c01d1a328fd9','899d79f7-8623-4442-a398-002178cf5d94','b80a00d4-f829-4051-961a-b8945c62c37d',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('d10f8dfa-f234-4f52-bfa1-b3d590589245','58dcc836-51e1-4633-9a89-73ac44eb2152','b80251b4-02a2-4122-add9-ab108cd011d7',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('1c5b3ad7-e3a9-41cd-b4b6-84d992fa4e7a','3ec11db4-f821-409f-84ad-07fc8e64d60d','6e802149-7e46-4d7a-ab57-6c4df832085d',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('4fdabb3f-a71e-42c7-a030-2744348cd61e','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','b194b7a9-a759-4c12-9482-b99e43a52294',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('07c010ab-49a5-4f66-a718-a38561e46d54','dd6c2ace-2593-445b-9569-55328090de99','4f2e3e38-6bf4-4e74-bd7b-fe6edb87ee42',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('12304e47-af4f-4e6a-a09a-e5de9ff31797','3ec11db4-f821-409f-84ad-07fc8e64d60d','5802e021-5283-4b43-ba85-31340065d5ec',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('89c5003e-59da-48d3-836b-f87b5e53170e','58dcc836-51e1-4633-9a89-73ac44eb2152','535e6789-c126-405f-8b3a-7bd886b94796',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('90bcc844-ac12-4b24-8c30-a287f13e9a06','58dcc836-51e1-4633-9a89-73ac44eb2152','649f665a-7624-4824-9cd5-b992462eb97b',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('d4517468-5426-46aa-8ca1-857b7f3fe3d8','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','c5aab403-d0e2-4e6e-b3f1-57fc52e6c2bd',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('189c5659-376a-4ae7-bda7-e48ec1124567','899d79f7-8623-4442-a398-002178cf5d94','43a09249-d81b-4897-b5c7-dd88331cf2bd',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('38f5babf-509b-4554-b375-be0916681255','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','e5d41d36-b355-4407-9ede-cd435da69873',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('e60f12a9-ea80-4afd-864e-e2034f177ba0','899d79f7-8623-4442-a398-002178cf5d94','649f665a-7624-4824-9cd5-b992462eb97b',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('8d8e1a36-7506-4caf-b3a6-9527d2e941c9','899d79f7-8623-4442-a398-002178cf5d94','dd6c2ace-2593-445b-9569-55328090de99',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('4049065b-6d02-4a1d-a7fb-73547b6bad8f','3ec11db4-f821-409f-84ad-07fc8e64d60d','146c58e5-c87d-4f54-a766-8da85c6b6b2c',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('708fa2ce-1483-444e-b34a-7d4cdff6f2d2','58dcc836-51e1-4633-9a89-73ac44eb2152','c5aab403-d0e2-4e6e-b3f1-57fc52e6c2bd',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('886710d6-d3a9-4021-9843-3c7dbb680286','3ec11db4-f821-409f-84ad-07fc8e64d60d','8abaed50-eac1-4f40-83db-c07d2c3a123a',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('2a5224a7-3a18-4046-a7e0-e8acd25ed572','4a366bb4-5104-45ea-ac9e-1da8e14387c3','b80a00d4-f829-4051-961a-b8945c62c37d',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('5fa5f7ea-2c09-4362-a510-ca15e1c7d4d8','3ec11db4-f821-409f-84ad-07fc8e64d60d','612c2ce9-39cc-45e6-a3f1-c6672267d392',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('86a948d2-e8e9-41fa-822d-e4b2bc4f3118','58dcc836-51e1-4633-9a89-73ac44eb2152','6e802149-7e46-4d7a-ab57-6c4df832085d',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('80801c50-bfc4-4905-a17b-ea6d02c31be4','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','7582d86d-d4e7-4a88-997d-05593ccefb37',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('4ba4a8a7-3a2f-4a8c-a86e-a97fa78f2b66','4a366bb4-5104-45ea-ac9e-1da8e14387c3','47e88f74-4e28-4027-b05e-bf9adf63e572',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('6b54db90-f571-4c1f-b5df-eb985b68ee88','7ee486f1-4de8-4700-922b-863168f612a0','c9036eb8-84bb-4909-be20-0662387219a7',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('c5964462-6832-47dd-8ac7-9a6c381f0706','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','d45cf336-8c4b-4651-b505-bbd34831d12d',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('0c1b0de4-6630-471c-816d-d0c0bc593fb7','899d79f7-8623-4442-a398-002178cf5d94','c7442d31-012a-40f6-ab04-600a70db8723',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('ea2b3666-c9c9-4ee0-942a-9a9006bf2042','dd6c2ace-2593-445b-9569-55328090de99','c4c73fcb-be11-4b1a-986a-a73451d402a7',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('173862d4-987d-4a1c-b730-2d5a53576f15','4a366bb4-5104-45ea-ac9e-1da8e14387c3','93052804-f158-485d-b3a5-f04fd0d41e55',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('f999e245-14e0-4f54-92f7-b52f6c6aaf0f','899d79f7-8623-4442-a398-002178cf5d94','612c2ce9-39cc-45e6-a3f1-c6672267d392',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('f6a105d1-3d6e-4d90-9dbc-15b02a778de4','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','b80251b4-02a2-4122-add9-ab108cd011d7',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('f156d1c0-010a-440e-9239-b2ca52c23130','dd6c2ace-2593-445b-9569-55328090de99','2a1b3667-e604-41a0-b741-ba19f1f56892',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('5856cfd5-8eb6-402f-8908-6fe0d1af25da','899d79f7-8623-4442-a398-002178cf5d94','829d8b45-19c1-49a3-920c-cc0ae14e8698',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('3c852f28-d0cb-4a6e-9a1c-14b59c6f9a49','899d79f7-8623-4442-a398-002178cf5d94','9893a927-6084-482c-8f1c-e85959eb3547',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('be3cd74d-53a6-4460-bf13-28d22258c96d','7ee486f1-4de8-4700-922b-863168f612a0','c3c46c6b-115a-4236-b88a-76126e7f9516',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('19a51d9b-2c60-4d13-96d2-45fd87c825cc','dd6c2ace-2593-445b-9569-55328090de99','30040c3f-667d-4dee-ba4c-24aad0891c9c',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('1ded40bc-b709-4303-8688-74bdb435de02','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','cae0eb53-a023-434c-ac8c-d0641067d8d8',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('f4b1b5f5-8ff6-4a87-a03e-76247cd902df','899d79f7-8623-4442-a398-002178cf5d94','433334c3-59dd-404d-a193-10dd4172fc8f',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('cba3ee66-2ff7-406a-89e0-8150332ea319','3ec11db4-f821-409f-84ad-07fc8e64d60d','f79dd433-2808-4f20-91ef-6b5efca07350',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('51faebc0-a185-488b-887d-5408f9f39b92','dd6c2ace-2593-445b-9569-55328090de99','7582d86d-d4e7-4a88-997d-05593ccefb37',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('03747594-592e-4504-b59d-2f2c01c90c4f','4a366bb4-5104-45ea-ac9e-1da8e14387c3','ee0ffe93-32b3-4817-982e-6d081da85d28',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('a71ee6eb-5960-435b-b4b4-780a21d4ae24','dd6c2ace-2593-445b-9569-55328090de99','02cc7df6-83d0-4ff1-a5ea-8240f5434e73',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('075b32a1-6edf-4530-8abc-73a7e1bef96a','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','0506bf0f-bc1c-43c7-a75f-639a1b4c0449',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('739eafbf-47b6-4ab2-8e02-b88452f7b2a4','7ee486f1-4de8-4700-922b-863168f612a0','d53d6be6-b36c-403f-b72d-d6160e9e52c1',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('6fe9b2a3-d74f-4a8b-81a7-622f88373e5d','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','816f84d1-ea01-47a0-a799-4b68508e35cc',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('578f504f-98a2-4ede-a255-0a65632507f6','58dcc836-51e1-4633-9a89-73ac44eb2152','d45cf336-8c4b-4651-b505-bbd34831d12d',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('4ebb4827-0e44-4449-a568-15cfe5b7f8f2','899d79f7-8623-4442-a398-002178cf5d94','47e88f74-4e28-4027-b05e-bf9adf63e572',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('69af6e71-b608-4120-be85-0e99e46851b8','899d79f7-8623-4442-a398-002178cf5d94','a4fa6b22-3d7f-4d56-96f1-941f9e7570aa',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('d2ee8733-51b9-414d-ad47-b57b2ace3d6c','58dcc836-51e1-4633-9a89-73ac44eb2152','c68492e9-c7d9-4394-8695-15f018ce6b90',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('f7c7e771-6b1b-4cd9-8737-89d9d9bd4810','dd6c2ace-2593-445b-9569-55328090de99','4a366bb4-5104-45ea-ac9e-1da8e14387c3',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('85a18a5f-e56d-47de-ac63-9384057e1299','7ee486f1-4de8-4700-922b-863168f612a0','9bb87311-1b29-4f29-8561-8a4c795654d4',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('07c6f63d-1309-45b9-b508-0f222afcfd67','3ec11db4-f821-409f-84ad-07fc8e64d60d','7675199b-55b9-4184-bce8-a6c0c2c9e9ab',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('643af8d0-fc65-4444-88a6-cb309f331255','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','709dad47-121a-4edd-ad95-b3dd6fd88f08',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('db43ab22-7ad4-4640-8bc8-04b773168442','58dcc836-51e1-4633-9a89-73ac44eb2152','311e5909-df08-4086-aa09-4c21a48b5e6e',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('1e9a1a7c-548f-4842-98cf-12f9a93a8622','dd6c2ace-2593-445b-9569-55328090de99','c3c46c6b-115a-4236-b88a-76126e7f9516',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('bac3b9fb-a504-4ec7-9abc-49efa723aaba','58dcc836-51e1-4633-9a89-73ac44eb2152','8abaed50-eac1-4f40-83db-c07d2c3a123a',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('a1e5a21e-1953-4285-9a08-76757b2a79c5','3ec11db4-f821-409f-84ad-07fc8e64d60d','c68e26d0-dc81-4320-bdd7-fa286f4cc891',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('c90d3dff-0781-4e87-9ff8-20285c7590c7','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','fd89694b-06ef-4472-ac9f-614c2de3317b',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('e71d73cb-053f-435d-9fc0-6e46181052cc','3ec11db4-f821-409f-84ad-07fc8e64d60d','64265049-1b4a-4a96-9cba-e01f59cafcc7',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('54798e1e-4687-4d69-8ceb-febd42f3d637','58dcc836-51e1-4633-9a89-73ac44eb2152','02cc7df6-83d0-4ff1-a5ea-8240f5434e73',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('362e5bb8-a96e-47b0-92cc-1b0f857ab439','3ec11db4-f821-409f-84ad-07fc8e64d60d','3ec11db4-f821-409f-84ad-07fc8e64d60d',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('71e4b441-9f7e-4004-b293-13c08906877e','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','ba215fd2-cdfc-4b98-bd78-cfa667b1b371',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('66c36fd5-906c-4ccb-a1af-e52bd0792ff4','4a366bb4-5104-45ea-ac9e-1da8e14387c3','0cb31c3c-dfd2-4b2a-b475-d2023008eea4',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('dc8befe6-3403-41a0-a3e4-c44d77fa47af','dd6c2ace-2593-445b-9569-55328090de99','cae0eb53-a023-434c-ac8c-d0641067d8d8',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('955cb0ad-dd7b-44c6-8dc3-7dc4f1affade','dd6c2ace-2593-445b-9569-55328090de99','0026678a-51b7-46de-af3d-b49428e0916c',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('aefd48e1-c16e-412d-9187-b3fd15d81521','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','47cbf0b7-e249-4b7e-8306-e5a2d2b3f394',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('75b5228d-351e-4cd2-9ef3-152e6a08b7ab','58dcc836-51e1-4633-9a89-73ac44eb2152','a4fa6b22-3d7f-4d56-96f1-941f9e7570aa',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('18020fe9-da58-4148-b2b2-d1116a6a3478','899d79f7-8623-4442-a398-002178cf5d94','a7f17fd7-3810-4866-9b51-8179157b4a2b',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('df3be343-4b72-4a5e-a6cd-d678acbf9a73','7ee486f1-4de8-4700-922b-863168f612a0','dcc3cae7-e05e-4ade-9b5b-c2eaade9f101',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('9095a4eb-f9e1-4d34-9b1e-ddc246e6a15b','58dcc836-51e1-4633-9a89-73ac44eb2152','b80a00d4-f829-4051-961a-b8945c62c37d',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('7fe20a64-442f-4720-b435-0d59ba98603c','899d79f7-8623-4442-a398-002178cf5d94','c5aab403-d0e2-4e6e-b3f1-57fc52e6c2bd',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('76ca2b5a-2c3d-43d0-be4b-085763607bec','7ee486f1-4de8-4700-922b-863168f612a0','899d79f7-8623-4442-a398-002178cf5d94',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('b4f4ed88-8615-458c-873a-48d38f0df38a','7ee486f1-4de8-4700-922b-863168f612a0','6f0e02be-08ad-48b1-8e23-eecaab34b4fe',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('d0316089-99e8-41e0-a6eb-b4adcd38aa66','899d79f7-8623-4442-a398-002178cf5d94','4fb560d1-6bf5-46b7-a047-d381a76c4fef',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('fdd6cab9-d15b-47bf-9139-6d3896952eec','58dcc836-51e1-4633-9a89-73ac44eb2152','3ece4e86-d328-4206-9f81-ec62bdf55335',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('9c1d7750-4150-45c2-9f72-36c5c0faa604','3ec11db4-f821-409f-84ad-07fc8e64d60d','9a9da923-06ef-47ea-bc20-23cc85b51ad0',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('57b59c53-d111-47fc-abf1-a3eacf5bf7a9','58dcc836-51e1-4633-9a89-73ac44eb2152','43a09249-d81b-4897-b5c7-dd88331cf2bd',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('4bb38cc9-e25c-4feb-ab29-50ede5a6d85f','4a366bb4-5104-45ea-ac9e-1da8e14387c3','9a9da923-06ef-47ea-bc20-23cc85b51ad0',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('247c736f-33df-4e7a-82a5-2b30ed0a6d2e','3ec11db4-f821-409f-84ad-07fc8e64d60d','816f84d1-ea01-47a0-a799-4b68508e35cc',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('2ffd478b-7943-4894-8433-677250ff9fed','3ec11db4-f821-409f-84ad-07fc8e64d60d','def8c7af-d4fc-474e-974d-6fd00c251da8',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('250ccf7d-59ff-4940-b11e-651bf8ad1c45','58dcc836-51e1-4633-9a89-73ac44eb2152','71755cc7-0844-4523-a0ac-da9a1e743ad1',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('4b2dc6e7-1363-413d-9aa2-7506f3b650a1','7ee486f1-4de8-4700-922b-863168f612a0','93052804-f158-485d-b3a5-f04fd0d41e55',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('a6032c70-dfbc-4bb1-b041-2ca8849d624d','58dcc836-51e1-4633-9a89-73ac44eb2152','a2fad63c-b6cb-4b0d-9ced-1a81a6bc9985',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('29ba6b0d-ed9c-412d-9825-a11b6c1e4fe0','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','c7442d31-012a-40f6-ab04-600a70db8723',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('5e828361-f406-4c0e-969e-6bce04363996','dd6c2ace-2593-445b-9569-55328090de99','8eb44185-f9bf-465e-8469-7bc422534319',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('89681e91-4e2e-4a04-a5b1-20f532e1a6bd','899d79f7-8623-4442-a398-002178cf5d94','311e5909-df08-4086-aa09-4c21a48b5e6e',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('34e6097a-04dd-48ab-abe1-39cc54c8e3f8','3ec11db4-f821-409f-84ad-07fc8e64d60d','43a09249-d81b-4897-b5c7-dd88331cf2bd',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('5a640c51-20c5-4619-8ec4-cc1f31ba2f93','7ee486f1-4de8-4700-922b-863168f612a0','c5aab403-d0e2-4e6e-b3f1-57fc52e6c2bd',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('6717b5ce-83bc-4a56-b11c-bf85801a5e35','3ec11db4-f821-409f-84ad-07fc8e64d60d','027f06cd-8c82-4c4a-a583-b20ccad9cc35',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('764e6a31-9251-4959-8657-321411d26b8a','899d79f7-8623-4442-a398-002178cf5d94','1e23a20c-2558-47bf-b720-d7758b717ce3',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('ea07713d-8288-44b0-adcd-c0eaa52f1b06','899d79f7-8623-4442-a398-002178cf5d94','fd57df67-e734-4eb2-80cf-2feafe91f238',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('dbe66700-ef8d-4355-8650-83ec9962de2b','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','7675199b-55b9-4184-bce8-a6c0c2c9e9ab',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('92756a51-0a80-43d1-a239-c9cdf3d24ecc','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','3733db73-602a-4402-8f94-36eec2fdab15',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('79e10bc9-5a10-4412-ad17-8ad68a7ea8d3','dd6c2ace-2593-445b-9569-55328090de99','e3071ca8-bedf-4eff-bda0-e9ff27f0e34c',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('786252e8-33ca-483c-9c0d-8f8c7f43bd57','58dcc836-51e1-4633-9a89-73ac44eb2152','03dd5854-8bc3-4b56-986e-eac513cc1ec0',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('ddd5f8db-1fe8-4694-a7ab-3b82f694b30b','dd6c2ace-2593-445b-9569-55328090de99','612c2ce9-39cc-45e6-a3f1-c6672267d392',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('f0925dc4-d8fc-4563-93c1-00f522c71eff','dd6c2ace-2593-445b-9569-55328090de99','829d8b45-19c1-49a3-920c-cc0ae14e8698',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('c30dec0a-527f-466f-8a6d-771128f13fa4','dd6c2ace-2593-445b-9569-55328090de99','ea0fa1cc-7d80-4bd9-989e-f119c33fb881',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('baf81222-bd46-4d42-ac1a-3c47f33c7e41','dd6c2ace-2593-445b-9569-55328090de99','10644589-71f6-4baf-ba1c-dfb19d924b25',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('2322fb88-fe8e-4029-a410-03d5d3cd7152','899d79f7-8623-4442-a398-002178cf5d94','709dad47-121a-4edd-ad95-b3dd6fd88f08',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('5aa2228f-0a46-406b-b013-6c9d11edadbf','dd6c2ace-2593-445b-9569-55328090de99','2124fcbf-be89-4975-9cc7-263ac14ad759',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('b8ff25a1-7c43-42e2-ab1f-9d56b75bfe8b','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','a7f17fd7-3810-4866-9b51-8179157b4a2b',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('4795cbf8-baf4-4d36-ab37-e7fc13e3b916','3ec11db4-f821-409f-84ad-07fc8e64d60d','6530aaba-4906-4d63-a6d3-deea01c99bea',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('57c23177-d2b8-4ef8-a1da-44264694bb84','899d79f7-8623-4442-a398-002178cf5d94','d45cf336-8c4b-4651-b505-bbd34831d12d',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('59e166c7-1c15-4c04-8d61-13c72bb53248','3ec11db4-f821-409f-84ad-07fc8e64d60d','40da86e6-76e5-443b-b4ca-27ad31a2baf6',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('30fbe47a-db85-4f4d-be9d-14df4b93d65c','3ec11db4-f821-409f-84ad-07fc8e64d60d','7ac1c0ec-0903-477c-89e0-88efe9249c98',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('919bafb3-1c70-4a96-ab71-7f9390c2b5a1','4a366bb4-5104-45ea-ac9e-1da8e14387c3','a4fa6b22-3d7f-4d56-96f1-941f9e7570aa',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('0bd77db8-7101-4e21-9346-17330e091290','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','cfca47bf-4639-4b7c-aed9-5ff87c9cddde',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('923b0193-3240-44ae-ae2f-d38bff93c831','899d79f7-8623-4442-a398-002178cf5d94','91eb2878-0368-4347-97e3-e6caa362d878',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('8be06bc1-01c1-4cfd-92f0-556bc7d080f1','4a366bb4-5104-45ea-ac9e-1da8e14387c3','709dad47-121a-4edd-ad95-b3dd6fd88f08',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('d69844cb-b81e-4430-9ff8-8b48b7405b22','dd6c2ace-2593-445b-9569-55328090de99','535e6789-c126-405f-8b3a-7bd886b94796',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('6c441ca2-54b9-4d61-bc1f-ac1ade00fbf4','58dcc836-51e1-4633-9a89-73ac44eb2152','899d79f7-8623-4442-a398-002178cf5d94',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('1f1d8042-9a58-466b-a3ea-c7530dbd826c','3ec11db4-f821-409f-84ad-07fc8e64d60d','c5aab403-d0e2-4e6e-b3f1-57fc52e6c2bd',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('c45b0292-4d5f-40df-a40e-932759cb6d33','dd6c2ace-2593-445b-9569-55328090de99','a761a482-2929-4345-8027-3c6258f0c8dd',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('b7690444-7c76-4d10-aa39-3c12b8c92da0','3ec11db4-f821-409f-84ad-07fc8e64d60d','4f16c772-1df4-4922-a9e1-761ca829bb85',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('e7691a95-2d8f-47af-b24c-5c9ea2605a08','58dcc836-51e1-4633-9a89-73ac44eb2152','dd6c2ace-2593-445b-9569-55328090de99',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('26dea23a-232d-4e82-81ba-b244079dc854','dd6c2ace-2593-445b-9569-55328090de99','709dad47-121a-4edd-ad95-b3dd6fd88f08',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('aff42966-5aac-46d9-a69e-1badb6477938','899d79f7-8623-4442-a398-002178cf5d94','ee0ffe93-32b3-4817-982e-6d081da85d28',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('0c52c1e2-94d2-4223-a08a-81e2d0d4d2d5','58dcc836-51e1-4633-9a89-73ac44eb2152','635e4b79-342c-4cfc-8069-39c408a2decd',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('170781b0-75a5-40b8-9f41-c8e19b7a4cc3','4a366bb4-5104-45ea-ac9e-1da8e14387c3','d45cf336-8c4b-4651-b505-bbd34831d12d',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('570ae079-acb0-4022-864b-af4f4c9e214b','899d79f7-8623-4442-a398-002178cf5d94','7ee486f1-4de8-4700-922b-863168f612a0',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('5427f194-87cb-4680-8be4-ba14df2f45db','4a366bb4-5104-45ea-ac9e-1da8e14387c3','0ba534f5-0d24-4d7c-9216-d07f57cd8edd',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('99291c29-6f9c-49f6-a484-c90d2685fa94','4a366bb4-5104-45ea-ac9e-1da8e14387c3','ca72968c-5921-4167-b7b6-837c88ca87f2',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('2ae743b2-af1e-47e4-b43e-2a9c0923b5b3','58dcc836-51e1-4633-9a89-73ac44eb2152','e337daba-5509-4507-be21-ca13ecaced9b',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('e529354e-c108-41b9-8aba-01c34d1040bd','dd6c2ace-2593-445b-9569-55328090de99','a2fad63c-b6cb-4b0d-9ced-1a81a6bc9985',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('d00a91cc-b3bd-43f8-aaca-596cbe92cc51','4a366bb4-5104-45ea-ac9e-1da8e14387c3','422021c7-08e1-4355-838d-8f2821f00f42',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('ea0ba08a-1846-4f64-9224-53ad1e651ae4','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','e4e467f2-449d-46e3-a59b-0f8714e4824a',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('cc7e1e18-247e-4b89-a135-fcf82f4da4fb','899d79f7-8623-4442-a398-002178cf5d94','4f2e3e38-6bf4-4e74-bd7b-fe6edb87ee42',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('7556fbe0-c1f1-4f4c-a124-195785630c4e','4a366bb4-5104-45ea-ac9e-1da8e14387c3','531e3a04-e84c-45d9-86bf-c6da0820b605',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('86294d33-3b76-45d9-937d-8aff74d03452','3ec11db4-f821-409f-84ad-07fc8e64d60d','e337daba-5509-4507-be21-ca13ecaced9b',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('819bb2ee-213e-42ed-b266-5dd6b57e9da4','899d79f7-8623-4442-a398-002178cf5d94','93052804-f158-485d-b3a5-f04fd0d41e55',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('ba48c2fc-0fb7-481c-914a-f300401bc6f0','4a366bb4-5104-45ea-ac9e-1da8e14387c3','a2fad63c-b6cb-4b0d-9ced-1a81a6bc9985',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('a984db44-52e8-48c2-aa8b-9da2aaa6af0a','4a366bb4-5104-45ea-ac9e-1da8e14387c3','30040c3f-667d-4dee-ba4c-24aad0891c9c',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('702e2a7d-6a2b-474d-8e78-26d638c256ad','4a366bb4-5104-45ea-ac9e-1da8e14387c3','cfca47bf-4639-4b7c-aed9-5ff87c9cddde',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('f24ae42a-e231-4ed9-b595-2851973c3274','3ec11db4-f821-409f-84ad-07fc8e64d60d','e4e467f2-449d-46e3-a59b-0f8714e4824a',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('8993d8f5-dfb7-4921-a358-7092e2f1dc69','7ee486f1-4de8-4700-922b-863168f612a0','b80251b4-02a2-4122-add9-ab108cd011d7',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('9ed55bf4-a9ea-4ca2-884c-761a99129233','899d79f7-8623-4442-a398-002178cf5d94','c9036eb8-84bb-4909-be20-0662387219a7',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('d023853e-2c4d-47d8-bb88-4698d8f6b461','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','b3911f28-d334-4cca-8924-7da60ea5a213',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('6c5a3ee8-240d-472c-ba27-9825a831ed31','4a366bb4-5104-45ea-ac9e-1da8e14387c3','182eb005-c185-418d-be8b-f47212c38af3',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('5e09dcd6-7b57-465d-b384-73effd326bd7','dd6c2ace-2593-445b-9569-55328090de99','40ab17b2-9e79-429c-a75d-b6fcbbe27901',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('8dddc601-9274-45a4-91a9-fbc06a44af9c','4a366bb4-5104-45ea-ac9e-1da8e14387c3','311e5909-df08-4086-aa09-4c21a48b5e6e',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('2a3773b0-fcc5-41d8-ba46-75c670f222cd','58dcc836-51e1-4633-9a89-73ac44eb2152','c4c73fcb-be11-4b1a-986a-a73451d402a7',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('93a5a88b-84ec-4d54-b852-35f9a7b27bb0','4a366bb4-5104-45ea-ac9e-1da8e14387c3','c68e26d0-dc81-4320-bdd7-fa286f4cc891',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('34d97e29-805b-4e3c-b366-9aa5414c1a1a','899d79f7-8623-4442-a398-002178cf5d94','e3071ca8-bedf-4eff-bda0-e9ff27f0e34c',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('e15f2e87-d7ab-473d-8fed-8c3920f85161','58dcc836-51e1-4633-9a89-73ac44eb2152','30040c3f-667d-4dee-ba4c-24aad0891c9c',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('bbb4924e-177f-48d1-b7b4-3816b5b95984','dd6c2ace-2593-445b-9569-55328090de99','760f146d-d5e7-4e08-9464-45371ea3267d',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('781cbc92-b1fc-409c-a841-ce020cef2297','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','58dcc836-51e1-4633-9a89-73ac44eb2152',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('273c326c-40a4-4a66-95e7-7aa4a001ae9d','3ec11db4-f821-409f-84ad-07fc8e64d60d','4fb560d1-6bf5-46b7-a047-d381a76c4fef',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('6b273190-6c7f-4b66-9247-c37ff86307c9','899d79f7-8623-4442-a398-002178cf5d94','f79dd433-2808-4f20-91ef-6b5efca07350',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('0d200065-14d5-4e99-b6ab-1a1f1f47b059','4a366bb4-5104-45ea-ac9e-1da8e14387c3','899d79f7-8623-4442-a398-002178cf5d94',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('4ef00027-36a5-4bca-887b-176d299e00ed','899d79f7-8623-4442-a398-002178cf5d94','8eb44185-f9bf-465e-8469-7bc422534319',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('5bfbbcef-95d9-4e0a-85fc-4e57b6089139','7ee486f1-4de8-4700-922b-863168f612a0','46c16bc1-df71-4c6f-835b-400c8caaf984',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('6f8d72a8-c492-4147-89c4-fe3355b984b6','7ee486f1-4de8-4700-922b-863168f612a0','c18e25f9-ec34-41ca-8c1b-05558c8d6364',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('dba555a9-e140-414a-9931-e4246f72ebcb','7ee486f1-4de8-4700-922b-863168f612a0','6a0f9a02-b6ba-4585-9d7a-6959f7b0248f',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('907ce374-b007-4738-8e09-e01e507506fd','7ee486f1-4de8-4700-922b-863168f612a0','508d9830-6a60-44d3-992f-3c48c507f9f6',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('cd794e3c-ac8e-4134-8933-32f0fb44a903','7ee486f1-4de8-4700-922b-863168f612a0','5802e021-5283-4b43-ba85-31340065d5ec',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('17db304a-7086-488d-8611-84d1b0a65ee1','dd6c2ace-2593-445b-9569-55328090de99','c68492e9-c7d9-4394-8695-15f018ce6b90',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('ec0c885e-d052-4ddc-9857-dde34f35604a','58dcc836-51e1-4633-9a89-73ac44eb2152','fd89694b-06ef-4472-ac9f-614c2de3317b',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('9b1a43e4-6a21-4903-8c55-0f3cc32911b2','899d79f7-8623-4442-a398-002178cf5d94','6e43ffbc-1102-45dc-8fb2-139f6b616083',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('3864427e-0aa6-40fb-8dd5-f582476616be','7ee486f1-4de8-4700-922b-863168f612a0','0506bf0f-bc1c-43c7-a75f-639a1b4c0449',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('a0ef8cbd-baeb-4840-bbf0-f0e4974866f1','3ec11db4-f821-409f-84ad-07fc8e64d60d','c68492e9-c7d9-4394-8695-15f018ce6b90',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('8107052e-218a-4c83-baf1-0867fbb51084','dd6c2ace-2593-445b-9569-55328090de99','b194b7a9-a759-4c12-9482-b99e43a52294',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('3d893f0f-0b76-4706-81fb-1eabe155eb10','7ee486f1-4de8-4700-922b-863168f612a0','027f06cd-8c82-4c4a-a583-b20ccad9cc35',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('d8bfdcd7-801d-426f-baa5-ecca0c14c5ca','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','4f2e3e38-6bf4-4e74-bd7b-fe6edb87ee42',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('eae0c2b8-986b-4d4f-bd3e-a16e36f51be2','58dcc836-51e1-4633-9a89-73ac44eb2152','f79dd433-2808-4f20-91ef-6b5efca07350',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('97d0a0c1-d5ad-4d42-a2c9-aa323749c688','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','5802e021-5283-4b43-ba85-31340065d5ec',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('d716f365-1794-4e00-8ae7-29a240f35e35','4a366bb4-5104-45ea-ac9e-1da8e14387c3','58dcc836-51e1-4633-9a89-73ac44eb2152',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('a9ad6dbb-649f-4744-b3ba-deb7e67a1030','4a366bb4-5104-45ea-ac9e-1da8e14387c3','9893a927-6084-482c-8f1c-e85959eb3547',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('69b383b9-55f1-4d4b-a556-d15c5a15a8da','7ee486f1-4de8-4700-922b-863168f612a0','535e6789-c126-405f-8b3a-7bd886b94796',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('b1f64b1f-e7b5-4aaa-b36e-d1dfa578965d','4a366bb4-5104-45ea-ac9e-1da8e14387c3','fd89694b-06ef-4472-ac9f-614c2de3317b',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('07519dc2-ddc8-4e9c-8ecf-9c5ce94f7dae','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','6e43ffbc-1102-45dc-8fb2-139f6b616083',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('5da8a99d-48ef-4bf7-a2a3-7f08f8faface','4a366bb4-5104-45ea-ac9e-1da8e14387c3','811a32c0-90d6-4744-9a57-ab4130091754',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('665c4280-b206-483e-81d1-5ddabe059e91','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','d53d6be6-b36c-403f-b72d-d6160e9e52c1',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('e5669ab5-0934-4070-9b4b-1479212b3ddc','899d79f7-8623-4442-a398-002178cf5d94','0026678a-51b7-46de-af3d-b49428e0916c',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('6cba1c3f-fd25-4aee-b2f2-9e1b10e5f806','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','311e5909-df08-4086-aa09-4c21a48b5e6e',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('013467fa-6617-4f96-a904-8e5b762f7957','7ee486f1-4de8-4700-922b-863168f612a0','8abaed50-eac1-4f40-83db-c07d2c3a123a',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('c8e7a6b0-b7bb-41ae-9d03-f1b5d509fd60','899d79f7-8623-4442-a398-002178cf5d94','7675199b-55b9-4184-bce8-a6c0c2c9e9ab',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('467bbf95-1336-48fd-b6ed-9ca3e0cdd8a0','3ec11db4-f821-409f-84ad-07fc8e64d60d','3733db73-602a-4402-8f94-36eec2fdab15',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('bb4a48cc-7f35-476e-b784-b5f6ca2d5d8d','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','a4fa6b22-3d7f-4d56-96f1-941f9e7570aa',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('fc216766-74e8-4575-90fe-e03def26c0bc','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','c68492e9-c7d9-4394-8695-15f018ce6b90',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('d8a88554-4932-47c7-88c8-cc4928de3e5d','899d79f7-8623-4442-a398-002178cf5d94','47cbf0b7-e249-4b7e-8306-e5a2d2b3f394',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('298cb37e-4d9e-4541-81ce-2502b9a4a6d2','4a366bb4-5104-45ea-ac9e-1da8e14387c3','6530aaba-4906-4d63-a6d3-deea01c99bea',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('eebd536b-a43e-45e8-9c45-cf3372bcfc04','899d79f7-8623-4442-a398-002178cf5d94','e4e467f2-449d-46e3-a59b-0f8714e4824a',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('08cb9e55-ae4e-4f32-9879-ce5d7d4de021','dd6c2ace-2593-445b-9569-55328090de99','649f665a-7624-4824-9cd5-b992462eb97b',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('7f00017d-c684-438f-8448-1b26bb1c5a27','dd6c2ace-2593-445b-9569-55328090de99','6a0f9a02-b6ba-4585-9d7a-6959f7b0248f',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('19840f79-ede7-4d91-a8c5-9cc8e41e0525','899d79f7-8623-4442-a398-002178cf5d94','3320e408-93d8-4933-abb8-538a5d697b41',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('a5f5934f-ec85-4956-90ff-7f424337e642','7ee486f1-4de8-4700-922b-863168f612a0','7582d86d-d4e7-4a88-997d-05593ccefb37',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('f6164e9d-a0f8-4898-a141-2e27734ee8a4','7ee486f1-4de8-4700-922b-863168f612a0','5e8d8851-bf33-4d48-9860-acc24aceea3d',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('94a99e9a-9bc7-4f22-bc82-60909308cae0','dd6c2ace-2593-445b-9569-55328090de99','9a9da923-06ef-47ea-bc20-23cc85b51ad0',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('9c825e6f-a902-43f0-a4fe-ca9b52de6b6b','dd6c2ace-2593-445b-9569-55328090de99','91eb2878-0368-4347-97e3-e6caa362d878',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('32d5a4c0-d940-4171-a46c-d576e2594131','dd6c2ace-2593-445b-9569-55328090de99','c18e25f9-ec34-41ca-8c1b-05558c8d6364',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('2d40022a-b0f5-4584-847f-ad981263b5f8','3ec11db4-f821-409f-84ad-07fc8e64d60d','3320e408-93d8-4933-abb8-538a5d697b41',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('84b36fde-1e3e-45d7-bd1e-80c23034c987','dd6c2ace-2593-445b-9569-55328090de99','433334c3-59dd-404d-a193-10dd4172fc8f',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('0973546e-2d5f-4001-a252-992319947e4c','dd6c2ace-2593-445b-9569-55328090de99','6e802149-7e46-4d7a-ab57-6c4df832085d',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('8d268175-bf34-41c2-b9c3-e7763d28ddc6','4a366bb4-5104-45ea-ac9e-1da8e14387c3','b80251b4-02a2-4122-add9-ab108cd011d7',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('63f2179c-84f3-4477-bd81-2fe508934014','899d79f7-8623-4442-a398-002178cf5d94','40ab17b2-9e79-429c-a75d-b6fcbbe27901',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('0ad2f414-072d-472d-960c-4cffbe6be9de','3ec11db4-f821-409f-84ad-07fc8e64d60d','dd6c2ace-2593-445b-9569-55328090de99',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('b234c1f4-6224-4b3f-9631-3d9fc6a1f8d6','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','8eb44185-f9bf-465e-8469-7bc422534319',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('62530f56-8bc0-4e87-94d3-9e7928219aad','3ec11db4-f821-409f-84ad-07fc8e64d60d','535e6789-c126-405f-8b3a-7bd886b94796',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('2204a19a-b300-4b87-bb76-241351c4b14e','7ee486f1-4de8-4700-922b-863168f612a0','fd57df67-e734-4eb2-80cf-2feafe91f238',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('6ce9a2d3-4a3d-4267-99bd-3d5bc69e9524','4a366bb4-5104-45ea-ac9e-1da8e14387c3','612c2ce9-39cc-45e6-a3f1-c6672267d392',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('b6072357-eebf-452c-a57d-d7a951afbd95','4a366bb4-5104-45ea-ac9e-1da8e14387c3','fe76b78f-67bc-4125-8f81-8e68697c136d',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('71563d97-6c1d-4d82-ac30-75df41a7918d','58dcc836-51e1-4633-9a89-73ac44eb2152','027f06cd-8c82-4c4a-a583-b20ccad9cc35',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('f6b69d37-5006-4432-8c94-f1b0674c5734','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','899d79f7-8623-4442-a398-002178cf5d94',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('07a3eb54-1482-45f1-bf45-404beddf912f','dd6c2ace-2593-445b-9569-55328090de99','6455326e-cc11-4cfe-903b-ccce70e6f04e',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('0dd2853d-a3d7-4929-a27e-1bf8f1cf3bab','58dcc836-51e1-4633-9a89-73ac44eb2152','2a1b3667-e604-41a0-b741-ba19f1f56892',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('c1605e53-5978-452e-97ec-d12805b57c36','899d79f7-8623-4442-a398-002178cf5d94','40da86e6-76e5-443b-b4ca-27ad31a2baf6',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('867d08cf-2ff1-46fe-822a-7850fa6bceb4','3ec11db4-f821-409f-84ad-07fc8e64d60d','7ee486f1-4de8-4700-922b-863168f612a0',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('29031c00-a250-4802-a77b-bb7af0209b1e','899d79f7-8623-4442-a398-002178cf5d94','9b6832a8-eb82-4afa-b12f-b52a3b2cda75',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('e589c801-dfcb-49c3-b3e7-9887d1d57abc','7ee486f1-4de8-4700-922b-863168f612a0','1beb0053-329a-4b47-879b-1a3046d3ff87',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('480dc02b-85bf-4f73-a516-e2c3734f82f1','dd6c2ace-2593-445b-9569-55328090de99','a7f17fd7-3810-4866-9b51-8179157b4a2b',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('e0f018a0-704e-47ee-a33b-2aa492ff7a0c','3ec11db4-f821-409f-84ad-07fc8e64d60d','422021c7-08e1-4355-838d-8f2821f00f42',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('b85ace42-c003-4cc2-89a9-52f448896337','58dcc836-51e1-4633-9a89-73ac44eb2152','0cb31c3c-dfd2-4b2a-b475-d2023008eea4',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('2d655193-cc72-48bf-8ebf-2c78ee2f8c7b','58dcc836-51e1-4633-9a89-73ac44eb2152','9a4aa0e1-6b5f-4624-a21c-3acfa858d7f3',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('2571b962-aa16-49a9-87a5-cbfd1a119599','7ee486f1-4de8-4700-922b-863168f612a0','182eb005-c185-418d-be8b-f47212c38af3',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('3781357e-e028-45cb-8ae7-90b507b07fda','4a366bb4-5104-45ea-ac9e-1da8e14387c3','d53d6be6-b36c-403f-b72d-d6160e9e52c1',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('f4c5c2ac-6e66-443b-b1e8-46ef98f98843','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','6e802149-7e46-4d7a-ab57-6c4df832085d',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('f457556f-db3f-4da0-b86c-684ad8f92caa','7ee486f1-4de8-4700-922b-863168f612a0','3320e408-93d8-4933-abb8-538a5d697b41',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('6a5ea88a-d399-4590-bfd1-b39d1fd3722c','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','dcc3cae7-e05e-4ade-9b5b-c2eaade9f101',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('f58074ee-de4b-4fc9-952d-5e9892f56657','899d79f7-8623-4442-a398-002178cf5d94','cfca47bf-4639-4b7c-aed9-5ff87c9cddde',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('1f09816e-e33a-436c-af47-c1822331d750','dd6c2ace-2593-445b-9569-55328090de99','9bb87311-1b29-4f29-8561-8a4c795654d4',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('72989157-a331-4089-8918-43bf9018db78','7ee486f1-4de8-4700-922b-863168f612a0','7ac1c0ec-0903-477c-89e0-88efe9249c98',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('bda8787a-93c9-486c-ba7b-f6a365056348','7ee486f1-4de8-4700-922b-863168f612a0','dd6c2ace-2593-445b-9569-55328090de99',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('ac6e7ff3-435f-4934-8f29-6bf239e55c0e','7ee486f1-4de8-4700-922b-863168f612a0','612c2ce9-39cc-45e6-a3f1-c6672267d392',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('0eea859f-dc73-45ab-a910-2757210b2858','899d79f7-8623-4442-a398-002178cf5d94','0ba534f5-0d24-4d7c-9216-d07f57cd8edd',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('25459fca-6c61-48c8-b411-f4c4e81f977f','58dcc836-51e1-4633-9a89-73ac44eb2152','47cbf0b7-e249-4b7e-8306-e5a2d2b3f394',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('0a257c22-7361-4a63-89ce-7521972051fd','58dcc836-51e1-4633-9a89-73ac44eb2152','0ba534f5-0d24-4d7c-9216-d07f57cd8edd',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('c08d097f-6a20-4f0f-95d2-cab0baf9d410','dd6c2ace-2593-445b-9569-55328090de99','cfe9ab8a-a353-433e-8204-c065deeae3d9',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('190189e1-90c5-4162-b1ea-a62387911b81','dd6c2ace-2593-445b-9569-55328090de99','def8c7af-d4fc-474e-974d-6fd00c251da8',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('716713a1-f385-407a-a05c-a86c93b063c6','7ee486f1-4de8-4700-922b-863168f612a0','811a32c0-90d6-4744-9a57-ab4130091754',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('470690b4-0829-49aa-865e-ae9f2b5c0f67','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','6530aaba-4906-4d63-a6d3-deea01c99bea',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('2c4f1a1f-021a-47d4-867e-e93ef5522892','899d79f7-8623-4442-a398-002178cf5d94','5bf18f68-55b8-4024-adb1-c2e6592a2582',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('a30c950c-5628-41d8-92c8-194340550dd7','4a366bb4-5104-45ea-ac9e-1da8e14387c3','3733db73-602a-4402-8f94-36eec2fdab15',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('a48d0ac0-bf73-4850-80ec-14ad8eb78aa9','dd6c2ace-2593-445b-9569-55328090de99','4f16c772-1df4-4922-a9e1-761ca829bb85',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('42b2a480-2817-4136-a506-92387630177d','3ec11db4-f821-409f-84ad-07fc8e64d60d','1a170f85-e7f1-467c-a4dc-7d0b7898287e',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('e6d072cf-3e04-4770-b3f9-f22ec2f9a25a','7ee486f1-4de8-4700-922b-863168f612a0','8eb44185-f9bf-465e-8469-7bc422534319',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('0752f608-3dda-4971-b27a-b480b5e21705','899d79f7-8623-4442-a398-002178cf5d94','ca72968c-5921-4167-b7b6-837c88ca87f2',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('b60e94f7-b968-4b56-be41-34bc0b40fa77','dd6c2ace-2593-445b-9569-55328090de99','311e5909-df08-4086-aa09-4c21a48b5e6e',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('8076ce7b-d0ba-4f05-a666-3c09da3858fe','7ee486f1-4de8-4700-922b-863168f612a0','829d8b45-19c1-49a3-920c-cc0ae14e8698',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('5faa942e-38e6-490d-8193-10b603167052','7ee486f1-4de8-4700-922b-863168f612a0','b194b7a9-a759-4c12-9482-b99e43a52294',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('89b7af0f-8c3b-4ed1-91ef-33e2424fbc63','899d79f7-8623-4442-a398-002178cf5d94','6e802149-7e46-4d7a-ab57-6c4df832085d',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('e252cef7-4b7d-4e11-9034-c2c2090c0227','7ee486f1-4de8-4700-922b-863168f612a0','10644589-71f6-4baf-ba1c-dfb19d924b25',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('a21a7cf3-a9dd-43ec-af21-529398e75f61','3ec11db4-f821-409f-84ad-07fc8e64d60d','b80a00d4-f829-4051-961a-b8945c62c37d',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('ab068613-b232-4a8d-8e86-ba599f9b7e33','58dcc836-51e1-4633-9a89-73ac44eb2152','433334c3-59dd-404d-a193-10dd4172fc8f',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('18dcf61f-363a-4511-bf80-a8c031811385','899d79f7-8623-4442-a398-002178cf5d94','e337daba-5509-4507-be21-ca13ecaced9b',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('9a2eec81-0970-4ae6-969f-322e359ce6e3','58dcc836-51e1-4633-9a89-73ac44eb2152','9b6832a8-eb82-4afa-b12f-b52a3b2cda75',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('09880c5f-0a9d-49ac-ba1a-679eb71c620b','58dcc836-51e1-4633-9a89-73ac44eb2152','fe76b78f-67bc-4125-8f81-8e68697c136d',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('d61046c5-5114-44ef-a9d4-36d6aaf6ddbd','899d79f7-8623-4442-a398-002178cf5d94','58dcc836-51e1-4633-9a89-73ac44eb2152',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('4fe56a35-7be8-4508-abaf-7a7b79c3bad9','3ec11db4-f821-409f-84ad-07fc8e64d60d','9a4aa0e1-6b5f-4624-a21c-3acfa858d7f3',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('27c0897a-b82e-41a6-9ffc-f2988a484fa4','4a366bb4-5104-45ea-ac9e-1da8e14387c3','433334c3-59dd-404d-a193-10dd4172fc8f',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('3fa5e471-d38a-4174-b56c-48c4bd97e7a9','4a366bb4-5104-45ea-ac9e-1da8e14387c3','9b6832a8-eb82-4afa-b12f-b52a3b2cda75',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('d3237e00-9b40-4bdf-9d27-988cf0311f27','7ee486f1-4de8-4700-922b-863168f612a0','4a239fdb-9ad7-4bbb-8685-528f3f861992',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('2c63088f-01de-4b97-8ab0-88425bcefa07','3ec11db4-f821-409f-84ad-07fc8e64d60d','ddd74fb8-c0f1-41a9-9d4f-234bd295ae1a',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('7a3ca421-8019-436d-84a1-e8fe456f8332','3ec11db4-f821-409f-84ad-07fc8e64d60d','a2fad63c-b6cb-4b0d-9ced-1a81a6bc9985',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('2cde084b-0e44-4e64-b150-a8c8639fa5df','58dcc836-51e1-4633-9a89-73ac44eb2152','5bf18f68-55b8-4024-adb1-c2e6592a2582',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('6dcffe62-fdec-4b64-9b1e-d19f969f5a8b','3ec11db4-f821-409f-84ad-07fc8e64d60d','5bf18f68-55b8-4024-adb1-c2e6592a2582',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('87f2feb4-d886-44eb-9edd-99c45954e032','7ee486f1-4de8-4700-922b-863168f612a0','0026678a-51b7-46de-af3d-b49428e0916c',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('386a07d6-89c8-4a5a-a8eb-367e68989025','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','c9036eb8-84bb-4909-be20-0662387219a7',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('bda3e897-63df-44c6-9ad4-112484760648','3ec11db4-f821-409f-84ad-07fc8e64d60d','1beb0053-329a-4b47-879b-1a3046d3ff87',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('7f4c7868-b73d-42a8-85c5-1a3b8b079cc4','dd6c2ace-2593-445b-9569-55328090de99','b3911f28-d334-4cca-8924-7da60ea5a213',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('2ffb1fa9-2370-4d15-9867-aa6c47fadfae','899d79f7-8623-4442-a398-002178cf5d94','ea0fa1cc-7d80-4bd9-989e-f119c33fb881',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('c449652a-bdc5-4fb2-974c-4df34c2279ed','899d79f7-8623-4442-a398-002178cf5d94','508d9830-6a60-44d3-992f-3c48c507f9f6',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('5df9f4d0-3187-43d1-aa72-470232e662db','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','182eb005-c185-418d-be8b-f47212c38af3',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('86d590f4-4bbe-4e1c-8f53-2596f1f2335d','4a366bb4-5104-45ea-ac9e-1da8e14387c3','43a09249-d81b-4897-b5c7-dd88331cf2bd',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('8f4177e7-c019-439f-913b-3f7bac35b940','7ee486f1-4de8-4700-922b-863168f612a0','a2fad63c-b6cb-4b0d-9ced-1a81a6bc9985',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('84c20ee8-9f1b-40ef-b807-63828ca7514d','dd6c2ace-2593-445b-9569-55328090de99','d53d6be6-b36c-403f-b72d-d6160e9e52c1',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('a0675cf9-24cb-4242-8558-6245e37b93bb','3ec11db4-f821-409f-84ad-07fc8e64d60d','fe76b78f-67bc-4125-8f81-8e68697c136d',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('a82fcb22-bd61-4eed-a5cd-ff81020f3e31','3ec11db4-f821-409f-84ad-07fc8e64d60d','91eb2878-0368-4347-97e3-e6caa362d878',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('fdd604d7-d9aa-41fc-9b7f-dbaf77ac42ed','dd6c2ace-2593-445b-9569-55328090de99','58dcc836-51e1-4633-9a89-73ac44eb2152',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('584c187f-baaf-44fb-98ef-d71b5bd36520','dd6c2ace-2593-445b-9569-55328090de99','5802e021-5283-4b43-ba85-31340065d5ec',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('08f37bdd-60c5-4724-93c4-febf5b3950bc','dd6c2ace-2593-445b-9569-55328090de99','c5aab403-d0e2-4e6e-b3f1-57fc52e6c2bd',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('6fbbe905-5294-4439-ab96-96636dc12178','3ec11db4-f821-409f-84ad-07fc8e64d60d','a761a482-2929-4345-8027-3c6258f0c8dd',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('69f4902b-f75e-484f-961d-9864510adb24','899d79f7-8623-4442-a398-002178cf5d94','4f16c772-1df4-4922-a9e1-761ca829bb85',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('4af57a0f-f93d-4726-b2ae-b473304772db','3ec11db4-f821-409f-84ad-07fc8e64d60d','b3911f28-d334-4cca-8924-7da60ea5a213',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('19c505e9-80c7-4865-b5da-11acc923a52d','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','c18e25f9-ec34-41ca-8c1b-05558c8d6364',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('72470324-d0e2-4e57-affe-f0fdb00b3719','899d79f7-8623-4442-a398-002178cf5d94','d53d6be6-b36c-403f-b72d-d6160e9e52c1',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('a43c4480-e22e-4360-b232-987d1ce45881','899d79f7-8623-4442-a398-002178cf5d94','b3911f28-d334-4cca-8924-7da60ea5a213',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('ef1518bd-49ad-4ce1-869e-ca514849e0a7','7ee486f1-4de8-4700-922b-863168f612a0','19ddeb7f-91c1-4bd0-83ef-264eb78a3f75',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('8300d216-776c-4483-b290-7933d355cff7','899d79f7-8623-4442-a398-002178cf5d94','dcc3cae7-e05e-4ade-9b5b-c2eaade9f101',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('960bdc0f-1c11-4b98-9b94-4a1314436f47','7ee486f1-4de8-4700-922b-863168f612a0','433334c3-59dd-404d-a193-10dd4172fc8f',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('0a20017c-a191-4005-a802-fa15968bfe58','3ec11db4-f821-409f-84ad-07fc8e64d60d','fd89694b-06ef-4472-ac9f-614c2de3317b',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('d1d0a9ea-5950-4f58-9df0-dba51468bfc1','dd6c2ace-2593-445b-9569-55328090de99','6530aaba-4906-4d63-a6d3-deea01c99bea',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('7d05a302-c58d-460a-bf97-57af0dde1578','3ec11db4-f821-409f-84ad-07fc8e64d60d','243e6e83-ff11-4a30-af30-8751e8e63bd4',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('2a8c4fd4-18c4-4e3f-9507-f2d8d8e26572','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','f18133b7-ef83-4b2b-beff-9c3b5f99e55a',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('df4d542c-b4d4-4759-8472-7b36e8d77155','7ee486f1-4de8-4700-922b-863168f612a0','c7442d31-012a-40f6-ab04-600a70db8723',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('0f24cc24-bcc2-4451-85ad-e992ae17b2b7','58dcc836-51e1-4633-9a89-73ac44eb2152','e3071ca8-bedf-4eff-bda0-e9ff27f0e34c',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('2bf0c522-567f-4395-b90d-b84dffd3651b','58dcc836-51e1-4633-9a89-73ac44eb2152','422021c7-08e1-4355-838d-8f2821f00f42',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('f87dae68-4119-4c3d-b8bb-4ad95789876a','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','7ee486f1-4de8-4700-922b-863168f612a0',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('4a549dfd-3474-48f4-9f07-a1f4c8a561b6','899d79f7-8623-4442-a398-002178cf5d94','422021c7-08e1-4355-838d-8f2821f00f42',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('1fae9f12-aadf-4ae4-9926-78cadb2b9bb1','58dcc836-51e1-4633-9a89-73ac44eb2152','3320e408-93d8-4933-abb8-538a5d697b41',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('6999f806-3da6-4247-9280-c1a49f117ca1','4a366bb4-5104-45ea-ac9e-1da8e14387c3','c7442d31-012a-40f6-ab04-600a70db8723',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('ae2c1b54-d146-49d3-ad43-71107c47dc1c','58dcc836-51e1-4633-9a89-73ac44eb2152','a7f17fd7-3810-4866-9b51-8179157b4a2b',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('16bc6301-0c53-420c-b5c6-e835282d4de8','58dcc836-51e1-4633-9a89-73ac44eb2152','dcc3cae7-e05e-4ade-9b5b-c2eaade9f101',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('3e7ea30b-4a72-466d-9f9f-f8ea251337dc','7ee486f1-4de8-4700-922b-863168f612a0','43a09249-d81b-4897-b5c7-dd88331cf2bd',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('1c16edc9-5914-4da9-abcb-7b8e4e0de386','dd6c2ace-2593-445b-9569-55328090de99','19ddeb7f-91c1-4bd0-83ef-264eb78a3f75',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('32b4462a-bb56-4073-b763-12dffb811eb3','3ec11db4-f821-409f-84ad-07fc8e64d60d','d53d6be6-b36c-403f-b72d-d6160e9e52c1',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('8996baec-4803-452b-87ad-7ea4e8bed270','7ee486f1-4de8-4700-922b-863168f612a0','ba215fd2-cdfc-4b98-bd78-cfa667b1b371',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('b977f04a-42cb-4806-b007-29f2f9cdc810','899d79f7-8623-4442-a398-002178cf5d94','19ddeb7f-91c1-4bd0-83ef-264eb78a3f75',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('dc79af97-a059-4b2c-b887-17ab49e8e206','58dcc836-51e1-4633-9a89-73ac44eb2152','243e6e83-ff11-4a30-af30-8751e8e63bd4',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('3d07772c-8509-4d91-a6b5-f6dcd7f22f6c','58dcc836-51e1-4633-9a89-73ac44eb2152','8eb44185-f9bf-465e-8469-7bc422534319',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('52aa9e8b-1b63-41c6-903a-17d17209a041','4a366bb4-5104-45ea-ac9e-1da8e14387c3','7ee486f1-4de8-4700-922b-863168f612a0',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('f3d6f2ee-b332-4c34-9b4d-82b23993f9ef','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','def8c7af-d4fc-474e-974d-6fd00c251da8',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('db8a3b2d-e987-4b30-a215-6a659d1bbe17','7ee486f1-4de8-4700-922b-863168f612a0','3ece4e86-d328-4206-9f81-ec62bdf55335',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('9eb4825f-57f2-4915-9be4-2895315537ac','3ec11db4-f821-409f-84ad-07fc8e64d60d','5e8d8851-bf33-4d48-9860-acc24aceea3d',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('cffad308-bb8d-4dfd-8a08-bb3476a9d0fa','3ec11db4-f821-409f-84ad-07fc8e64d60d','b194b7a9-a759-4c12-9482-b99e43a52294',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('2ede526d-2076-415e-ae71-b706b720d4c2','58dcc836-51e1-4633-9a89-73ac44eb2152','fd57df67-e734-4eb2-80cf-2feafe91f238',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('70f68faf-d82e-4f50-b9ee-418f2810c752','dd6c2ace-2593-445b-9569-55328090de99','027f06cd-8c82-4c4a-a583-b20ccad9cc35',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('017f1ac3-c747-4f44-afe1-281e2df8167f','4a366bb4-5104-45ea-ac9e-1da8e14387c3','027f06cd-8c82-4c4a-a583-b20ccad9cc35',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('ca0b7e61-12ea-4fa5-abf2-74c26a0fd405','3ec11db4-f821-409f-84ad-07fc8e64d60d','182eb005-c185-418d-be8b-f47212c38af3',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('917cd205-7e8f-4f6f-a506-f882b6cbf3d4','7ee486f1-4de8-4700-922b-863168f612a0','9a9da923-06ef-47ea-bc20-23cc85b51ad0',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('3d828a0f-2fba-4e8e-a370-8f3702949ef9','3ec11db4-f821-409f-84ad-07fc8e64d60d','6455326e-cc11-4cfe-903b-ccce70e6f04e',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('9c9adb02-8dcb-4310-87fc-789a74d96c31','899d79f7-8623-4442-a398-002178cf5d94','1a170f85-e7f1-467c-a4dc-7d0b7898287e',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('06287bf2-8a8f-4e97-a48c-9146f3ddb0ec','58dcc836-51e1-4633-9a89-73ac44eb2152','6e43ffbc-1102-45dc-8fb2-139f6b616083',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('25988079-c2fe-40dd-af75-8f1adc4d5d89','7ee486f1-4de8-4700-922b-863168f612a0','47e88f74-4e28-4027-b05e-bf9adf63e572',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('5a4937b5-63a0-470f-a191-a80db38a0b18','3ec11db4-f821-409f-84ad-07fc8e64d60d','a7f17fd7-3810-4866-9b51-8179157b4a2b',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('2d766a80-794d-4992-8cc5-8dbefc995604','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','ea0fa1cc-7d80-4bd9-989e-f119c33fb881',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('b226b975-129a-41ae-8249-20812b58b39a','899d79f7-8623-4442-a398-002178cf5d94','ba215fd2-cdfc-4b98-bd78-cfa667b1b371',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('12e3033e-d07e-4eb9-ad02-6381a8f0e62d','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','6a0f9a02-b6ba-4585-9d7a-6959f7b0248f',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('304babdf-0247-4d0e-8180-c732db84b17b','7ee486f1-4de8-4700-922b-863168f612a0','2b1d1842-15f8-491a-bdce-e5f9fea947e7',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('abb3b48c-bad9-4fe7-b4a7-686509552f34','4a366bb4-5104-45ea-ac9e-1da8e14387c3','6e43ffbc-1102-45dc-8fb2-139f6b616083',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('dbd146f3-6d48-453a-b8c7-c0a5180b1ad2','4a366bb4-5104-45ea-ac9e-1da8e14387c3','0506bf0f-bc1c-43c7-a75f-639a1b4c0449',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('2b341419-1e41-4b6f-a670-1ffa9234ff18','4a366bb4-5104-45ea-ac9e-1da8e14387c3','9bb87311-1b29-4f29-8561-8a4c795654d4',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('07fd2b6c-bd73-4df1-9715-a55061c4bf6e','dd6c2ace-2593-445b-9569-55328090de99','635e4b79-342c-4cfc-8069-39c408a2decd',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('cb26bd16-2492-4ce8-8d9c-442ee66b4dc7','58dcc836-51e1-4633-9a89-73ac44eb2152','ca72968c-5921-4167-b7b6-837c88ca87f2',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('68647969-b590-4d50-83d2-a0ff1462191a','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','635e4b79-342c-4cfc-8069-39c408a2decd',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('f8adb0cb-2c61-463e-bda8-aa24ac767858','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','422021c7-08e1-4355-838d-8f2821f00f42',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('2e3fe068-3874-4a6f-aebf-3c6a3112da09','4a366bb4-5104-45ea-ac9e-1da8e14387c3','e5d41d36-b355-4407-9ede-cd435da69873',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('f2cda25e-cd13-463a-9890-9f86fa4e1a4c','dd6c2ace-2593-445b-9569-55328090de99','ee0ffe93-32b3-4817-982e-6d081da85d28',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('c46c88f5-d061-4d5c-a93d-d2cedc9e64a4','3ec11db4-f821-409f-84ad-07fc8e64d60d','098488af-82c9-49c6-9daa-879eff3d3bee',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('ee065cfb-1bb5-4f57-9040-26b8edaf9909','4a366bb4-5104-45ea-ac9e-1da8e14387c3','9a4aa0e1-6b5f-4624-a21c-3acfa858d7f3',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('6bf19370-b636-4738-acad-4c56ae177953','3ec11db4-f821-409f-84ad-07fc8e64d60d','6f0e02be-08ad-48b1-8e23-eecaab34b4fe',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('76b758e1-7d60-4363-97a3-a41ca8accbd2','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','535e6789-c126-405f-8b3a-7bd886b94796',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('d87f9757-7e20-4ab2-a08f-0e94326ced74','7ee486f1-4de8-4700-922b-863168f612a0','f18133b7-ef83-4b2b-beff-9c3b5f99e55a',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('5387cfbf-2469-4def-ada3-b8669ef5c308','3ec11db4-f821-409f-84ad-07fc8e64d60d','30040c3f-667d-4dee-ba4c-24aad0891c9c',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('3b5739a5-59a6-4ded-941b-56f388a0f20c','4a366bb4-5104-45ea-ac9e-1da8e14387c3','ba215fd2-cdfc-4b98-bd78-cfa667b1b371',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('20c61952-df10-446f-9a0c-b0d985226b54','3ec11db4-f821-409f-84ad-07fc8e64d60d','93052804-f158-485d-b3a5-f04fd0d41e55',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('e2a24dff-5e13-4e19-b2f7-f3465104bd39','7ee486f1-4de8-4700-922b-863168f612a0','47cbf0b7-e249-4b7e-8306-e5a2d2b3f394',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('1660a36a-78bb-4601-83fd-328339fa8583','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','dd6c2ace-2593-445b-9569-55328090de99',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('ee445c8d-bc4a-43dc-ab7e-a80a2acfdc74','dd6c2ace-2593-445b-9569-55328090de99','5bf18f68-55b8-4024-adb1-c2e6592a2582',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('38b7812c-2d15-4061-b413-a7b2caf2f8b9','3ec11db4-f821-409f-84ad-07fc8e64d60d','40ab17b2-9e79-429c-a75d-b6fcbbe27901',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('6ff5792f-75c1-42e9-9de7-869b11471d85','58dcc836-51e1-4633-9a89-73ac44eb2152','0026678a-51b7-46de-af3d-b49428e0916c',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('f7979e68-2f02-420e-8ec6-1a870294cad9','4a366bb4-5104-45ea-ac9e-1da8e14387c3','e4e467f2-449d-46e3-a59b-0f8714e4824a',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('059a6d35-199c-4852-8130-953b9772de7b','4a366bb4-5104-45ea-ac9e-1da8e14387c3','c18e25f9-ec34-41ca-8c1b-05558c8d6364',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('a1d0d0f8-dd40-4c1a-a534-7d284a87d7fe','dd6c2ace-2593-445b-9569-55328090de99','5a27e806-21d4-4672-aa5e-29518f10c0aa',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('a041a770-f233-424e-9d01-4e30a50ac535','4a366bb4-5104-45ea-ac9e-1da8e14387c3','7675199b-55b9-4184-bce8-a6c0c2c9e9ab',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('01f889f8-f1a8-4be7-8f6a-7344bb295962','58dcc836-51e1-4633-9a89-73ac44eb2152','46c16bc1-df71-4c6f-835b-400c8caaf984',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('6098a390-04af-487f-bf85-16c7ab84f893','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','93052804-f158-485d-b3a5-f04fd0d41e55',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('3a9ecb5f-8a24-4e4d-8ebb-67e8cfec5f8a','7ee486f1-4de8-4700-922b-863168f612a0','0cb31c3c-dfd2-4b2a-b475-d2023008eea4',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('81e49f93-6380-4e5e-ab4b-227f7e853afd','4a366bb4-5104-45ea-ac9e-1da8e14387c3','2a1b3667-e604-41a0-b741-ba19f1f56892',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('edc30a8b-81eb-40ce-9a3c-5d39de3a9988','4a366bb4-5104-45ea-ac9e-1da8e14387c3','c9036eb8-84bb-4909-be20-0662387219a7',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('eb741931-8615-4448-9f79-2f344612e734','3ec11db4-f821-409f-84ad-07fc8e64d60d','b80251b4-02a2-4122-add9-ab108cd011d7',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('000d6172-b536-43d5-a0d0-fa240071a43a','4a366bb4-5104-45ea-ac9e-1da8e14387c3','2124fcbf-be89-4975-9cc7-263ac14ad759',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('e767757d-25ba-4f18-935b-b827477d34bd','dd6c2ace-2593-445b-9569-55328090de99','4a239fdb-9ad7-4bbb-8685-528f3f861992',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('5229e5b5-ac45-4d38-a452-03fc74ba82ff','4a366bb4-5104-45ea-ac9e-1da8e14387c3','64265049-1b4a-4a96-9cba-e01f59cafcc7',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('2411d55a-2bb6-474d-ae27-8b5a1d29c63c','58dcc836-51e1-4633-9a89-73ac44eb2152','098488af-82c9-49c6-9daa-879eff3d3bee',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('e6287ce3-cb92-4ab8-9c1f-c11660bed9ae','4a366bb4-5104-45ea-ac9e-1da8e14387c3','03dd5854-8bc3-4b56-986e-eac513cc1ec0',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('15a3f84a-4e12-4014-9c86-f7f905c292a3','899d79f7-8623-4442-a398-002178cf5d94','5802e021-5283-4b43-ba85-31340065d5ec',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('aa0c005f-0565-4987-a985-f6f596d55f08','4a366bb4-5104-45ea-ac9e-1da8e14387c3','5e8d8851-bf33-4d48-9860-acc24aceea3d',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('a21c342a-d55b-4100-91d3-11ae79aeb74e','58dcc836-51e1-4633-9a89-73ac44eb2152','1beb0053-329a-4b47-879b-1a3046d3ff87',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('71f107bf-9862-4723-8587-54f8ba331e43','7ee486f1-4de8-4700-922b-863168f612a0','2124fcbf-be89-4975-9cc7-263ac14ad759',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('034d16e5-9c66-4f41-80d6-50ab810553c2','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','19ddeb7f-91c1-4bd0-83ef-264eb78a3f75',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('cea5c549-87ba-4e68-bed0-e69e1e898afa','899d79f7-8623-4442-a398-002178cf5d94','10644589-71f6-4baf-ba1c-dfb19d924b25',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('a6504c6e-160c-48f3-80eb-30467b95f89a','899d79f7-8623-4442-a398-002178cf5d94','b7329731-65df-4427-bdee-18a0ab51efb4',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('b6b57d13-c1a7-4511-89e8-3b6d36de9bd4','3ec11db4-f821-409f-84ad-07fc8e64d60d','d45cf336-8c4b-4651-b505-bbd34831d12d',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('496dbf6e-f3a3-46ae-8238-5beeb03e10df','7ee486f1-4de8-4700-922b-863168f612a0','71755cc7-0844-4523-a0ac-da9a1e743ad1',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('8e074bdc-a7ef-4ae0-96ca-7ce95ff5575c','dd6c2ace-2593-445b-9569-55328090de99','243e6e83-ff11-4a30-af30-8751e8e63bd4',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('cbe2dbbf-c10b-40f7-a36b-bf26171265a8','58dcc836-51e1-4633-9a89-73ac44eb2152','58dcc836-51e1-4633-9a89-73ac44eb2152',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('0d7665b8-7f28-4560-8eda-2d249c3ed423','3ec11db4-f821-409f-84ad-07fc8e64d60d','2b1d1842-15f8-491a-bdce-e5f9fea947e7',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('47e5708d-a3dc-49da-98d6-aefcf07bc797','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','0026678a-51b7-46de-af3d-b49428e0916c',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('53704c83-0fc8-4965-9ba7-d8a725dcf9a7','4a366bb4-5104-45ea-ac9e-1da8e14387c3','2b1d1842-15f8-491a-bdce-e5f9fea947e7',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('bb690185-1ce2-4e65-9267-2eec59b99c89','58dcc836-51e1-4633-9a89-73ac44eb2152','cfca47bf-4639-4b7c-aed9-5ff87c9cddde',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('876804fe-b2e8-4463-9290-a51508d588db','899d79f7-8623-4442-a398-002178cf5d94','c68e26d0-dc81-4320-bdd7-fa286f4cc891',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('56c2d363-8ad2-44ca-9639-97ee0aeafae8','899d79f7-8623-4442-a398-002178cf5d94','def8c7af-d4fc-474e-974d-6fd00c251da8',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('1f205c45-d803-4afb-bde3-3317f2c0de90','4a366bb4-5104-45ea-ac9e-1da8e14387c3','7582d86d-d4e7-4a88-997d-05593ccefb37',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('ad070e7d-08d3-4794-941f-7d6bea930c25','58dcc836-51e1-4633-9a89-73ac44eb2152','ba215fd2-cdfc-4b98-bd78-cfa667b1b371',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('630ae9aa-0616-4f97-99e6-48edea6fd01b','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','afb334ca-9466-44ec-9be1-4c881db6d060',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('24f36c41-9260-46d4-9a4e-9c469db2557f','58dcc836-51e1-4633-9a89-73ac44eb2152','3733db73-602a-4402-8f94-36eec2fdab15',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('10a9bbe1-1e2b-4cc3-b131-44f388a4394a','899d79f7-8623-4442-a398-002178cf5d94','4a366bb4-5104-45ea-ac9e-1da8e14387c3',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('04ac7d4c-03a6-46de-8e03-4fbbdbf0cec9','4a366bb4-5104-45ea-ac9e-1da8e14387c3','1beb0053-329a-4b47-879b-1a3046d3ff87',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('07334bdb-f767-414f-b0fd-1fc95acfa5a9','4a366bb4-5104-45ea-ac9e-1da8e14387c3','098488af-82c9-49c6-9daa-879eff3d3bee',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('c2678142-7b03-4870-8965-6484899ada8c','dd6c2ace-2593-445b-9569-55328090de99','dcc3cae7-e05e-4ade-9b5b-c2eaade9f101',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('855167b2-dd12-4955-b318-3c37c7c627f0','58dcc836-51e1-4633-9a89-73ac44eb2152','508d9830-6a60-44d3-992f-3c48c507f9f6',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('51d0c9c1-cfce-472b-9050-00136651d74d','4a366bb4-5104-45ea-ac9e-1da8e14387c3','5a27e806-21d4-4672-aa5e-29518f10c0aa',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('60807e03-1be0-411e-8b43-4f4ff7481507','dd6c2ace-2593-445b-9569-55328090de99','7ee486f1-4de8-4700-922b-863168f612a0',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('80e885c5-df6f-46f7-b645-7b7cb2df4403','dd6c2ace-2593-445b-9569-55328090de99','508d9830-6a60-44d3-992f-3c48c507f9f6',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('290d9157-16f8-4af9-b0e9-707e2a2fbc57','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','ee0ffe93-32b3-4817-982e-6d081da85d28',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('c16542f8-5c00-41ce-a9c4-c312e39d06a8','dd6c2ace-2593-445b-9569-55328090de99','c68e26d0-dc81-4320-bdd7-fa286f4cc891',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('fe8a86c7-9a70-42f3-99a9-fc63f6b4c773','899d79f7-8623-4442-a398-002178cf5d94','5a27e806-21d4-4672-aa5e-29518f10c0aa',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('a678e3cb-796a-484c-81c9-c1f312d4f336','3ec11db4-f821-409f-84ad-07fc8e64d60d','c3c46c6b-115a-4236-b88a-76126e7f9516',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('300457a3-57fb-4482-a43a-6e96bd6d6b75','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','146c58e5-c87d-4f54-a766-8da85c6b6b2c',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('4289cabb-ca97-4c8e-b7b9-a5ea5d17f1d5','58dcc836-51e1-4633-9a89-73ac44eb2152','e4e467f2-449d-46e3-a59b-0f8714e4824a',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('2c5e3db0-a242-4001-807c-bc26a75fff5b','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','7ac1c0ec-0903-477c-89e0-88efe9249c98',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('22b822e0-5a23-4097-b55e-a2b628dc02e0','58dcc836-51e1-4633-9a89-73ac44eb2152','9bb87311-1b29-4f29-8561-8a4c795654d4',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('d54dbfef-742f-47ac-8f65-a68e29533300','dd6c2ace-2593-445b-9569-55328090de99','b7329731-65df-4427-bdee-18a0ab51efb4',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('78a92331-587c-44f4-b584-1dff9a3fbfbf','899d79f7-8623-4442-a398-002178cf5d94','0506bf0f-bc1c-43c7-a75f-639a1b4c0449',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('f6abb47d-1edf-4326-9985-00d5932df8ff','7ee486f1-4de8-4700-922b-863168f612a0','709dad47-121a-4edd-ad95-b3dd6fd88f08',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('b8e42daa-6b7e-4ba2-9320-cb8df5488b0d','58dcc836-51e1-4633-9a89-73ac44eb2152','5802e021-5283-4b43-ba85-31340065d5ec',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('4e11fe1d-0503-4146-848f-5ffa76c738d5','58dcc836-51e1-4633-9a89-73ac44eb2152','146c58e5-c87d-4f54-a766-8da85c6b6b2c',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('f6682953-8cc5-4127-8f9c-3b1a265eba55','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','7d0fc5a1-719b-4070-a740-fe387075f0c3',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('500c2c90-4199-4445-af3f-d73ae81e9d5e','899d79f7-8623-4442-a398-002178cf5d94','fd89694b-06ef-4472-ac9f-614c2de3317b',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('8c4a1960-ce6e-4a35-9a82-114978bee16e','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','508d9830-6a60-44d3-992f-3c48c507f9f6',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('39863258-46db-4ecf-8cc4-f4bf4c8f33be','899d79f7-8623-4442-a398-002178cf5d94','46c16bc1-df71-4c6f-835b-400c8caaf984',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('d856b44f-941b-43a5-90c6-b5b800269583','4a366bb4-5104-45ea-ac9e-1da8e14387c3','4a366bb4-5104-45ea-ac9e-1da8e14387c3',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('6306dbc5-55a2-4df4-af1b-0fe6f43a1073','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','4a239fdb-9ad7-4bbb-8685-528f3f861992',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('4c7f3cba-a59b-48a2-b2f3-cfd6d30be79e','dd6c2ace-2593-445b-9569-55328090de99','811a32c0-90d6-4744-9a57-ab4130091754',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('1dac0f89-a439-40bd-9255-a707362f61a7','58dcc836-51e1-4633-9a89-73ac44eb2152','760f146d-d5e7-4e08-9464-45371ea3267d',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('a3481d4d-a635-4cdf-9ee4-383393cc0541','3ec11db4-f821-409f-84ad-07fc8e64d60d','71755cc7-0844-4523-a0ac-da9a1e743ad1',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('858d5f2b-6ed8-4a27-a2ec-c42cc9ba2321','dd6c2ace-2593-445b-9569-55328090de99','ca72968c-5921-4167-b7b6-837c88ca87f2',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('bf61704e-80db-4a46-a02c-435ec84ae93c','7ee486f1-4de8-4700-922b-863168f612a0','fe76b78f-67bc-4125-8f81-8e68697c136d',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('d8293854-0107-4ebc-b68d-84a7cc073534','899d79f7-8623-4442-a398-002178cf5d94','5e8d8851-bf33-4d48-9860-acc24aceea3d',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('71172f97-8ddd-492d-95f1-c9197a3784be','58dcc836-51e1-4633-9a89-73ac44eb2152','2124fcbf-be89-4975-9cc7-263ac14ad759',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('2b40b0fe-cff4-4a7d-8552-a52409fcc53d','899d79f7-8623-4442-a398-002178cf5d94','71755cc7-0844-4523-a0ac-da9a1e743ad1',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('d1eb3e5f-f398-4646-a89e-6ac704105729','4a366bb4-5104-45ea-ac9e-1da8e14387c3','6f0e02be-08ad-48b1-8e23-eecaab34b4fe',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('f9762070-2891-40cd-8a16-6d468612577b','4a366bb4-5104-45ea-ac9e-1da8e14387c3','cfe9ab8a-a353-433e-8204-c065deeae3d9',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('9f73cd91-6951-4361-87bc-7e1f1b80acae','4a366bb4-5104-45ea-ac9e-1da8e14387c3','afb334ca-9466-44ec-9be1-4c881db6d060',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('dcd665a5-c262-48a8-b322-b6fdc8a2703a','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','3320e408-93d8-4933-abb8-538a5d697b41',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('4875f120-fb6c-4407-9c76-67ac076aed33','3ec11db4-f821-409f-84ad-07fc8e64d60d','4f2e3e38-6bf4-4e74-bd7b-fe6edb87ee42',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('123c3589-b017-43db-99fa-dfef5f1f4727','58dcc836-51e1-4633-9a89-73ac44eb2152','4f2e3e38-6bf4-4e74-bd7b-fe6edb87ee42',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('80b8d262-4048-40cf-a447-bdbb232574b6','3ec11db4-f821-409f-84ad-07fc8e64d60d','9893a927-6084-482c-8f1c-e85959eb3547',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('dd5a1311-7936-4fb8-8836-46699526dca0','3ec11db4-f821-409f-84ad-07fc8e64d60d','508d9830-6a60-44d3-992f-3c48c507f9f6',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('f1d0f9f6-7052-4148-a944-988fc2200806','dd6c2ace-2593-445b-9569-55328090de99','0506bf0f-bc1c-43c7-a75f-639a1b4c0449',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('7f0a49cb-de3d-4212-a68b-d71b7a6da0b4','dd6c2ace-2593-445b-9569-55328090de99','46c16bc1-df71-4c6f-835b-400c8caaf984',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('0e32aaa1-06e1-4693-9cbc-9685d4661e21','899d79f7-8623-4442-a398-002178cf5d94','6455326e-cc11-4cfe-903b-ccce70e6f04e',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('cf694467-8b1b-4e42-9bc2-afa8eabbc2de','7ee486f1-4de8-4700-922b-863168f612a0','64265049-1b4a-4a96-9cba-e01f59cafcc7',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('08a0c5c1-7d2e-48b8-be79-2aba26c161cb','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','4fb560d1-6bf5-46b7-a047-d381a76c4fef',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('cb8c2dec-dec4-4023-98cd-56127897c1bb','899d79f7-8623-4442-a398-002178cf5d94','2124fcbf-be89-4975-9cc7-263ac14ad759',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('69f5cc07-e796-4704-aa89-9d139f025c8a','7ee486f1-4de8-4700-922b-863168f612a0','635e4b79-342c-4cfc-8069-39c408a2decd',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('2770e561-3cd2-4252-b737-89c9a9e9182c','899d79f7-8623-4442-a398-002178cf5d94','635e4b79-342c-4cfc-8069-39c408a2decd',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('3bf5e2fc-d7a5-49d7-8c0d-d2a888cab7dd','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','b80a00d4-f829-4051-961a-b8945c62c37d',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('ce47a81b-9bed-4d33-bcd4-d01ed0f10ed2','58dcc836-51e1-4633-9a89-73ac44eb2152','b7329731-65df-4427-bdee-18a0ab51efb4',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('4fc0bdc7-f83b-45b2-a502-a2673e56e40d','4a366bb4-5104-45ea-ac9e-1da8e14387c3','40da86e6-76e5-443b-b4ca-27ad31a2baf6',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('0c674f59-e71f-4259-b315-4b46cbbe2d7a','58dcc836-51e1-4633-9a89-73ac44eb2152','f18133b7-ef83-4b2b-beff-9c3b5f99e55a',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('690abf44-9c8e-4a18-8324-9f74a0f55ab7','dd6c2ace-2593-445b-9569-55328090de99','03dd5854-8bc3-4b56-986e-eac513cc1ec0',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('19ca1a5e-00ff-47aa-84aa-c527bea6ab0c','4a366bb4-5104-45ea-ac9e-1da8e14387c3','2c144ea1-9b49-4842-ad56-e5120912fd18',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('55adb39c-dac7-4321-8394-6845585d88db','dd6c2ace-2593-445b-9569-55328090de99','b80a00d4-f829-4051-961a-b8945c62c37d',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('922dc01d-f191-4e18-a794-e2a9a8933fe2','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','5e8d8851-bf33-4d48-9860-acc24aceea3d',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('cef4e5c0-6db0-4b8b-a0df-9eb2ce42416a','7ee486f1-4de8-4700-922b-863168f612a0','40da86e6-76e5-443b-b4ca-27ad31a2baf6',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('34503c8b-29de-4e35-8811-14ad8a713746','dd6c2ace-2593-445b-9569-55328090de99','fd89694b-06ef-4472-ac9f-614c2de3317b',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('54cb7e64-7788-4107-8e3a-2e18aa753894','899d79f7-8623-4442-a398-002178cf5d94','fe76b78f-67bc-4125-8f81-8e68697c136d',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('4d9caa94-a56c-4b51-a3c9-0a2b2cab7dd6','4a366bb4-5104-45ea-ac9e-1da8e14387c3','40ab17b2-9e79-429c-a75d-b6fcbbe27901',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('90781b15-9d11-45af-9021-db7bd27e2473','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','71755cc7-0844-4523-a0ac-da9a1e743ad1',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('cd2db5b4-78f5-428c-b6c6-619bba1b8955','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','64265049-1b4a-4a96-9cba-e01f59cafcc7',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('bd4ebf06-8295-4c7b-8de4-8da031fc5aa0','4a366bb4-5104-45ea-ac9e-1da8e14387c3','f42c9e51-5b7e-4ab3-847d-fd86b4e90dc1',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('3206135f-5929-42ec-b9f8-4bdd0167644e','3ec11db4-f821-409f-84ad-07fc8e64d60d','0026678a-51b7-46de-af3d-b49428e0916c',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('67731db1-9335-412c-aa28-cd830d31e06c','4a366bb4-5104-45ea-ac9e-1da8e14387c3','01d0be5d-aaec-483d-a841-6ab1301aa9bd',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('9fbe10f3-4e05-43af-8b64-094fce20d3bf','dd6c2ace-2593-445b-9569-55328090de99','f79dd433-2808-4f20-91ef-6b5efca07350',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('3144a4e8-ec34-4bff-a37b-1ab452d465bc','4a366bb4-5104-45ea-ac9e-1da8e14387c3','760f146d-d5e7-4e08-9464-45371ea3267d',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('a896b454-763c-4e5a-8aca-f563e6a1a71c','899d79f7-8623-4442-a398-002178cf5d94','f18133b7-ef83-4b2b-beff-9c3b5f99e55a',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('9d62bc12-290b-40be-8d3d-5bb139ab5cd9','7ee486f1-4de8-4700-922b-863168f612a0','c4c73fcb-be11-4b1a-986a-a73451d402a7',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('c5d0a4d2-4a15-4e3f-b341-90d980b5d1d4','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','30040c3f-667d-4dee-ba4c-24aad0891c9c',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('a3f1b0ce-5e1d-48b9-9e49-4f20ef40c5ba','58dcc836-51e1-4633-9a89-73ac44eb2152','7675199b-55b9-4184-bce8-a6c0c2c9e9ab',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('7a26d902-2c7a-43a9-bffa-7fa7b0b107de','899d79f7-8623-4442-a398-002178cf5d94','cae0eb53-a023-434c-ac8c-d0641067d8d8',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('2d9bdf8c-7588-4c9c-88a2-da99c6f1981d','58dcc836-51e1-4633-9a89-73ac44eb2152','5e8d8851-bf33-4d48-9860-acc24aceea3d',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('0b80e595-c315-4aec-ba14-d76fd1e43ed5','dd6c2ace-2593-445b-9569-55328090de99','fd57df67-e734-4eb2-80cf-2feafe91f238',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('b078183c-26be-43fa-9b4d-eac3cb7937f5','4a366bb4-5104-45ea-ac9e-1da8e14387c3','f18133b7-ef83-4b2b-beff-9c3b5f99e55a',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('78dadee7-0ab6-43ae-9a42-757d2c60b242','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','243e6e83-ff11-4a30-af30-8751e8e63bd4',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('66fb3fe6-4d8d-4adb-9676-dbde21265684','58dcc836-51e1-4633-9a89-73ac44eb2152','ee0ffe93-32b3-4817-982e-6d081da85d28',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('730ffea8-4f18-4552-a0bf-9cc87bea1b7f','3ec11db4-f821-409f-84ad-07fc8e64d60d','0cb31c3c-dfd2-4b2a-b475-d2023008eea4',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('b8146c87-c760-49e0-98a5-d29d2edf2559','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','fd57df67-e734-4eb2-80cf-2feafe91f238',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('0d74fb00-44de-426f-a051-fe1feb1c8883','58dcc836-51e1-4633-9a89-73ac44eb2152','2c144ea1-9b49-4842-ad56-e5120912fd18',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('4c310e7a-9a88-4f76-bf5a-75d6dade2ac0','7ee486f1-4de8-4700-922b-863168f612a0','6e43ffbc-1102-45dc-8fb2-139f6b616083',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('34195feb-eb38-4dee-add0-f16089d1220e','7ee486f1-4de8-4700-922b-863168f612a0','afb334ca-9466-44ec-9be1-4c881db6d060',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('021b6c60-996e-4fcf-b17f-822fc1b9b7b2','dd6c2ace-2593-445b-9569-55328090de99','cfca47bf-4639-4b7c-aed9-5ff87c9cddde',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('1d01449b-b78a-428a-be1b-ec8ecdd39481','3ec11db4-f821-409f-84ad-07fc8e64d60d','cfca47bf-4639-4b7c-aed9-5ff87c9cddde',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('c6aa2718-ed8b-4ad0-8065-8b3a90dfe17b','3ec11db4-f821-409f-84ad-07fc8e64d60d','8eb44185-f9bf-465e-8469-7bc422534319',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('84e1065a-5062-4eef-babd-53c9599a6434','3ec11db4-f821-409f-84ad-07fc8e64d60d','c18e25f9-ec34-41ca-8c1b-05558c8d6364',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('cb15952b-212b-4847-9f53-99f987c1a13d','3ec11db4-f821-409f-84ad-07fc8e64d60d','2a1b3667-e604-41a0-b741-ba19f1f56892',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('4f3952cd-69b0-444f-bffe-5ddf76b84030','4a366bb4-5104-45ea-ac9e-1da8e14387c3','8abaed50-eac1-4f40-83db-c07d2c3a123a',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('a728e56b-790b-4fff-8611-09000721e12a','4a366bb4-5104-45ea-ac9e-1da8e14387c3','635e4b79-342c-4cfc-8069-39c408a2decd',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('7d036277-589a-432a-af54-3866a231508f','4a366bb4-5104-45ea-ac9e-1da8e14387c3','5802e021-5283-4b43-ba85-31340065d5ec',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('2965e2f5-f761-4b70-82d6-5865286030a5','dd6c2ace-2593-445b-9569-55328090de99','93052804-f158-485d-b3a5-f04fd0d41e55',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('b53ac194-6bf4-432c-8a99-5bb31ac27ba8','4a366bb4-5104-45ea-ac9e-1da8e14387c3','3ece4e86-d328-4206-9f81-ec62bdf55335',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('668e61ae-fe3d-4b28-8c07-400b3c658f05','dd6c2ace-2593-445b-9569-55328090de99','01d0be5d-aaec-483d-a841-6ab1301aa9bd',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('5fd0b2d5-9898-44b3-8279-edcf8a663fbd','3ec11db4-f821-409f-84ad-07fc8e64d60d','c9036eb8-84bb-4909-be20-0662387219a7',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('073e49bb-fba9-4f81-9870-754ddda2cdf7','3ec11db4-f821-409f-84ad-07fc8e64d60d','1e23a20c-2558-47bf-b720-d7758b717ce3',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('45474626-398f-4be8-b4f3-d62eb0cd37bd','899d79f7-8623-4442-a398-002178cf5d94','03dd5854-8bc3-4b56-986e-eac513cc1ec0',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('3993353b-736c-4aaa-88c0-36e03756a383','58dcc836-51e1-4633-9a89-73ac44eb2152','47e88f74-4e28-4027-b05e-bf9adf63e572',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('ee587cf7-a4be-4f3b-a6af-3aa81cea8bf4','dd6c2ace-2593-445b-9569-55328090de99','1beb0053-329a-4b47-879b-1a3046d3ff87',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('6ce9e236-7b27-4da6-9abf-584eefe80e96','58dcc836-51e1-4633-9a89-73ac44eb2152','cae0eb53-a023-434c-ac8c-d0641067d8d8',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('cf203d97-d5bb-4451-b2a4-426829c08974','899d79f7-8623-4442-a398-002178cf5d94','098488af-82c9-49c6-9daa-879eff3d3bee',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('0099330d-38d6-4a03-8b5d-560b78f2bee5','3ec11db4-f821-409f-84ad-07fc8e64d60d','10644589-71f6-4baf-ba1c-dfb19d924b25',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('d459e5a4-14e6-42de-b1d9-1d4ecc3723d9','7ee486f1-4de8-4700-922b-863168f612a0','cfe9ab8a-a353-433e-8204-c065deeae3d9',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('0800451c-d73f-4e97-983b-0cff5dbd5a43','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','01d0be5d-aaec-483d-a841-6ab1301aa9bd',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('83ede020-3a1e-4c10-983e-e8444c952e1f','58dcc836-51e1-4633-9a89-73ac44eb2152','182eb005-c185-418d-be8b-f47212c38af3',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('f737acf2-c646-4552-8d96-4d32f875cb70','dd6c2ace-2593-445b-9569-55328090de99','40da86e6-76e5-443b-b4ca-27ad31a2baf6',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('620834cf-c46b-469b-8ae0-db08f1c4eac7','7ee486f1-4de8-4700-922b-863168f612a0','ca72968c-5921-4167-b7b6-837c88ca87f2',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('8ad4cbfb-cbb1-48b8-b784-bcfa3fa9a5f0','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','47e88f74-4e28-4027-b05e-bf9adf63e572',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('096dccd8-6db9-4eee-afd3-c1c7d26d555e','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','03dd5854-8bc3-4b56-986e-eac513cc1ec0',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('d932c922-f21b-41a9-9fa2-1a731e29fb85','7ee486f1-4de8-4700-922b-863168f612a0','ddd74fb8-c0f1-41a9-9d4f-234bd295ae1a',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('a34c288e-7109-48b9-af0d-59cf2a8bdc19','3ec11db4-f821-409f-84ad-07fc8e64d60d','311e5909-df08-4086-aa09-4c21a48b5e6e',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('3951c2cd-2cca-44f3-b2eb-32ea3ceeed08','58dcc836-51e1-4633-9a89-73ac44eb2152','b3911f28-d334-4cca-8924-7da60ea5a213',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('bf40ddf7-2215-4fd6-9e64-04b3a5e9f36f','dd6c2ace-2593-445b-9569-55328090de99','816f84d1-ea01-47a0-a799-4b68508e35cc',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('08fc1e89-952a-4611-93c0-30df01ba9211','58dcc836-51e1-4633-9a89-73ac44eb2152','7ee486f1-4de8-4700-922b-863168f612a0',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('71bc740f-a3d4-4449-964d-a9ee01ea6a41','7ee486f1-4de8-4700-922b-863168f612a0','9a4aa0e1-6b5f-4624-a21c-3acfa858d7f3',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('8978ffa3-5645-4107-89c2-c35a53710892','3ec11db4-f821-409f-84ad-07fc8e64d60d','47cbf0b7-e249-4b7e-8306-e5a2d2b3f394',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('6490e626-7517-4327-9388-c2cf1034a97a','899d79f7-8623-4442-a398-002178cf5d94','c18e25f9-ec34-41ca-8c1b-05558c8d6364',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('fa40ec4f-7e88-4a32-8870-c36c96a30322','4a366bb4-5104-45ea-ac9e-1da8e14387c3','02cc7df6-83d0-4ff1-a5ea-8240f5434e73',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('91b3da58-7e0c-4d11-8667-08a782b945d8','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','fe76b78f-67bc-4125-8f81-8e68697c136d',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('33185d90-6564-4943-9ff1-d230d7f46630','899d79f7-8623-4442-a398-002178cf5d94','182eb005-c185-418d-be8b-f47212c38af3',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('43887091-6486-459c-b559-af91b815a3a3','7ee486f1-4de8-4700-922b-863168f612a0','def8c7af-d4fc-474e-974d-6fd00c251da8',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('7229f7ec-b135-4204-af3b-7c59dd43cd9d','7ee486f1-4de8-4700-922b-863168f612a0','a4fa6b22-3d7f-4d56-96f1-941f9e7570aa',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('535e07d7-823c-4c4d-b88f-8d25c3bca4d8','7ee486f1-4de8-4700-922b-863168f612a0','cfca47bf-4639-4b7c-aed9-5ff87c9cddde',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('b4fd0838-25b2-4275-a715-bb7d7caf2e4f','3ec11db4-f821-409f-84ad-07fc8e64d60d','47e88f74-4e28-4027-b05e-bf9adf63e572',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('cf68a884-3650-4ebc-8506-598c059ddd29','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','40ab17b2-9e79-429c-a75d-b6fcbbe27901',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('c80c1fec-61e1-43a4-be23-b2e53b684735','4a366bb4-5104-45ea-ac9e-1da8e14387c3','dcc3cae7-e05e-4ade-9b5b-c2eaade9f101',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('7e4c34b0-12a2-4751-b6f1-87ba4abe1c5e','3ec11db4-f821-409f-84ad-07fc8e64d60d','c4c73fcb-be11-4b1a-986a-a73451d402a7',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('7d214d40-b92f-45ff-87d0-feb7257164b4','7ee486f1-4de8-4700-922b-863168f612a0','311e5909-df08-4086-aa09-4c21a48b5e6e',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('a3c6f515-dc57-4993-a106-24e27a4065d3','4a366bb4-5104-45ea-ac9e-1da8e14387c3','7ac1c0ec-0903-477c-89e0-88efe9249c98',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('ff47bc5d-be26-4dc4-ad1c-dc26651e9210','dd6c2ace-2593-445b-9569-55328090de99','2c144ea1-9b49-4842-ad56-e5120912fd18',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('b0099867-8c37-4076-b16a-4956dfb8670c','4a366bb4-5104-45ea-ac9e-1da8e14387c3','cae0eb53-a023-434c-ac8c-d0641067d8d8',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('61f16104-c4d5-42d1-80ed-0b6d723ce2db','3ec11db4-f821-409f-84ad-07fc8e64d60d','6a0f9a02-b6ba-4585-9d7a-6959f7b0248f',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('325b32e5-c2cd-42cb-9227-2f7cc0a5ec42','7ee486f1-4de8-4700-922b-863168f612a0','531e3a04-e84c-45d9-86bf-c6da0820b605',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('be5f81ee-1c1c-4134-a1da-bc63ece1dcd3','58dcc836-51e1-4633-9a89-73ac44eb2152','40da86e6-76e5-443b-b4ca-27ad31a2baf6',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('392add76-295d-4b52-98a9-4d3a748ff83c','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','ddd74fb8-c0f1-41a9-9d4f-234bd295ae1a',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('65b1dfc7-2a07-49f0-bcf6-1dc5a9d0da39','dd6c2ace-2593-445b-9569-55328090de99','422021c7-08e1-4355-838d-8f2821f00f42',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('b0ffc768-7927-41fd-af66-bc5a0f7c706f','3ec11db4-f821-409f-84ad-07fc8e64d60d','b7329731-65df-4427-bdee-18a0ab51efb4',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('55e27629-3f23-4fa4-bece-3bace1120644','3ec11db4-f821-409f-84ad-07fc8e64d60d','4a239fdb-9ad7-4bbb-8685-528f3f861992',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('e7018752-8c06-4465-9ce0-48d0a1aba1d0','58dcc836-51e1-4633-9a89-73ac44eb2152','1e23a20c-2558-47bf-b720-d7758b717ce3',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('45a33bdb-7272-4a32-a262-4808eb42afaa','899d79f7-8623-4442-a398-002178cf5d94','531e3a04-e84c-45d9-86bf-c6da0820b605',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('f3881991-3369-4f31-8012-c7b0b825a8c3','3ec11db4-f821-409f-84ad-07fc8e64d60d','e3071ca8-bedf-4eff-bda0-e9ff27f0e34c',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('b5f0d40c-9e4d-4b51-8eca-ae38ccfadd3f','7ee486f1-4de8-4700-922b-863168f612a0','098488af-82c9-49c6-9daa-879eff3d3bee',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('5c18c569-d9b9-44a9-b234-dcb0306d8cc4','7ee486f1-4de8-4700-922b-863168f612a0','a7f17fd7-3810-4866-9b51-8179157b4a2b',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('89113660-8252-4ddf-8a18-61a6a4f56ff4','3ec11db4-f821-409f-84ad-07fc8e64d60d','899d79f7-8623-4442-a398-002178cf5d94',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('c70cb3c4-4d52-4c89-b201-14435efdd3a3','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','829d8b45-19c1-49a3-920c-cc0ae14e8698',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('8775e5d4-f0a5-4564-b833-00e4ecef1e9a','dd6c2ace-2593-445b-9569-55328090de99','1a170f85-e7f1-467c-a4dc-7d0b7898287e',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('b11b756f-3365-4600-ac3b-647469acad99','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','027f06cd-8c82-4c4a-a583-b20ccad9cc35',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('84507356-f198-4c3c-8721-e790caab43ca','7ee486f1-4de8-4700-922b-863168f612a0','4fb560d1-6bf5-46b7-a047-d381a76c4fef',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('eb8fa2ee-99b9-4f21-b3f0-f8e87a063502','3ec11db4-f821-409f-84ad-07fc8e64d60d','dcc3cae7-e05e-4ade-9b5b-c2eaade9f101',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('bef04abf-af47-45a3-b9cf-359e13dc9212','3ec11db4-f821-409f-84ad-07fc8e64d60d','9b6832a8-eb82-4afa-b12f-b52a3b2cda75',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('9cd79d29-8765-45b5-b09a-c3d500041a66','58dcc836-51e1-4633-9a89-73ac44eb2152','4f16c772-1df4-4922-a9e1-761ca829bb85',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('f41f7cd0-a003-4c4a-9d6f-c7de315534ab','dd6c2ace-2593-445b-9569-55328090de99','b80251b4-02a2-4122-add9-ab108cd011d7',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('7949bdd3-c1b1-414b-8b4e-09d3d725a109','4a366bb4-5104-45ea-ac9e-1da8e14387c3','b3911f28-d334-4cca-8924-7da60ea5a213',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('1ff03f98-1d7b-4419-a96c-aa30abd9a46c','dd6c2ace-2593-445b-9569-55328090de99','899d79f7-8623-4442-a398-002178cf5d94',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('f999b293-a26c-4752-b081-f9627e007194','58dcc836-51e1-4633-9a89-73ac44eb2152','6455326e-cc11-4cfe-903b-ccce70e6f04e',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('f8c3219d-be88-4cf0-b41b-0dadbd4ab594','58dcc836-51e1-4633-9a89-73ac44eb2152','91eb2878-0368-4347-97e3-e6caa362d878',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('3b47bc18-3e35-42a0-98b2-843f8cf2be23','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','0cb31c3c-dfd2-4b2a-b475-d2023008eea4',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('af93840d-0c81-4830-9f7e-60781b9a1edf','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','0ba534f5-0d24-4d7c-9216-d07f57cd8edd',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('d01d7e15-2881-4035-a2b6-5526ab640cba','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','2c144ea1-9b49-4842-ad56-e5120912fd18',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('e22a55c1-d9a1-4127-99fc-a83a71eb3f0e','3ec11db4-f821-409f-84ad-07fc8e64d60d','811a32c0-90d6-4744-9a57-ab4130091754',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('c74d43cf-993a-4be0-bfa6-5fa7d83ff1ac','899d79f7-8623-4442-a398-002178cf5d94','7d0fc5a1-719b-4070-a740-fe387075f0c3',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('e2127e22-5c79-4935-b9af-52de1139e624','7ee486f1-4de8-4700-922b-863168f612a0','0ba534f5-0d24-4d7c-9216-d07f57cd8edd',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('2111d289-2990-43c2-a2c9-b112c13f11cf','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','1beb0053-329a-4b47-879b-1a3046d3ff87',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('2807b0bc-b58c-40b8-ba00-0484de15fd86','3ec11db4-f821-409f-84ad-07fc8e64d60d','02cc7df6-83d0-4ff1-a5ea-8240f5434e73',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('8f5e375c-8657-41c2-8ccd-06bc3c67ef09','4a366bb4-5104-45ea-ac9e-1da8e14387c3','1e23a20c-2558-47bf-b720-d7758b717ce3',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('2959da0f-e66b-41b3-ab40-62aff92eef82','899d79f7-8623-4442-a398-002178cf5d94','899d79f7-8623-4442-a398-002178cf5d94',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('f94ffd16-fd8a-44ec-bda3-fe64ef939248','899d79f7-8623-4442-a398-002178cf5d94','535e6789-c126-405f-8b3a-7bd886b94796',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('7052114e-4268-458a-9730-bdbd82ab8cd2','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','3ece4e86-d328-4206-9f81-ec62bdf55335',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('b7763c4a-1401-4675-b895-8e8809fddcbf','899d79f7-8623-4442-a398-002178cf5d94','cfe9ab8a-a353-433e-8204-c065deeae3d9',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('c5379622-29d6-4939-a8be-ca3f2c8d69ce','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','9893a927-6084-482c-8f1c-e85959eb3547',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('6445267a-41cf-40db-9633-e5c60ac92190','7ee486f1-4de8-4700-922b-863168f612a0','1a170f85-e7f1-467c-a4dc-7d0b7898287e',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('52d84ed7-430d-4433-b7ab-20654c8c63c6','58dcc836-51e1-4633-9a89-73ac44eb2152','612c2ce9-39cc-45e6-a3f1-c6672267d392',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('30af67f1-565c-40de-9e4e-d2a0acc40ff8','4a366bb4-5104-45ea-ac9e-1da8e14387c3','19ddeb7f-91c1-4bd0-83ef-264eb78a3f75',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('aca6af03-2382-4837-9cb3-ccfb4be7ec46','dd6c2ace-2593-445b-9569-55328090de99','fe76b78f-67bc-4125-8f81-8e68697c136d',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('d36b1515-97b5-46c0-b3ab-07f42dc8f3b5','899d79f7-8623-4442-a398-002178cf5d94','ddd74fb8-c0f1-41a9-9d4f-234bd295ae1a',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('fa8207ce-4659-4d19-8789-dcb47af60417','dd6c2ace-2593-445b-9569-55328090de99','d45cf336-8c4b-4651-b505-bbd34831d12d',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('93361e8d-9d09-46c5-bfe6-99f8b13cdbf6','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','c3c46c6b-115a-4236-b88a-76126e7f9516',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('0bd88d25-2480-4765-b527-49fd42bbfcfe','7ee486f1-4de8-4700-922b-863168f612a0','02cc7df6-83d0-4ff1-a5ea-8240f5434e73',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('2c62fb78-ed81-42a4-ac6c-591ef56426e7','dd6c2ace-2593-445b-9569-55328090de99','f18133b7-ef83-4b2b-beff-9c3b5f99e55a',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('ae946157-f53f-4c55-b32a-d6140a8db37c','dd6c2ace-2593-445b-9569-55328090de99','ddd74fb8-c0f1-41a9-9d4f-234bd295ae1a',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('a7303a1e-314e-4d0c-873b-6293678bd168','899d79f7-8623-4442-a398-002178cf5d94','30040c3f-667d-4dee-ba4c-24aad0891c9c',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('f6b29f3e-079f-4f8d-8ee7-bf3ab928e9bd','7ee486f1-4de8-4700-922b-863168f612a0','146c58e5-c87d-4f54-a766-8da85c6b6b2c',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('a768b2cb-09a8-4d0f-b4e6-ba6d6003b58f','899d79f7-8623-4442-a398-002178cf5d94','afb334ca-9466-44ec-9be1-4c881db6d060',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('4ca6e00f-0952-479c-b29a-70dfb7bde552','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','c68e26d0-dc81-4320-bdd7-fa286f4cc891',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('db81f4e4-0ed7-48ee-9595-dce0bb734e3c','dd6c2ace-2593-445b-9569-55328090de99','7675199b-55b9-4184-bce8-a6c0c2c9e9ab',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('dfd784f6-4d4b-4cee-ac56-1b9e53a28fe2','899d79f7-8623-4442-a398-002178cf5d94','c3c46c6b-115a-4236-b88a-76126e7f9516',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('673491bf-c63a-4f71-ad3a-403dc9424ca5','3ec11db4-f821-409f-84ad-07fc8e64d60d','829d8b45-19c1-49a3-920c-cc0ae14e8698',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('54d63c8f-3f50-4bdd-8708-bbee0d7bd6a9','7ee486f1-4de8-4700-922b-863168f612a0','7675199b-55b9-4184-bce8-a6c0c2c9e9ab',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('b17dcae5-75cd-49f0-8a65-77c1faa499b7','899d79f7-8623-4442-a398-002178cf5d94','1beb0053-329a-4b47-879b-1a3046d3ff87',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('8c0ff2c4-1120-40ad-a259-3b87a78aa90b','4a366bb4-5104-45ea-ac9e-1da8e14387c3','6a0f9a02-b6ba-4585-9d7a-6959f7b0248f',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('661ef5b2-ee32-46d9-8f69-2ed05516ac42','7ee486f1-4de8-4700-922b-863168f612a0','b80a00d4-f829-4051-961a-b8945c62c37d',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('f805d04c-9888-405a-a874-d41cfcf76a08','4a366bb4-5104-45ea-ac9e-1da8e14387c3','c5aab403-d0e2-4e6e-b3f1-57fc52e6c2bd',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('ca9c8915-d77b-4517-8683-d606ea1613bb','58dcc836-51e1-4633-9a89-73ac44eb2152','829d8b45-19c1-49a3-920c-cc0ae14e8698',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('d7b1174b-e6dd-436c-8708-6765e687357c','4a366bb4-5104-45ea-ac9e-1da8e14387c3','f79dd433-2808-4f20-91ef-6b5efca07350',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('4122279c-7f79-464c-bb40-639743721cea','4a366bb4-5104-45ea-ac9e-1da8e14387c3','4a239fdb-9ad7-4bbb-8685-528f3f861992',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('131101b9-d546-4b96-baf7-2d396063eac9','3ec11db4-f821-409f-84ad-07fc8e64d60d','afb334ca-9466-44ec-9be1-4c881db6d060',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('fbc2cf02-7c5a-43ad-9179-cdeeb9fae996','58dcc836-51e1-4633-9a89-73ac44eb2152','7582d86d-d4e7-4a88-997d-05593ccefb37',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('e6a17f01-eb1e-4d50-b26d-5d9fcfa5d8d3','58dcc836-51e1-4633-9a89-73ac44eb2152','c3c46c6b-115a-4236-b88a-76126e7f9516',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('bba3c26d-14b8-4cf0-b03a-12bee9e487cf','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','9a4aa0e1-6b5f-4624-a21c-3acfa858d7f3',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('20577554-cd1d-4df8-90dd-3df340f10e57','58dcc836-51e1-4633-9a89-73ac44eb2152','9893a927-6084-482c-8f1c-e85959eb3547',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('7080c86b-16ef-4d21-a8b6-9675227c9b20','7ee486f1-4de8-4700-922b-863168f612a0','01d0be5d-aaec-483d-a841-6ab1301aa9bd',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('1c229942-f370-4cc7-9481-edf4b8f779a5','7ee486f1-4de8-4700-922b-863168f612a0','e3071ca8-bedf-4eff-bda0-e9ff27f0e34c',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('8f61be43-2e54-4cdb-a919-f1eb96d1e9f1','58dcc836-51e1-4633-9a89-73ac44eb2152','4fb560d1-6bf5-46b7-a047-d381a76c4fef',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('e857081f-51d8-4fb8-895d-1e5171de7eea','7ee486f1-4de8-4700-922b-863168f612a0','9893a927-6084-482c-8f1c-e85959eb3547',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('e1f34681-9076-47e7-a677-3c4ab204ba52','3ec11db4-f821-409f-84ad-07fc8e64d60d','ca72968c-5921-4167-b7b6-837c88ca87f2',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('2f38d629-ab8f-4ede-960a-d3176db7910c','dd6c2ace-2593-445b-9569-55328090de99','dd6c2ace-2593-445b-9569-55328090de99',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('ecda7e1f-0793-4ca0-9c51-0fe01316f105','58dcc836-51e1-4633-9a89-73ac44eb2152','ea0fa1cc-7d80-4bd9-989e-f119c33fb881',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('97d367b1-608c-403f-b47a-48616d685c7d','dd6c2ace-2593-445b-9569-55328090de99','7d0fc5a1-719b-4070-a740-fe387075f0c3',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('b58b7590-dad4-4fd3-8484-c0e12d02b161','899d79f7-8623-4442-a398-002178cf5d94','2a1b3667-e604-41a0-b741-ba19f1f56892',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('ca13ff1d-d0f0-4fbb-994e-b09af94c5485','4a366bb4-5104-45ea-ac9e-1da8e14387c3','ea0fa1cc-7d80-4bd9-989e-f119c33fb881',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('e4d71621-8450-4cae-ad07-c7c9ee691de6','7ee486f1-4de8-4700-922b-863168f612a0','f42c9e51-5b7e-4ab3-847d-fd86b4e90dc1',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('f6396a5b-1116-490b-a1ab-0463850a941a','dd6c2ace-2593-445b-9569-55328090de99','c7442d31-012a-40f6-ab04-600a70db8723',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('d2eda27d-9e9c-4f93-ae08-7a982ef9ec3e','58dcc836-51e1-4633-9a89-73ac44eb2152','5a27e806-21d4-4672-aa5e-29518f10c0aa',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('0556e0e0-e810-46c9-b2aa-b1f929aed15b','899d79f7-8623-4442-a398-002178cf5d94','9a4aa0e1-6b5f-4624-a21c-3acfa858d7f3',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('e62cc57e-7afa-48bc-bfa7-3813b08bdc75','7ee486f1-4de8-4700-922b-863168f612a0','4f16c772-1df4-4922-a9e1-761ca829bb85',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('9bddacde-07b8-4b22-93cf-fb878bff2155','4a366bb4-5104-45ea-ac9e-1da8e14387c3','4f16c772-1df4-4922-a9e1-761ca829bb85',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('5fc4bf7f-ecf9-448b-8490-e13f9037e5a1','3ec11db4-f821-409f-84ad-07fc8e64d60d','649f665a-7624-4824-9cd5-b992462eb97b',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('1848a483-cada-4844-a845-c5a0352b76a6','58dcc836-51e1-4633-9a89-73ac44eb2152','9a9da923-06ef-47ea-bc20-23cc85b51ad0',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('de39b2ce-1d04-4047-b465-f2f4b2a96366','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','cfe9ab8a-a353-433e-8204-c065deeae3d9',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('a3257574-7ff3-4e65-bc5e-8390347ced37','4a366bb4-5104-45ea-ac9e-1da8e14387c3','47cbf0b7-e249-4b7e-8306-e5a2d2b3f394',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('b02c6a6e-0717-4156-8c3e-3dea6289c258','dd6c2ace-2593-445b-9569-55328090de99','a4fa6b22-3d7f-4d56-96f1-941f9e7570aa',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('6386806e-e8ab-4f65-89b5-72c107839dbf','3ec11db4-f821-409f-84ad-07fc8e64d60d','f42c9e51-5b7e-4ab3-847d-fd86b4e90dc1',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('46c45656-22e8-47eb-be1e-eb4da6907e57','3ec11db4-f821-409f-84ad-07fc8e64d60d','7582d86d-d4e7-4a88-997d-05593ccefb37',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('c6473cb5-19f9-481a-a746-a7d2b926bbcf','dd6c2ace-2593-445b-9569-55328090de99','5e8d8851-bf33-4d48-9860-acc24aceea3d',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('a1177c66-3529-4553-8e1c-4d11c1f7be04','dd6c2ace-2593-445b-9569-55328090de99','e337daba-5509-4507-be21-ca13ecaced9b',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('deba6c4e-e5d0-4e29-826c-48dc9354c81a','899d79f7-8623-4442-a398-002178cf5d94','02cc7df6-83d0-4ff1-a5ea-8240f5434e73',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('cf33d65f-2788-49c2-abd0-6f9e116b2ff2','3ec11db4-f821-409f-84ad-07fc8e64d60d','ee0ffe93-32b3-4817-982e-6d081da85d28',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('43094ebd-c396-42cd-97a2-879b8054b344','7ee486f1-4de8-4700-922b-863168f612a0','c68e26d0-dc81-4320-bdd7-fa286f4cc891',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('051af5a8-2dd4-44d3-906e-31663624c13c','58dcc836-51e1-4633-9a89-73ac44eb2152','709dad47-121a-4edd-ad95-b3dd6fd88f08',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('a1da855c-2843-41d8-b45e-cd936f1865e5','4a366bb4-5104-45ea-ac9e-1da8e14387c3','146c58e5-c87d-4f54-a766-8da85c6b6b2c',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('aef4b223-12b6-4ddd-8b82-51015d392f3b','58dcc836-51e1-4633-9a89-73ac44eb2152','c9036eb8-84bb-4909-be20-0662387219a7',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('5d983686-d11e-49cf-9fb5-215497ce53a4','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','1a170f85-e7f1-467c-a4dc-7d0b7898287e',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('6b9e6e04-923a-4b34-aa8f-fe0b02479a1f','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','e337daba-5509-4507-be21-ca13ecaced9b',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('be0281c2-1b71-4b27-8fc0-e0eb3afad84d','dd6c2ace-2593-445b-9569-55328090de99','e4e467f2-449d-46e3-a59b-0f8714e4824a',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('e1ac7c83-05dc-48fb-b64a-eb6ee9f6485d','dd6c2ace-2593-445b-9569-55328090de99','6f0e02be-08ad-48b1-8e23-eecaab34b4fe',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('bcca909d-5c5c-4e94-92c6-6fe389dbe654','7ee486f1-4de8-4700-922b-863168f612a0','03dd5854-8bc3-4b56-986e-eac513cc1ec0',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('9c31fa05-3ec9-446d-9da6-8c712a0d934d','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','811a32c0-90d6-4744-9a57-ab4130091754',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('66271bb2-c73c-4c92-8540-f40698211604','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','a761a482-2929-4345-8027-3c6258f0c8dd',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('ef31091b-6493-4a5d-99f6-5b40f431b3bb','7ee486f1-4de8-4700-922b-863168f612a0','5bf18f68-55b8-4024-adb1-c2e6592a2582',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('d4990fbe-e12b-4f60-b934-b93b61099dbc','4a366bb4-5104-45ea-ac9e-1da8e14387c3','91eb2878-0368-4347-97e3-e6caa362d878',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('a04a293c-fcd9-4285-868e-95b0ad46e0a6','58dcc836-51e1-4633-9a89-73ac44eb2152','6530aaba-4906-4d63-a6d3-deea01c99bea',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('c8711694-e10e-4693-a5fd-618f2f610971','58dcc836-51e1-4633-9a89-73ac44eb2152','f42c9e51-5b7e-4ab3-847d-fd86b4e90dc1',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('87892d46-4609-493f-a98d-0ca8639d31b9','7ee486f1-4de8-4700-922b-863168f612a0','6530aaba-4906-4d63-a6d3-deea01c99bea',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('9663a0eb-c3a8-410d-96f9-de0a000e9214','4a366bb4-5104-45ea-ac9e-1da8e14387c3','5bf18f68-55b8-4024-adb1-c2e6592a2582',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('d7860147-591f-49a7-a529-4c563f8feda9','4a366bb4-5104-45ea-ac9e-1da8e14387c3','816f84d1-ea01-47a0-a799-4b68508e35cc',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('ff8a7cff-2f3b-4ce1-87c5-7e1dfe82d0e4','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','1e23a20c-2558-47bf-b720-d7758b717ce3',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('bd44b3e8-6057-4a1e-b7da-95159a815f57','58dcc836-51e1-4633-9a89-73ac44eb2152','1a170f85-e7f1-467c-a4dc-7d0b7898287e',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('9450bb6c-a79f-42b0-bfad-04eab12d4be7','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','2124fcbf-be89-4975-9cc7-263ac14ad759',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('75cb751d-9caa-4935-8622-29162bcd6386','899d79f7-8623-4442-a398-002178cf5d94','a761a482-2929-4345-8027-3c6258f0c8dd',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('85b9a043-62d1-4a93-a0a1-56a5e35205f1','dd6c2ace-2593-445b-9569-55328090de99','9a4aa0e1-6b5f-4624-a21c-3acfa858d7f3',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('5303a89b-cda0-44d6-8bda-c27cbed2c07b','58dcc836-51e1-4633-9a89-73ac44eb2152','64265049-1b4a-4a96-9cba-e01f59cafcc7',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('a16dba47-2d29-4cf8-8994-2fa177ef4ac0','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','2b1d1842-15f8-491a-bdce-e5f9fea947e7',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('f5d305ad-2927-477f-aaee-f7f312c1cc56','7ee486f1-4de8-4700-922b-863168f612a0','a761a482-2929-4345-8027-3c6258f0c8dd',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('b62b0e00-9b2e-44cf-88bd-b1219bda6d35','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','9bb87311-1b29-4f29-8561-8a4c795654d4',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('2114ef1d-002d-4ed4-ac9c-a646892f455c','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','5a27e806-21d4-4672-aa5e-29518f10c0aa',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('b3e478e8-e1f6-4324-992f-11bae5de8d1e','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','760f146d-d5e7-4e08-9464-45371ea3267d',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('363e35ba-95a6-413c-bb1c-d22bf45fe324','4a366bb4-5104-45ea-ac9e-1da8e14387c3','c3c46c6b-115a-4236-b88a-76126e7f9516',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('bb7faea9-85c1-449b-b9bc-274f2ad2a28c','58dcc836-51e1-4633-9a89-73ac44eb2152','10644589-71f6-4baf-ba1c-dfb19d924b25',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('e8c08c68-5e12-492d-bbe0-23b284b0f04a','3ec11db4-f821-409f-84ad-07fc8e64d60d','cae0eb53-a023-434c-ac8c-d0641067d8d8',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('56397c36-c465-4d70-a640-832e4cf22912','dd6c2ace-2593-445b-9569-55328090de99','64265049-1b4a-4a96-9cba-e01f59cafcc7',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('317afce1-71dd-47f4-8574-5cdd6b9c3233','58dcc836-51e1-4633-9a89-73ac44eb2152','7ac1c0ec-0903-477c-89e0-88efe9249c98',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('f2cb4d6d-01fd-49c6-813f-1a607b15b791','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','e3071ca8-bedf-4eff-bda0-e9ff27f0e34c',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('d849652e-f3d0-4f4f-b499-741539922dd4','7ee486f1-4de8-4700-922b-863168f612a0','f79dd433-2808-4f20-91ef-6b5efca07350',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('a548f5be-dd1c-42b1-bc5c-f3f5e8b79136','899d79f7-8623-4442-a398-002178cf5d94','146c58e5-c87d-4f54-a766-8da85c6b6b2c',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('df90e35a-ab79-47fa-9b35-cd09af1ef6b0','899d79f7-8623-4442-a398-002178cf5d94','243e6e83-ff11-4a30-af30-8751e8e63bd4',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('63aa317a-7b3b-4b32-8d29-8f934b1f8fbb','58dcc836-51e1-4633-9a89-73ac44eb2152','c7442d31-012a-40f6-ab04-600a70db8723',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('64d8dcd5-9666-4724-b104-0317f18d5a44','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','ca72968c-5921-4167-b7b6-837c88ca87f2',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('5a8586a9-6c3f-45a3-85f5-ccf68dc2efcb','dd6c2ace-2593-445b-9569-55328090de99','c9036eb8-84bb-4909-be20-0662387219a7',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('9b39d790-2393-4d10-b29b-2fbff155d972','58dcc836-51e1-4633-9a89-73ac44eb2152','ddd74fb8-c0f1-41a9-9d4f-234bd295ae1a',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('bfb93ff5-6e14-4edf-ad50-0220cd8152fc','dd6c2ace-2593-445b-9569-55328090de99','e5d41d36-b355-4407-9ede-cd435da69873',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('ead7ced6-4216-4b2c-a655-bfb40e15be37','3ec11db4-f821-409f-84ad-07fc8e64d60d','635e4b79-342c-4cfc-8069-39c408a2decd',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('ee20fa2f-15b7-4939-b134-35561adb73ec','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','9a9da923-06ef-47ea-bc20-23cc85b51ad0',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('ff7be0ab-6b6a-472e-b242-044c87ce0b94','58dcc836-51e1-4633-9a89-73ac44eb2152','816f84d1-ea01-47a0-a799-4b68508e35cc',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('1f1b61e7-5fa8-457d-9852-607c201c57b8','7ee486f1-4de8-4700-922b-863168f612a0','9b6832a8-eb82-4afa-b12f-b52a3b2cda75',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('fb9827ac-a6e3-477b-8909-7c0ad064a975','3ec11db4-f821-409f-84ad-07fc8e64d60d','6e43ffbc-1102-45dc-8fb2-139f6b616083',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('a9c85b9b-3839-4dea-91e2-c538e7c4f060','7ee486f1-4de8-4700-922b-863168f612a0','816f84d1-ea01-47a0-a799-4b68508e35cc',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('048b1e9b-cc9e-4b9c-a618-be41b04e3b82','58dcc836-51e1-4633-9a89-73ac44eb2152','cfe9ab8a-a353-433e-8204-c065deeae3d9',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('06c40e27-563b-4e32-9444-ac1164617d4f','7ee486f1-4de8-4700-922b-863168f612a0','ee0ffe93-32b3-4817-982e-6d081da85d28',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('425d311a-4c73-47d4-979b-01b3a1f7056d','7ee486f1-4de8-4700-922b-863168f612a0','4f2e3e38-6bf4-4e74-bd7b-fe6edb87ee42',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('658a6b57-8541-4340-bb1f-796963f177d0','7ee486f1-4de8-4700-922b-863168f612a0','7d0fc5a1-719b-4070-a740-fe387075f0c3',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('67836411-2336-4e18-bb63-6bc08b747021','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','02cc7df6-83d0-4ff1-a5ea-8240f5434e73',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('193c33a1-05ca-4d9a-a4e2-13cbe76490b1','58dcc836-51e1-4633-9a89-73ac44eb2152','c18e25f9-ec34-41ca-8c1b-05558c8d6364',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('1bb0c76f-4125-4f82-828b-d01a8ff09e09','7ee486f1-4de8-4700-922b-863168f612a0','1e23a20c-2558-47bf-b720-d7758b717ce3',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('dc811c18-6c71-40fb-91f3-9990f0581576','3ec11db4-f821-409f-84ad-07fc8e64d60d','760f146d-d5e7-4e08-9464-45371ea3267d',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('526fc4e4-8ad8-4f2a-a8ea-22e21137a18f','4a366bb4-5104-45ea-ac9e-1da8e14387c3','1a170f85-e7f1-467c-a4dc-7d0b7898287e',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('c5ddbf7e-229d-4585-bb03-527f9c7d25c5','58dcc836-51e1-4633-9a89-73ac44eb2152','2b1d1842-15f8-491a-bdce-e5f9fea947e7',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('d7ca56fc-c7b3-462e-b0e7-30f2fe5467a2','4a366bb4-5104-45ea-ac9e-1da8e14387c3','8eb44185-f9bf-465e-8469-7bc422534319',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('c7991028-3ee0-4004-a8ba-45c5505dbaf8','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','8abaed50-eac1-4f40-83db-c07d2c3a123a',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('a8232eca-4bdf-4794-a587-b3e8fa1c08f4','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','9b6832a8-eb82-4afa-b12f-b52a3b2cda75',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('c145ad96-f7f5-45b9-a161-d72aa12f4a5b','7ee486f1-4de8-4700-922b-863168f612a0','4a366bb4-5104-45ea-ac9e-1da8e14387c3',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('49ba72dc-a8b3-4be3-9ecc-8500969fe8c9','58dcc836-51e1-4633-9a89-73ac44eb2152','811a32c0-90d6-4744-9a57-ab4130091754',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('fdb88f3a-1e58-43fb-986a-7e364b9e2c5a','3ec11db4-f821-409f-84ad-07fc8e64d60d','58dcc836-51e1-4633-9a89-73ac44eb2152',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('96dc1a9c-48ed-4862-8a00-4233088893df','7ee486f1-4de8-4700-922b-863168f612a0','6455326e-cc11-4cfe-903b-ccce70e6f04e',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('9ff5d592-3949-4b95-a5a7-6a0230015d94','7ee486f1-4de8-4700-922b-863168f612a0','b3911f28-d334-4cca-8924-7da60ea5a213',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('fd6d39b1-d22b-47ab-834d-d4e4140d0d93','899d79f7-8623-4442-a398-002178cf5d94','64265049-1b4a-4a96-9cba-e01f59cafcc7',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('6cd1cad5-65fd-4684-8609-0835c55aaada','899d79f7-8623-4442-a398-002178cf5d94','6530aaba-4906-4d63-a6d3-deea01c99bea',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('bd72685f-a66a-4911-8227-9e809c2ed640','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','b7329731-65df-4427-bdee-18a0ab51efb4',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('69ee846d-7afe-4313-9d8f-9de492aa8958','dd6c2ace-2593-445b-9569-55328090de99','146c58e5-c87d-4f54-a766-8da85c6b6b2c',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('75926620-0cc9-4e2a-9626-7a3bbc18a4f2','58dcc836-51e1-4633-9a89-73ac44eb2152','531e3a04-e84c-45d9-86bf-c6da0820b605',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('06f3a7e1-2517-486c-a934-0b1c2d7b804a','4a366bb4-5104-45ea-ac9e-1da8e14387c3','10644589-71f6-4baf-ba1c-dfb19d924b25',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('2ddfc2b3-3137-4f55-8492-7561c1d865b6','899d79f7-8623-4442-a398-002178cf5d94','c68492e9-c7d9-4394-8695-15f018ce6b90',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('483e0379-523d-4132-a90a-2a4d3448b765','3ec11db4-f821-409f-84ad-07fc8e64d60d','4a366bb4-5104-45ea-ac9e-1da8e14387c3',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('6300c858-bc1c-41c1-b066-033992c434cb','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','40da86e6-76e5-443b-b4ca-27ad31a2baf6',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('6444d7d1-d44e-437d-8824-0bac615d4740','58dcc836-51e1-4633-9a89-73ac44eb2152','0506bf0f-bc1c-43c7-a75f-639a1b4c0449',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('8cfe3a58-6a3c-489e-83b4-ee608ebc1f9d','3ec11db4-f821-409f-84ad-07fc8e64d60d','0506bf0f-bc1c-43c7-a75f-639a1b4c0449',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('16f79e4e-1164-4360-8bb3-ee995bebfbe1','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','098488af-82c9-49c6-9daa-879eff3d3bee',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('0b105777-8011-4bac-b441-8fb64bfdc0a8','dd6c2ace-2593-445b-9569-55328090de99','afb334ca-9466-44ec-9be1-4c881db6d060',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('e5dac46e-76de-4de6-b6fc-72f550a0e1c9','dd6c2ace-2593-445b-9569-55328090de99','6e43ffbc-1102-45dc-8fb2-139f6b616083',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('fe519a77-471f-4c14-95ce-08e262f70bdb','4a366bb4-5104-45ea-ac9e-1da8e14387c3','4fb560d1-6bf5-46b7-a047-d381a76c4fef',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('91d48e52-9949-4197-ab3e-90e1655ee2c9','7ee486f1-4de8-4700-922b-863168f612a0','91eb2878-0368-4347-97e3-e6caa362d878',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('f91258b2-988e-402c-afb8-44a1c902a494','7ee486f1-4de8-4700-922b-863168f612a0','3733db73-602a-4402-8f94-36eec2fdab15',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('2a931d99-ba28-4029-99ff-0a703b9e53c4','dd6c2ace-2593-445b-9569-55328090de99','8abaed50-eac1-4f40-83db-c07d2c3a123a',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('43390aa4-bc71-4b56-99af-028c680e8d11','899d79f7-8623-4442-a398-002178cf5d94','816f84d1-ea01-47a0-a799-4b68508e35cc',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('00b16c80-a823-4676-b947-3072dfddcbd2','7ee486f1-4de8-4700-922b-863168f612a0','422021c7-08e1-4355-838d-8f2821f00f42',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('b4c59bea-3cd7-4e34-919a-ca4de7243635','7ee486f1-4de8-4700-922b-863168f612a0','c68492e9-c7d9-4394-8695-15f018ce6b90',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('acf05db3-d2c4-4731-b8dd-636c038fe7d3','7ee486f1-4de8-4700-922b-863168f612a0','fd89694b-06ef-4472-ac9f-614c2de3317b',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('b3121e98-3765-41d4-a78f-01a35704ac56','7ee486f1-4de8-4700-922b-863168f612a0','ea0fa1cc-7d80-4bd9-989e-f119c33fb881',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('7e9c29c3-ae00-4cef-a628-ed12f0bd8b72','3ec11db4-f821-409f-84ad-07fc8e64d60d','709dad47-121a-4edd-ad95-b3dd6fd88f08',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('c12562ee-3240-4dfa-a6a8-a73e143a0a61','dd6c2ace-2593-445b-9569-55328090de99','3733db73-602a-4402-8f94-36eec2fdab15',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('ab3608d8-43ee-4ea3-b11a-ce4a93020e1c','899d79f7-8623-4442-a398-002178cf5d94','6f0e02be-08ad-48b1-8e23-eecaab34b4fe',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('c6f2cd2a-192f-4ad0-a730-cbc66f352ecd','7ee486f1-4de8-4700-922b-863168f612a0','e337daba-5509-4507-be21-ca13ecaced9b',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('5841ab4c-320e-44d7-8ed7-30b757c18a46','dd6c2ace-2593-445b-9569-55328090de99','9b6832a8-eb82-4afa-b12f-b52a3b2cda75',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('a8d94fdb-ef3b-4b21-b4ec-08ba4b783daa','899d79f7-8623-4442-a398-002178cf5d94','b194b7a9-a759-4c12-9482-b99e43a52294',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('f9b8c91e-7e21-4f8f-8360-5cb505b30709','dd6c2ace-2593-445b-9569-55328090de99','43a09249-d81b-4897-b5c7-dd88331cf2bd',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('7da4dca3-6579-4662-b306-448b6a48ad2d','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','c4c73fcb-be11-4b1a-986a-a73451d402a7',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('30430455-48eb-457c-864d-e56e4b1975ff','899d79f7-8623-4442-a398-002178cf5d94','a2fad63c-b6cb-4b0d-9ced-1a81a6bc9985',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('49277f2d-f002-4fec-98ea-6c98f0d7c30c','3ec11db4-f821-409f-84ad-07fc8e64d60d','c7442d31-012a-40f6-ab04-600a70db8723',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('2ec5dc8f-0408-4566-81dd-cdb29794985b','58dcc836-51e1-4633-9a89-73ac44eb2152','c68e26d0-dc81-4320-bdd7-fa286f4cc891',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('70c60c0d-3b89-4151-9bd6-b55780ebbe16','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','612c2ce9-39cc-45e6-a3f1-c6672267d392',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('423543e3-5c89-4afb-8102-bc92b1a73449','7ee486f1-4de8-4700-922b-863168f612a0','6e802149-7e46-4d7a-ab57-6c4df832085d',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('ff4bf5b7-d424-4486-9243-577bcbbdc5d8','dd6c2ace-2593-445b-9569-55328090de99','47e88f74-4e28-4027-b05e-bf9adf63e572',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('4cd9b78f-9bad-457a-89d4-ef637d77f726','58dcc836-51e1-4633-9a89-73ac44eb2152','def8c7af-d4fc-474e-974d-6fd00c251da8',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('bc78f29c-d893-4332-b5da-4eeb15a4cdef','dd6c2ace-2593-445b-9569-55328090de99','0cb31c3c-dfd2-4b2a-b475-d2023008eea4',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('8bdd0fc4-bf2e-4085-a2c6-03cd6bf001c9','7ee486f1-4de8-4700-922b-863168f612a0','58dcc836-51e1-4633-9a89-73ac44eb2152',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('2109125a-14a2-441c-bd81-1d405464dbd5','899d79f7-8623-4442-a398-002178cf5d94','7582d86d-d4e7-4a88-997d-05593ccefb37',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('8a71cbd8-f359-4dfb-9a8d-1916af041977','7ee486f1-4de8-4700-922b-863168f612a0','7ee486f1-4de8-4700-922b-863168f612a0',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('a4e67c46-efe3-4866-996a-cd4a22f9f563','4a366bb4-5104-45ea-ac9e-1da8e14387c3','7d0fc5a1-719b-4070-a740-fe387075f0c3',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('a8ca3951-28a5-42fb-92cb-03c854be5879','58dcc836-51e1-4633-9a89-73ac44eb2152','b194b7a9-a759-4c12-9482-b99e43a52294',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('ca7512ac-399e-4c90-9e82-41cda85a9d59','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','531e3a04-e84c-45d9-86bf-c6da0820b605',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('f169a107-9710-40d5-b386-c06b777a479b','58dcc836-51e1-4633-9a89-73ac44eb2152','d53d6be6-b36c-403f-b72d-d6160e9e52c1',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('03e4370d-da60-45d8-9ecb-0002cbb85de2','7ee486f1-4de8-4700-922b-863168f612a0','d45cf336-8c4b-4651-b505-bbd34831d12d',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('4ce9ee25-180c-4b28-9e0b-7cb0b37a2158','3ec11db4-f821-409f-84ad-07fc8e64d60d','cfe9ab8a-a353-433e-8204-c065deeae3d9',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('f0f66690-5183-4c0e-85c1-30882de49e26','899d79f7-8623-4442-a398-002178cf5d94','6a0f9a02-b6ba-4585-9d7a-6959f7b0248f',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('f0341f85-5c40-489b-a543-6f5db8ab53f3','dd6c2ace-2593-445b-9569-55328090de99','1e23a20c-2558-47bf-b720-d7758b717ce3',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('04e633e7-9a45-4759-9984-72222b415b5f','899d79f7-8623-4442-a398-002178cf5d94','0cb31c3c-dfd2-4b2a-b475-d2023008eea4',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('8213590f-b708-4854-a981-d68af013bcb6','7ee486f1-4de8-4700-922b-863168f612a0','30040c3f-667d-4dee-ba4c-24aad0891c9c',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('7bc82ad8-508e-4364-bb0d-7b6fb720b9d9','4a366bb4-5104-45ea-ac9e-1da8e14387c3','6e802149-7e46-4d7a-ab57-6c4df832085d',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('044df905-7997-4b74-a6dd-b75697eb645c','899d79f7-8623-4442-a398-002178cf5d94','760f146d-d5e7-4e08-9464-45371ea3267d',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('9fae66dc-5e67-4e24-9182-4d8db7ff9449','899d79f7-8623-4442-a398-002178cf5d94','2b1d1842-15f8-491a-bdce-e5f9fea947e7',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('236881d7-c14a-4629-9457-3db5ab483eff','4a366bb4-5104-45ea-ac9e-1da8e14387c3','fd57df67-e734-4eb2-80cf-2feafe91f238',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('23732d4e-0784-46c9-8699-462ceac9beae','dd6c2ace-2593-445b-9569-55328090de99','3320e408-93d8-4933-abb8-538a5d697b41',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('0046ae36-c55a-4c8a-80b0-8be21b612f7e','899d79f7-8623-4442-a398-002178cf5d94','9bb87311-1b29-4f29-8561-8a4c795654d4',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('9c9fa9ee-2c93-4c82-b90d-5dd80617c0f1','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','649f665a-7624-4824-9cd5-b992462eb97b',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('c8d2a634-83dc-45f2-b1ab-ed50fbe3726a','899d79f7-8623-4442-a398-002178cf5d94','027f06cd-8c82-4c4a-a583-b20ccad9cc35',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('99d81c4f-d989-4c85-a7d1-59528eee20a8','3ec11db4-f821-409f-84ad-07fc8e64d60d','2c144ea1-9b49-4842-ad56-e5120912fd18',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('a56ebdee-becd-4225-84f2-36e1da6e6021','899d79f7-8623-4442-a398-002178cf5d94','01d0be5d-aaec-483d-a841-6ab1301aa9bd',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('273a2267-2810-4158-9fe5-ff98ef01dc1e','4a366bb4-5104-45ea-ac9e-1da8e14387c3','0026678a-51b7-46de-af3d-b49428e0916c',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('b20362bf-8004-4345-b584-0038fac147d4','dd6c2ace-2593-445b-9569-55328090de99','9893a927-6084-482c-8f1c-e85959eb3547',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('8cf46a53-3032-4b9c-a669-69554b83818c','4a366bb4-5104-45ea-ac9e-1da8e14387c3','4f2e3e38-6bf4-4e74-bd7b-fe6edb87ee42',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('06df2969-9375-4107-abae-6317f82c9ca6','58dcc836-51e1-4633-9a89-73ac44eb2152','6a0f9a02-b6ba-4585-9d7a-6959f7b0248f',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('f6863f66-540f-4b22-ba0f-fd00b77e7de8','58dcc836-51e1-4633-9a89-73ac44eb2152','4a366bb4-5104-45ea-ac9e-1da8e14387c3',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('854d0fdc-2bc7-4abb-87e3-fa349ba2a42c','899d79f7-8623-4442-a398-002178cf5d94','e5d41d36-b355-4407-9ede-cd435da69873',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('03c876a7-a596-42b7-9439-44556b0118a0','4a366bb4-5104-45ea-ac9e-1da8e14387c3','535e6789-c126-405f-8b3a-7bd886b94796',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('db6139ad-bdd1-4381-8498-9a71668723aa','7ee486f1-4de8-4700-922b-863168f612a0','b7329731-65df-4427-bdee-18a0ab51efb4',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('bca768b4-44b0-4385-906c-583dc67cc177','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','5bf18f68-55b8-4024-adb1-c2e6592a2582',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('2a4e77e4-c336-4f08-8ee3-96d1562f0a42','3ec11db4-f821-409f-84ad-07fc8e64d60d','a4fa6b22-3d7f-4d56-96f1-941f9e7570aa',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('96f1f2df-c2f9-4606-8304-53352c4cd3df','3ec11db4-f821-409f-84ad-07fc8e64d60d','e5d41d36-b355-4407-9ede-cd435da69873',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('d55c2bb0-8182-4fda-8b0d-4aef2da81f39','3ec11db4-f821-409f-84ad-07fc8e64d60d','531e3a04-e84c-45d9-86bf-c6da0820b605',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('247ddf95-33f3-43d7-8227-c5b3c3e35fdf','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','46c16bc1-df71-4c6f-835b-400c8caaf984',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('d3dd5072-173e-4795-b251-183f4fe0181d','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','91eb2878-0368-4347-97e3-e6caa362d878',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('afa66583-ff83-4c6c-968f-e5a583634b3d','dd6c2ace-2593-445b-9569-55328090de99','182eb005-c185-418d-be8b-f47212c38af3',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('c82c0c5f-c2a1-4987-a5b6-26e458359e14','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','43a09249-d81b-4897-b5c7-dd88331cf2bd',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('33e923aa-06d1-4a1b-a982-fa30bdfb08aa','4a366bb4-5104-45ea-ac9e-1da8e14387c3','6455326e-cc11-4cfe-903b-ccce70e6f04e',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('3ad23dd1-3fe0-43a5-b87e-e2ded5af9055','3ec11db4-f821-409f-84ad-07fc8e64d60d','0ba534f5-0d24-4d7c-9216-d07f57cd8edd',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('f6c8aa62-8826-456d-9068-8785fb0da2d8','58dcc836-51e1-4633-9a89-73ac44eb2152','4a239fdb-9ad7-4bbb-8685-528f3f861992',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('d49657d1-b291-46e0-bf46-4f1e3a780af7','4a366bb4-5104-45ea-ac9e-1da8e14387c3','b7329731-65df-4427-bdee-18a0ab51efb4',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('f6cbc757-3ec8-4954-bdba-1a063def481b','3ec11db4-f821-409f-84ad-07fc8e64d60d','fd57df67-e734-4eb2-80cf-2feafe91f238',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('dd9e334c-8b0b-4caa-974f-36b471492dac','7ee486f1-4de8-4700-922b-863168f612a0','243e6e83-ff11-4a30-af30-8751e8e63bd4',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('fda3cfb7-88a8-4ae6-b80a-eab015b6cf8a','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','f42c9e51-5b7e-4ab3-847d-fd86b4e90dc1',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('1631eccf-25dd-4e0f-ac66-e95f26f02242','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','6f0e02be-08ad-48b1-8e23-eecaab34b4fe',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('eaec7503-0a87-4bbf-863c-017d2f5afaf0','dd6c2ace-2593-445b-9569-55328090de99','531e3a04-e84c-45d9-86bf-c6da0820b605',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('00b9d629-d4c1-4d14-984a-1fef8aee666c','899d79f7-8623-4442-a398-002178cf5d94','b80251b4-02a2-4122-add9-ab108cd011d7',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('5406b04f-6fd9-4f25-b1dd-19389304bf28','4a366bb4-5104-45ea-ac9e-1da8e14387c3','a7f17fd7-3810-4866-9b51-8179157b4a2b',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('fcdad2df-0fbe-4ad3-ab1a-e525a1042189','dd6c2ace-2593-445b-9569-55328090de99','3ece4e86-d328-4206-9f81-ec62bdf55335',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('3caaecb9-c19b-46f9-b2b4-58cc747d7d52','3ec11db4-f821-409f-84ad-07fc8e64d60d','5a27e806-21d4-4672-aa5e-29518f10c0aa',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); +INSERT INTO re_intl_transit_times (id,origin_rate_area_id,destination_rate_area_id,hhg_transit_time,ub_transit_time,created_at,updated_at,active) VALUES + ('1f5fb06f-6f2a-43de-b332-51ad42c39fed','899d79f7-8623-4442-a398-002178cf5d94','2c144ea1-9b49-4842-ad56-e5120912fd18',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('1eb470fb-d60c-459e-a596-f74fe9907782','dd6c2ace-2593-445b-9569-55328090de99','7ac1c0ec-0903-477c-89e0-88efe9249c98',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('410fe157-a8f3-46a4-bbd4-319f5fd8052a','58dcc836-51e1-4633-9a89-73ac44eb2152','7d0fc5a1-719b-4070-a740-fe387075f0c3',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('16669200-1de6-4d7a-bbfe-070c4588ac37','02cc7df6-83d0-4ff1-a5ea-8240f5434e73','6455326e-cc11-4cfe-903b-ccce70e6f04e',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('4e9f71b0-0f8a-45f3-928d-db7b7bf3cd86','7ee486f1-4de8-4700-922b-863168f612a0','760f146d-d5e7-4e08-9464-45371ea3267d',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('5f069de2-2e07-437a-a9c7-51a9f7d627c4','3ec11db4-f821-409f-84ad-07fc8e64d60d','9bb87311-1b29-4f29-8561-8a4c795654d4',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('e948110f-074a-47f0-9fdd-5c7df81c0cdf','4a366bb4-5104-45ea-ac9e-1da8e14387c3','ddd74fb8-c0f1-41a9-9d4f-234bd295ae1a',20,20,'2024-11-26 15:07:27.501911','2024-11-26 15:07:27.501911',true), + ('d209d598-4d2e-429a-a616-16335bf721e0','7ee486f1-4de8-4700-922b-863168f612a0','2c144ea1-9b49-4842-ad56-e5120912fd18',75,35,'2024-11-26 15:08:26.396274','2024-11-26 15:08:26.396274',true), + ('f0a06d12-e853-4bce-8cba-2638172a4d6e','58dcc836-51e1-4633-9a89-73ac44eb2152','40ab17b2-9e79-429c-a75d-b6fcbbe27901',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true), + ('722cf98c-0ef1-4ebd-9b29-5bd4ca5dd671','58dcc836-51e1-4633-9a89-73ac44eb2152','93052804-f158-485d-b3a5-f04fd0d41e55',60,30,'2024-11-26 15:08:45.433229','2024-11-26 15:08:45.433229',true); diff --git a/migrations/app/schema/20250204162411_updating_create_accessorial_service_item_proc_for_crating.up.sql b/migrations/app/schema/20250204162411_updating_create_accessorial_service_item_proc_for_crating.up.sql new file mode 100644 index 00000000000..135a027eecd --- /dev/null +++ b/migrations/app/schema/20250204162411_updating_create_accessorial_service_item_proc_for_crating.up.sql @@ -0,0 +1,131 @@ +DO ' +BEGIN + IF EXISTS (SELECT 1 FROM pg_type WHERE typname = ''mto_service_item_type'') THEN + IF NOT EXISTS ( + SELECT 1 FROM pg_attribute + WHERE attrelid = ''mto_service_item_type''::regtype + AND attname = ''external_crate'' + ) THEN + ALTER TYPE mto_service_item_type ADD ATTRIBUTE "external_crate" bool; + END IF; + END IF; +END +'; + +-- added external_crate +CREATE OR REPLACE PROCEDURE create_accessorial_service_items_for_shipment ( + IN shipment_id UUID, + IN service_items mto_service_item_type[], + INOUT created_service_item_ids text[] +) AS ' +DECLARE + s_type mto_shipment_type; + m_code market_code_enum; + move_id UUID; + service_item RECORD; + item mto_service_item_type; + new_service_id text; +BEGIN + -- get the shipment type, market code, and move_id based on shipment_id + SELECT ms.shipment_type, ms.market_code, ms.move_id + INTO s_type, m_code, move_id + FROM mto_shipments ms + WHERE ms.id = shipment_id; + + IF s_type IS NULL OR m_code IS NULL THEN + RAISE EXCEPTION ''Shipment with ID % not found or missing required details.'', shipment_id; + END IF; + + -- loop through each provided service item object + FOREACH item IN ARRAY service_items + LOOP + FOR service_item IN + SELECT rsi.id, + rs.id AS re_service_id, + rs.service_location, + rsi.is_auto_approved, + rs.code AS service_code + FROM re_service_items rsi + JOIN re_services rs ON rsi.service_id = rs.id + WHERE rsi.shipment_type = s_type + AND rsi.market_code = m_code + AND rs.code = (item.re_service_code) + AND rsi.is_auto_approved = false + LOOP + BEGIN + -- International crating/uncrating will not have the SI update functionality. + -- Prime should to be able to create multiple crating SI for now. + IF service_item.service_code IN (''ICRT'', ''IUCRT'') OR (NOT does_service_item_exist(service_item.re_service_id, shipment_id)) THEN + + INSERT INTO mto_service_items ( + mto_shipment_id, + move_id, + re_service_id, + service_location, + status, + created_at, + updated_at, + sit_postal_code, + sit_entry_date, + sit_customer_contacted, + reason, + estimated_weight, + actual_weight, + pickup_postal_code, + description, + sit_destination_original_address_id, + sit_destination_final_address_id, + sit_requested_delivery, + sit_departure_date, + sit_origin_hhg_original_address_id, + sit_origin_hhg_actual_address_id, + customer_expense, + customer_expense_reason, + sit_delivery_miles, + standalone_crate, + external_crate + ) + VALUES ( + shipment_id, + move_id, + service_item.re_service_id, + service_item.service_location, + ''SUBMITTED''::service_item_status, + NOW(), + NOW(), + (item).sit_postal_code, + (item).sit_entry_date, + (item).sit_customer_contacted, + (item).reason, + (item).estimated_weight, + (item).actual_weight, + (item).pickup_postal_code, + (item).description, + (item).sit_destination_original_address_id, + (item).sit_destination_final_address_id, + (item).sit_requested_delivery, + (item).sit_departure_date, + (item).sit_origin_hhg_original_address_id, + (item).sit_origin_hhg_actual_address_id, + (item).customer_expense, + (item).customer_expense_reason, + (item).sit_delivery_miles, + (item).standalone_crate, + (item).external_crate + ) RETURNING id INTO new_service_id; + + created_service_item_ids := array_append(created_service_item_ids, new_service_id); + + END IF; + EXCEPTION + WHEN OTHERS THEN + RAISE EXCEPTION ''Error creating accessorial service item with code % for shipment %: %'', + service_item.service_code, shipment_id, SQLERRM; + END; + END LOOP; + END LOOP; + + UPDATE moves SET status = ''APPROVALS REQUESTED'' WHERE id = move_id; +END; +' +LANGUAGE plpgsql; \ 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/migrations/app/schema/20250207153450_add_fetch_documents_func.up.sql b/migrations/app/schema/20250207153450_add_fetch_documents_func.up.sql new file mode 100644 index 00000000000..2bc71695066 --- /dev/null +++ b/migrations/app/schema/20250207153450_add_fetch_documents_func.up.sql @@ -0,0 +1,25 @@ +CREATE OR REPLACE FUNCTION public.fetch_documents(docCursor refcursor, useruploadCursor refcursor, uploadCursor refcursor, _docID uuid) RETURNS setof refcursor AS $$ +BEGIN + OPEN $1 FOR + SELECT documents.created_at, documents.deleted_at, documents.id, documents.service_member_id, documents.updated_at + FROM documents AS documents + WHERE documents.id = _docID and documents.deleted_at is null + LIMIT 1; + RETURN NEXT $1; + OPEN $2 FOR + SELECT user_uploads.created_at, user_uploads.deleted_at, user_uploads.document_id, user_uploads.id, user_uploads.updated_at, + user_uploads.upload_id, user_uploads.uploader_id + FROM user_uploads AS user_uploads + WHERE user_uploads.deleted_at is null and user_uploads.document_id = _docID + ORDER BY created_at asc; + RETURN NEXT $2; + OPEN $3 FOR + SELECT uploads.id, uploads.bytes, uploads.checksum, uploads.content_type, uploads.created_at, uploads.deleted_at, uploads.filename, + uploads.rotation, uploads.storage_key, uploads.updated_at, uploads.upload_type + FROM uploads AS uploads, user_uploads + WHERE uploads.deleted_at is null + and uploads.id = user_uploads.upload_id + and user_uploads.deleted_at is null and user_uploads.document_id = _docID; + RETURN NEXT $3; +END; +$$ LANGUAGE plpgsql; diff --git a/migrations/app/schema/20250210175754_B22451_update_dest_queue_to_consider_sit_extensions.up.sql b/migrations/app/schema/20250210175754_B22451_update_dest_queue_to_consider_sit_extensions.up.sql new file mode 100644 index 00000000000..1c3d1ceced6 --- /dev/null +++ b/migrations/app/schema/20250210175754_B22451_update_dest_queue_to_consider_sit_extensions.up.sql @@ -0,0 +1,280 @@ +-- updating to consider sit extensions in PENDING status +CREATE OR REPLACE FUNCTION get_destination_queue( + user_gbloc TEXT DEFAULT NULL, + customer_name TEXT DEFAULT NULL, + edipi TEXT DEFAULT NULL, + emplid TEXT DEFAULT NULL, + m_status TEXT[] DEFAULT NULL, + move_code TEXT DEFAULT NULL, + requested_move_date TIMESTAMP DEFAULT NULL, + date_submitted TIMESTAMP DEFAULT NULL, + branch TEXT DEFAULT NULL, + origin_duty_location TEXT DEFAULT NULL, + counseling_office TEXT DEFAULT NULL, + too_assigned_user TEXT DEFAULT NULL, + page INTEGER DEFAULT 1, + per_page INTEGER DEFAULT 20, + sort TEXT DEFAULT NULL, + sort_direction TEXT DEFAULT NULL +) +RETURNS TABLE ( + id UUID, + show BOOLEAN, + locator TEXT, + submitted_at TIMESTAMP WITH TIME ZONE, + orders_id UUID, + status TEXT, + locked_by UUID, + too_assigned_id UUID, + counseling_transportation_office_id UUID, + orders JSONB, + mto_shipments JSONB, + counseling_transportation_office JSONB, + too_assigned JSONB, + total_count BIGINT +) AS $$ +DECLARE + sql_query TEXT; + offset_value INTEGER; + sort_column TEXT; + sort_order TEXT; +BEGIN + IF page < 1 THEN + page := 1; + END IF; + + IF per_page < 1 THEN + per_page := 20; + END IF; + + offset_value := (page - 1) * per_page; + + sql_query := ' + SELECT + moves.id AS id, + moves.show AS show, + moves.locator::TEXT AS locator, + moves.submitted_at::TIMESTAMP WITH TIME ZONE AS submitted_at, + moves.orders_id AS orders_id, + moves.status::TEXT AS status, + moves.locked_by AS locked_by, + moves.too_assigned_id AS too_assigned_id, + moves.counseling_transportation_office_id AS counseling_transportation_office_id, + json_build_object( + ''id'', orders.id, + ''origin_duty_location_gbloc'', orders.gbloc, + ''service_member'', json_build_object( + ''id'', service_members.id, + ''first_name'', service_members.first_name, + ''last_name'', service_members.last_name, + ''edipi'', service_members.edipi, + ''emplid'', service_members.emplid, + ''affiliation'', service_members.affiliation + ), + ''origin_duty_location'', json_build_object( + ''name'', origin_duty_locations.name + ) + )::JSONB AS orders, + COALESCE( + ( + SELECT json_agg( + json_build_object( + ''id'', ms.id, + ''shipment_type'', ms.shipment_type, + ''status'', ms.status, + ''requested_pickup_date'', TO_CHAR(ms.requested_pickup_date, ''YYYY-MM-DD"T00:00:00Z"''), + ''scheduled_pickup_date'', TO_CHAR(ms.scheduled_pickup_date, ''YYYY-MM-DD"T00:00:00Z"''), + ''approved_date'', TO_CHAR(ms.approved_date, ''YYYY-MM-DD"T00:00:00Z"''), + ''prime_estimated_weight'', ms.prime_estimated_weight + ) + ) + FROM ( + SELECT DISTINCT ON (mto_shipments.id) mto_shipments.* + FROM mto_shipments + WHERE mto_shipments.move_id = moves.id + ) AS ms + ), + ''[]'' + )::JSONB AS mto_shipments, + json_build_object( + ''name'', counseling_offices.name + )::JSONB AS counseling_transportation_office, + json_build_object( + ''first_name'', too_user.first_name, + ''last_name'', too_user.last_name + )::JSONB AS too_assigned, + COUNT(*) OVER() AS total_count + FROM moves + JOIN orders ON moves.orders_id = orders.id + JOIN mto_shipments ON mto_shipments.move_id = moves.id + LEFT JOIN ppm_shipments ON ppm_shipments.shipment_id = mto_shipments.id + JOIN mto_service_items ON mto_shipments.id = mto_service_items.mto_shipment_id + JOIN re_services ON mto_service_items.re_service_id = re_services.id + JOIN service_members ON orders.service_member_id = service_members.id + JOIN duty_locations AS new_duty_locations ON orders.new_duty_location_id = new_duty_locations.id + JOIN duty_locations AS origin_duty_locations ON orders.origin_duty_location_id = origin_duty_locations.id + LEFT JOIN office_users AS too_user ON moves.too_assigned_id = too_user.id + LEFT JOIN office_users AS locked_user ON moves.locked_by = locked_user.id + LEFT JOIN transportation_offices AS counseling_offices + ON moves.counseling_transportation_office_id = counseling_offices.id + LEFT JOIN shipment_address_updates ON shipment_address_updates.shipment_id = mto_shipments.id + LEFT JOIN sit_extensions ON sit_extensions.mto_shipment_id = mto_shipments.id + JOIN move_to_dest_gbloc ON move_to_dest_gbloc.move_id = moves.id + WHERE moves.show = TRUE + '; + + IF user_gbloc IS NOT NULL THEN + sql_query := sql_query || ' AND move_to_dest_gbloc.gbloc = $1 '; + END IF; + + IF customer_name IS NOT NULL THEN + sql_query := sql_query || ' AND ( + service_members.first_name || '' '' || service_members.last_name ILIKE ''%'' || $2 || ''%'' + OR service_members.last_name || '' '' || service_members.first_name ILIKE ''%'' || $2 || ''%'' + )'; + END IF; + + IF edipi IS NOT NULL THEN + sql_query := sql_query || ' AND service_members.edipi ILIKE ''%'' || $3 || ''%'' '; + END IF; + + IF emplid IS NOT NULL THEN + sql_query := sql_query || ' AND service_members.emplid ILIKE ''%'' || $4 || ''%'' '; + END IF; + + IF m_status IS NOT NULL THEN + sql_query := sql_query || ' AND moves.status = ANY($5) '; + END IF; + + IF move_code IS NOT NULL THEN + sql_query := sql_query || ' AND moves.locator ILIKE ''%'' || $6 || ''%'' '; + END IF; + + IF requested_move_date IS NOT NULL THEN + sql_query := sql_query || ' AND ( + mto_shipments.requested_pickup_date::DATE = $7::DATE + OR ppm_shipments.expected_departure_date::DATE = $7::DATE + OR (mto_shipments.shipment_type = ''HHG_OUTOF_NTS'' AND mto_shipments.requested_delivery_date::DATE = $7::DATE) + )'; + END IF; + + IF date_submitted IS NOT NULL THEN + sql_query := sql_query || ' AND moves.submitted_at::DATE = $8::DATE '; + END IF; + + IF branch IS NOT NULL THEN + sql_query := sql_query || ' AND service_members.affiliation ILIKE ''%'' || $9 || ''%'' '; + END IF; + + IF origin_duty_location IS NOT NULL THEN + sql_query := sql_query || ' AND origin_duty_locations.name ILIKE ''%'' || $10 || ''%'' '; + END IF; + + IF counseling_office IS NOT NULL THEN + sql_query := sql_query || ' AND counseling_offices.name ILIKE ''%'' || $11 || ''%'' '; + END IF; + + IF too_assigned_user IS NOT NULL THEN + sql_query := sql_query || ' AND (too_user.first_name || '' '' || too_user.last_name) ILIKE ''%'' || $12 || ''%'' '; + END IF; + + -- add destination queue-specific filters (pending dest address requests, pending dest SIT extension requests when there are dest SIT service items, submitted dest SIT & dest shuttle service items) + sql_query := sql_query || ' + AND ( + shipment_address_updates.status = ''REQUESTED'' + OR ( + sit_extensions.status = ''PENDING'' + AND re_services.code IN (''DDFSIT'', ''DDASIT'', ''DDDSIT'', ''DDSFSC'', ''DDSHUT'', ''IDFSIT'', ''IDASIT'', ''IDDSIT'', ''IDSFSC'', ''IDSHUT'') + ) + OR ( + mto_service_items.status = ''SUBMITTED'' + AND re_services.code IN (''DDFSIT'', ''DDASIT'', ''DDDSIT'', ''DDSFSC'', ''DDSHUT'', ''IDFSIT'', ''IDASIT'', ''IDDSIT'', ''IDSFSC'', ''IDSHUT'') + ) + ) + '; + + -- default sorting values if none are provided (move.id) + sort_column := 'id'; + sort_order := 'ASC'; + + IF sort IS NOT NULL THEN + CASE sort + WHEN 'locator' THEN sort_column := 'moves.locator'; + WHEN 'status' THEN sort_column := 'moves.status'; + WHEN 'customerName' THEN sort_column := 'service_members.last_name'; + WHEN 'edipi' THEN sort_column := 'service_members.edipi'; + WHEN 'emplid' THEN sort_column := 'service_members.emplid'; + WHEN 'requestedMoveDate' THEN sort_column := 'COALESCE(mto_shipments.requested_pickup_date, ppm_shipments.expected_departure_date, mto_shipments.requested_delivery_date)'; + WHEN 'appearedInTooAt' THEN sort_column := 'COALESCE(moves.submitted_at, moves.approvals_requested_at)'; + WHEN 'branch' THEN sort_column := 'service_members.affiliation'; + WHEN 'originDutyLocation' THEN sort_column := 'origin_duty_locations.name'; + WHEN 'counselingOffice' THEN sort_column := 'counseling_offices.name'; + WHEN 'assignedTo' THEN sort_column := 'too_user.last_name'; + ELSE + sort_column := 'moves.id'; + END CASE; + END IF; + + IF sort_direction IS NOT NULL THEN + IF LOWER(sort_direction) = 'desc' THEN + sort_order := 'DESC'; + ELSE + sort_order := 'ASC'; + END IF; + END IF; + + sql_query := sql_query || ' + GROUP BY + moves.id, + moves.show, + moves.locator, + moves.submitted_at, + moves.orders_id, + moves.status, + moves.locked_by, + moves.too_assigned_id, + moves.counseling_transportation_office_id, + mto_shipments.requested_pickup_date, + mto_shipments.requested_delivery_date, + ppm_shipments.expected_departure_date, + orders.id, + service_members.id, + service_members.first_name, + service_members.last_name, + service_members.edipi, + service_members.emplid, + service_members.affiliation, + origin_duty_locations.name, + counseling_offices.name, + too_user.first_name, + too_user.last_name'; + sql_query := sql_query || format(' ORDER BY %s %s ', sort_column, sort_order); + sql_query := sql_query || ' LIMIT $13 OFFSET $14 '; + + RETURN QUERY EXECUTE sql_query + USING user_gbloc, customer_name, edipi, emplid, m_status, move_code, requested_move_date, date_submitted, + branch, origin_duty_location, counseling_office, too_assigned_user, per_page, offset_value; + +END; +$$ LANGUAGE plpgsql; + +-- fixing some capitalization discrepencies for consistency +UPDATE re_services +SET name = 'International POE fuel surcharge' +WHERE name = 'International POE Fuel Surcharge'; + +UPDATE re_services +SET name = 'International POD fuel surcharge' +WHERE name = 'International POD Fuel Surcharge'; + +UPDATE re_services +SET name = 'International destination SIT fuel surcharge' +WHERE name = 'International Destination SIT Fuel Surcharge'; + +UPDATE re_services +SET name = 'International origin SIT fuel surcharge' +WHERE name = 'International Origin SIT Fuel Surcharge'; + +UPDATE re_services +SET name = 'International shipping & linehaul' +WHERE name = 'International Shipping & Linehaul'; \ No newline at end of file diff --git a/migrations/app/schema/20250213151815_fix_spacing_fetch_documents.up.sql b/migrations/app/schema/20250213151815_fix_spacing_fetch_documents.up.sql new file mode 100644 index 00000000000..e5dd6537ee8 --- /dev/null +++ b/migrations/app/schema/20250213151815_fix_spacing_fetch_documents.up.sql @@ -0,0 +1,22 @@ +CREATE OR REPLACE FUNCTION public.fetch_documents(docCursor refcursor, useruploadCursor refcursor, uploadCursor refcursor, _docID uuid) RETURNS setof refcursor AS $$ +BEGIN + OPEN $1 FOR + SELECT documents.created_at, documents.deleted_at, documents.id, documents.service_member_id, documents.updated_at + FROM documents AS documents + WHERE documents.id = _docID and documents.deleted_at is null + LIMIT 1; + RETURN NEXT $1; + OPEN $2 FOR + SELECT user_uploads.created_at, user_uploads.deleted_at, user_uploads.document_id, user_uploads.id, user_uploads.updated_at, + user_uploads.upload_id, user_uploads.uploader_id + FROM user_uploads AS user_uploads + WHERE user_uploads.deleted_at is null and user_uploads.document_id = _docID + ORDER BY created_at asc; + RETURN NEXT $2; + OPEN $3 FOR + SELECT uploads.id, uploads.bytes, uploads.checksum, uploads.content_type, uploads.created_at, uploads.deleted_at, uploads.filename, + uploads.rotation, uploads.storage_key, uploads.updated_at, uploads.upload_type FROM uploads AS uploads , user_uploads + WHERE uploads.deleted_at is null and uploads.id = user_uploads.upload_id and user_uploads.deleted_at is null and user_uploads.document_id = _docID; + RETURN NEXT $3; +END; +$$ LANGUAGE plpgsql; \ No newline at end of file diff --git a/migrations/app/schema/20250213214427_drop_received_by_gex_payment_request_status_type.up.sql b/migrations/app/schema/20250213214427_drop_received_by_gex_payment_request_status_type.up.sql new file mode 100644 index 00000000000..e6fa11a91f3 --- /dev/null +++ b/migrations/app/schema/20250213214427_drop_received_by_gex_payment_request_status_type.up.sql @@ -0,0 +1,34 @@ +-- This migration removes unused payment request status type of RECEIVED_BY_GEX +-- all previous payment requests using type were updated to TPPS_RECEIVED in +-- migrations/app/schema/20240725190050_update_payment_request_status_tpps_received.up.sql + +-- update again in case new payment requests have used this status +UPDATE payment_requests SET status = 'TPPS_RECEIVED' where status = 'RECEIVED_BY_GEX'; + +--- rename existing enum +ALTER TYPE payment_request_status RENAME TO payment_request_status_temp; + +-- create a new enum with both old and new statuses - both old and new statuses must exist in the enum to do the update setting old to new +CREATE TYPE payment_request_status AS ENUM( + 'PENDING', + 'REVIEWED', + 'SENT_TO_GEX', + 'PAID', + 'REVIEWED_AND_ALL_SERVICE_ITEMS_REJECTED', + 'EDI_ERROR', + 'DEPRECATED', + 'TPPS_RECEIVED' + ); + +alter table payment_requests alter column status drop default; +alter table payment_requests alter column status drop not null; + +-- alter the payment_requests status column to use the new enum +ALTER TABLE payment_requests ALTER COLUMN status TYPE payment_request_status USING status::text::payment_request_status; + +-- get rid of the temp type +DROP TYPE payment_request_status_temp; + +ALTER TABLE payment_requests +ALTER COLUMN status SET DEFAULT 'PENDING', +ALTER COLUMN status SET NOT NULL; \ No newline at end of file diff --git a/migrations/app/secure/20250110214012_homesafeconnect_cert.up.sql b/migrations/app/secure/20250110214012_homesafeconnect_cert.up.sql deleted file mode 100644 index f9862f58a7c..00000000000 --- a/migrations/app/secure/20250110214012_homesafeconnect_cert.up.sql +++ /dev/null @@ -1,4 +0,0 @@ --- Local test migration. --- This will be run on development environments. --- It should mirror what you intend to apply on prd/stg/exp/demo --- DO NOT include any sensitive data. diff --git a/package.json b/package.json index 77eeb2bcc59..5fb79cde889 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "reselect": "^4.1.8", "sass": "^1.77.6", "swagger-client": "^3.18.5", - "swagger-ui-dist": "^5.18.2", + "swagger-ui-dist": "^5.2.0", "uswds": "2.13.3", "uuid": "^9.0.0", "webpack": "5", diff --git a/pkg/cli/receiver.go b/pkg/cli/receiver.go new file mode 100644 index 00000000000..ed71d45d209 --- /dev/null +++ b/pkg/cli/receiver.go @@ -0,0 +1,61 @@ +package cli + +import ( + "fmt" + + "github.com/spf13/pflag" + "github.com/spf13/viper" +) + +const ( + // ReceiverBackendFlag is the Receiver Backend Flag + ReceiverBackendFlag string = "receiver-backend" + // SNSTagsUpdatedTopicFlag is the SNS Tags Updated Topic Flag + SNSTagsUpdatedTopicFlag string = "sns-tags-updated-topic" + // SNSRegionFlag is the SNS Region flag + SNSRegionFlag string = "sns-region" + // SNSAccountId is the application's AWS account id + SNSAccountId string = "aws-account-id" + // ReceiverCleanupOnStartFlag is the Receiver Cleanup On Start Flag + ReceiverCleanupOnStartFlag string = "receiver-cleanup-on-start" +) + +// InitReceiverFlags initializes Storage command line flags +func InitReceiverFlags(flag *pflag.FlagSet) { + flag.String(ReceiverBackendFlag, "local", "Receiver backend to use, either local or sns_sqs.") + flag.String(SNSTagsUpdatedTopicFlag, "", "SNS Topic for receiving event messages") + flag.String(SNSRegionFlag, "", "Region used for SNS and SQS") + flag.String(SNSAccountId, "", "SNS account Id") + flag.Bool(ReceiverCleanupOnStartFlag, false, "Receiver will cleanup previous aws artifacts on start.") +} + +// CheckReceiver validates Storage command line flags +func CheckReceiver(v *viper.Viper) error { + + receiverBackend := v.GetString(ReceiverBackendFlag) + if !stringSliceContains([]string{"local", "sns_sqs"}, receiverBackend) { + return fmt.Errorf("invalid receiver_backend %s, expecting local or sns_sqs", receiverBackend) + } + + receiverCleanupOnStart := v.GetString(ReceiverCleanupOnStartFlag) + if !stringSliceContains([]string{"true", "false"}, receiverCleanupOnStart) { + return fmt.Errorf("invalid receiver_cleanup_on_start %s, expecting true or false", receiverCleanupOnStart) + } + + if receiverBackend == "sns_sqs" { + r := v.GetString(SNSRegionFlag) + if r == "" { + return fmt.Errorf("invalid value for %s: %s", SNSRegionFlag, r) + } + topic := v.GetString(SNSTagsUpdatedTopicFlag) + if topic == "" { + return fmt.Errorf("invalid value for %s: %s", SNSTagsUpdatedTopicFlag, topic) + } + accountId := v.GetString(SNSAccountId) + if topic == "" { + return fmt.Errorf("invalid value for %s: %s", SNSAccountId, accountId) + } + } + + return nil +} diff --git a/pkg/cli/receiver_test.go b/pkg/cli/receiver_test.go new file mode 100644 index 00000000000..7095a672f5f --- /dev/null +++ b/pkg/cli/receiver_test.go @@ -0,0 +1,6 @@ +package cli + +func (suite *cliTestSuite) TestConfigReceiver() { + suite.Setup(InitReceiverFlags, []string{}) + suite.NoError(CheckReceiver(suite.viper)) +} diff --git a/pkg/factory/address_factory.go b/pkg/factory/address_factory.go index 27d92999d00..345967bc625 100644 --- a/pkg/factory/address_factory.go +++ b/pkg/factory/address_factory.go @@ -201,3 +201,93 @@ func GetTraitAddress4() []Customization { }, } } + +// GetTraitAddressAKZone1 is an address in Zone 1 of AK +func GetTraitAddressAKZone1() []Customization { + + return []Customization{ + { + Model: models.Address{ + StreetAddress1: "82 Joe Gibbs Rd", + StreetAddress2: models.StringPointer("P.O. Box 1234"), + StreetAddress3: models.StringPointer("c/o Another Person"), + City: "ANCHORAGE", + State: "AK", + PostalCode: "99695", + IsOconus: models.BoolPointer(true), + }, + }, + } +} + +// GetTraitAddressAKZone2 is an address in Zone 2 of Alaska +func GetTraitAddressAKZone2() []Customization { + + return []Customization{ + { + Model: models.Address{ + StreetAddress1: "44 John Riggins Rd", + StreetAddress2: models.StringPointer("P.O. Box 1234"), + StreetAddress3: models.StringPointer("c/o Another Person"), + City: "FAIRBANKS", + State: "AK", + PostalCode: "99703", + IsOconus: models.BoolPointer(true), + }, + }, + } +} + +// GetTraitAddressAKZone3 is an address in Zone 3 of Alaska +func GetTraitAddressAKZone3() []Customization { + + return []Customization{ + { + Model: models.Address{ + StreetAddress1: "26 Clinton Portis Rd", + StreetAddress2: models.StringPointer("P.O. Box 1234"), + StreetAddress3: models.StringPointer("c/o Another Person"), + City: "KODIAK", + State: "AK", + PostalCode: "99697", + IsOconus: models.BoolPointer(true), + }, + }, + } +} + +// GetTraitAddressAKZone4 is an address in Zone 4 of Alaska +func GetTraitAddressAKZone4() []Customization { + + return []Customization{ + { + Model: models.Address{ + StreetAddress1: "8 Alex Ovechkin Rd", + StreetAddress2: models.StringPointer("P.O. Box 1234"), + StreetAddress3: models.StringPointer("c/o Another Person"), + City: "JUNEAU", + State: "AK", + PostalCode: "99801", + IsOconus: models.BoolPointer(true), + }, + }, + } +} + +// GetTraitAddressAKZone5 is an address in Zone 5 of Alaska for NSRA15 rates +func GetTraitAddressAKZone5() []Customization { + + return []Customization{ + { + Model: models.Address{ + StreetAddress1: "Street Address 1", + StreetAddress2: models.StringPointer("P.O. Box 1234"), + StreetAddress3: models.StringPointer("c/o Another Person"), + City: "ANAKTUVUK", + State: "AK", + PostalCode: "99721", + IsOconus: models.BoolPointer(true), + }, + }, + } +} diff --git a/pkg/factory/mto_service_item_factory.go b/pkg/factory/mto_service_item_factory.go index 2bc9c236f4d..623b3a2c583 100644 --- a/pkg/factory/mto_service_item_factory.go +++ b/pkg/factory/mto_service_item_factory.go @@ -747,7 +747,7 @@ func BuildFullOriginMTOServiceItems(db *pop.Connection, customs []Customization, // are required params, and entryDate and departureDate can be specificed // optionally. func BuildOriginSITServiceItems(db *pop.Connection, move models.Move, shipment models.MTOShipment, entryDate *time.Time, departureDate *time.Time) models.MTOServiceItems { - postalCode := "90210" + postalCode := shipment.PickupAddress.PostalCode reason := "peak season all trucks in use" defaultEntryDate := time.Now().AddDate(0, 0, -45) defaultApprovedAtDate := time.Now() @@ -759,87 +759,193 @@ func BuildOriginSITServiceItems(db *pop.Connection, move models.Move, shipment m defaultDepartureDate = departureDate } - dofsit := BuildRealMTOServiceItemWithAllDeps(db, models.ReServiceCodeDOFSIT, move, shipment, []Customization{ - { - Model: models.MTOServiceItem{ - Status: models.MTOServiceItemStatusApproved, - ApprovedAt: &defaultApprovedAtDate, - SITEntryDate: &defaultEntryDate, - SITPostalCode: &postalCode, - Reason: &reason, + var firstDaySit, addlDaySit, pickupSit, fuelSurchargeSit models.MTOServiceItem + + if shipment.MarketCode != models.MarketCodeInternational { + firstDaySit = BuildRealMTOServiceItemWithAllDeps(db, models.ReServiceCodeDOFSIT, move, shipment, []Customization{ + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &defaultApprovedAtDate, + SITEntryDate: &defaultEntryDate, + SITPostalCode: &postalCode, + Reason: &reason, + }, }, - }, - { - Model: models.Address{}, - Type: &Addresses.SITOriginHHGActualAddress, - }, - { - Model: models.Address{}, - Type: &Addresses.SITOriginHHGOriginalAddress, - }, - }, nil) + { + Model: models.Address{}, + Type: &Addresses.SITOriginHHGActualAddress, + }, + { + Model: models.Address{}, + Type: &Addresses.SITOriginHHGOriginalAddress, + }, + }, nil) - doasit := BuildRealMTOServiceItemWithAllDeps(db, models.ReServiceCodeDOASIT, move, shipment, []Customization{ - { - Model: models.MTOServiceItem{ - Status: models.MTOServiceItemStatusApproved, - ApprovedAt: &defaultApprovedAtDate, - SITEntryDate: &defaultEntryDate, - SITPostalCode: &postalCode, - Reason: &reason, + addlDaySit = BuildRealMTOServiceItemWithAllDeps(db, models.ReServiceCodeDOASIT, move, shipment, []Customization{ + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &defaultApprovedAtDate, + SITEntryDate: &defaultEntryDate, + SITPostalCode: &postalCode, + Reason: &reason, + }, }, - }, - { - Model: models.Address{}, - Type: &Addresses.SITOriginHHGActualAddress, - }, - { - Model: models.Address{}, - Type: &Addresses.SITOriginHHGOriginalAddress, - }, - }, nil) + { + Model: models.Address{}, + Type: &Addresses.SITOriginHHGActualAddress, + }, + { + Model: models.Address{}, + Type: &Addresses.SITOriginHHGOriginalAddress, + }, + }, nil) - dopsit := BuildRealMTOServiceItemWithAllDeps(db, models.ReServiceCodeDOPSIT, move, shipment, []Customization{ - { - Model: models.MTOServiceItem{ - Status: models.MTOServiceItemStatusApproved, - ApprovedAt: &defaultApprovedAtDate, - SITEntryDate: &defaultEntryDate, - SITDepartureDate: defaultDepartureDate, - SITPostalCode: &postalCode, - Reason: &reason, + pickupSit = BuildRealMTOServiceItemWithAllDeps(db, models.ReServiceCodeDOPSIT, move, shipment, []Customization{ + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &defaultApprovedAtDate, + SITEntryDate: &defaultEntryDate, + SITDepartureDate: defaultDepartureDate, + SITPostalCode: &postalCode, + Reason: &reason, + }, }, - }, - { - Model: models.Address{}, - Type: &Addresses.SITOriginHHGActualAddress, - }, - { - Model: models.Address{}, - Type: &Addresses.SITOriginHHGOriginalAddress, - }, - }, nil) + { + Model: models.Address{}, + Type: &Addresses.SITOriginHHGActualAddress, + }, + { + Model: models.Address{}, + Type: &Addresses.SITOriginHHGOriginalAddress, + }, + }, nil) - dosfsc := BuildRealMTOServiceItemWithAllDeps(db, models.ReServiceCodeDOSFSC, move, shipment, []Customization{ - { - Model: models.MTOServiceItem{ - Status: models.MTOServiceItemStatusApproved, - ApprovedAt: &defaultApprovedAtDate, - SITEntryDate: &defaultEntryDate, - SITPostalCode: &postalCode, - Reason: &reason, + fuelSurchargeSit = BuildRealMTOServiceItemWithAllDeps(db, models.ReServiceCodeDOSFSC, move, shipment, []Customization{ + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &defaultApprovedAtDate, + SITEntryDate: &defaultEntryDate, + SITPostalCode: &postalCode, + Reason: &reason, + }, }, - }, - { - Model: models.Address{}, - Type: &Addresses.SITOriginHHGActualAddress, - }, - { - Model: models.Address{}, - Type: &Addresses.SITOriginHHGOriginalAddress, - }, - }, nil) - return []models.MTOServiceItem{dofsit, doasit, dopsit, dosfsc} + { + Model: models.Address{}, + Type: &Addresses.SITOriginHHGActualAddress, + }, + { + Model: models.Address{}, + Type: &Addresses.SITOriginHHGOriginalAddress, + }, + }, nil) + } else { + firstDaySit = BuildMTOServiceItem(db, []Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeIOFSIT, + }, + }, + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &defaultApprovedAtDate, + SITEntryDate: &defaultEntryDate, + SITPostalCode: &postalCode, + Reason: &reason, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + }, nil) + + addlDaySit = BuildMTOServiceItem(db, []Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeIOASIT, + }, + }, + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &defaultApprovedAtDate, + SITEntryDate: &defaultEntryDate, + SITPostalCode: &postalCode, + Reason: &reason, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + }, nil) + + pickupSit = BuildMTOServiceItem(db, []Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeIOPSIT, + }, + }, + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &defaultApprovedAtDate, + SITEntryDate: &defaultEntryDate, + SITDepartureDate: defaultDepartureDate, + SITPostalCode: &postalCode, + Reason: &reason, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + }, nil) + + fuelSurchargeSit = BuildMTOServiceItem(db, []Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeIOSFSC, + }, + }, + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &defaultApprovedAtDate, + SITEntryDate: &defaultEntryDate, + SITPostalCode: &postalCode, + Reason: &reason, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + }, nil) + } + + return []models.MTOServiceItem{firstDaySit, addlDaySit, pickupSit, fuelSurchargeSit} } // BuildDestSITServiceItems makes all of the service items that are @@ -847,7 +953,7 @@ func BuildOriginSITServiceItems(db *pop.Connection, move models.Move, shipment m // are required params, and entryDate and departureDate can be specificed // optionally. func BuildDestSITServiceItems(db *pop.Connection, move models.Move, shipment models.MTOShipment, entryDate *time.Time, departureDate *time.Time) models.MTOServiceItems { - postalCode := "90210" + postalCode := shipment.DestinationAddress.PostalCode reason := "peak season all trucks in use" defaultEntryDate := time.Now().AddDate(0, 0, -45) defaultApprovedAtDate := time.Now() @@ -859,71 +965,181 @@ func BuildDestSITServiceItems(db *pop.Connection, move models.Move, shipment mod defaultDepartureDate = departureDate } - ddfsit := BuildRealMTOServiceItemWithAllDeps(db, models.ReServiceCodeDDFSIT, move, shipment, []Customization{ - { - Model: models.MTOServiceItem{ - Status: models.MTOServiceItemStatusApproved, - ApprovedAt: &defaultApprovedAtDate, - SITEntryDate: &defaultEntryDate, - SITPostalCode: &postalCode, - Reason: &reason, + var firstDaySit models.MTOServiceItem + var addlDaySit models.MTOServiceItem + var deliverySit models.MTOServiceItem + var fuelSurchargeSit models.MTOServiceItem + + // handling domestic SIT service item creation vs international + if shipment.MarketCode != models.MarketCodeInternational { + firstDaySit = BuildRealMTOServiceItemWithAllDeps(db, models.ReServiceCodeDDFSIT, move, shipment, []Customization{ + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &defaultApprovedAtDate, + SITEntryDate: &defaultEntryDate, + SITPostalCode: &postalCode, + Reason: &reason, + }, }, - }, - }, nil) + }, nil) - ddasit := BuildRealMTOServiceItemWithAllDeps(db, models.ReServiceCodeDDASIT, move, shipment, []Customization{ - { - Model: models.MTOServiceItem{ - Status: models.MTOServiceItemStatusApproved, - ApprovedAt: &defaultApprovedAtDate, - SITEntryDate: &defaultEntryDate, - SITPostalCode: &postalCode, - Reason: &reason, + addlDaySit = BuildRealMTOServiceItemWithAllDeps(db, models.ReServiceCodeDDASIT, move, shipment, []Customization{ + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &defaultApprovedAtDate, + SITEntryDate: &defaultEntryDate, + SITPostalCode: &postalCode, + Reason: &reason, + }, }, - }, - }, nil) + }, nil) - dddsit := BuildRealMTOServiceItemWithAllDeps(db, models.ReServiceCodeDDDSIT, move, shipment, []Customization{ - { - Model: models.MTOServiceItem{ - Status: models.MTOServiceItemStatusApproved, - ApprovedAt: &defaultApprovedAtDate, - SITEntryDate: &defaultEntryDate, - SITDepartureDate: defaultDepartureDate, - SITPostalCode: &postalCode, - Reason: &reason, + deliverySit = BuildRealMTOServiceItemWithAllDeps(db, models.ReServiceCodeDDDSIT, move, shipment, []Customization{ + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &defaultApprovedAtDate, + SITEntryDate: &defaultEntryDate, + SITDepartureDate: defaultDepartureDate, + SITPostalCode: &postalCode, + Reason: &reason, + }, }, - }, - { - Model: models.Address{}, - Type: &Addresses.SITDestinationFinalAddress, - }, - { - Model: models.Address{}, - Type: &Addresses.SITDestinationOriginalAddress, - }, - }, nil) + { + Model: models.Address{}, + Type: &Addresses.SITDestinationFinalAddress, + }, + { + Model: models.Address{}, + Type: &Addresses.SITDestinationOriginalAddress, + }, + }, nil) - ddsfsc := BuildRealMTOServiceItemWithAllDeps(db, models.ReServiceCodeDDSFSC, move, shipment, []Customization{ - { - Model: models.MTOServiceItem{ - Status: models.MTOServiceItemStatusApproved, - ApprovedAt: &defaultApprovedAtDate, - SITEntryDate: &defaultEntryDate, - SITPostalCode: &postalCode, - Reason: &reason, + fuelSurchargeSit = BuildRealMTOServiceItemWithAllDeps(db, models.ReServiceCodeDDSFSC, move, shipment, []Customization{ + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &defaultApprovedAtDate, + SITEntryDate: &defaultEntryDate, + SITPostalCode: &postalCode, + Reason: &reason, + }, }, - }, - { - Model: models.Address{}, - Type: &Addresses.SITDestinationFinalAddress, - }, - { - Model: models.Address{}, - Type: &Addresses.SITDestinationOriginalAddress, - }, - }, nil) - return []models.MTOServiceItem{ddfsit, ddasit, dddsit, ddsfsc} + { + Model: models.Address{}, + Type: &Addresses.SITDestinationFinalAddress, + }, + { + Model: models.Address{}, + Type: &Addresses.SITDestinationOriginalAddress, + }, + }, nil) + } else { + firstDaySit = BuildMTOServiceItem(db, []Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeIDFSIT, + }, + }, + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &defaultApprovedAtDate, + SITEntryDate: &defaultEntryDate, + SITPostalCode: &postalCode, + Reason: &reason, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + }, nil) + + addlDaySit = BuildMTOServiceItem(db, []Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeIDASIT, + }, + }, + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &defaultApprovedAtDate, + SITEntryDate: &defaultEntryDate, + SITPostalCode: &postalCode, + Reason: &reason, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + }, nil) + + deliverySit = BuildMTOServiceItem(db, []Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeIDDSIT, + }, + }, + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &defaultApprovedAtDate, + SITEntryDate: &defaultEntryDate, + SITDepartureDate: defaultDepartureDate, + SITPostalCode: &postalCode, + Reason: &reason, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + }, nil) + + fuelSurchargeSit = BuildMTOServiceItem(db, []Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeIDSFSC, + }, + }, + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &defaultApprovedAtDate, + SITEntryDate: &defaultEntryDate, + SITPostalCode: &postalCode, + Reason: &reason, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + }, nil) + } + + return []models.MTOServiceItem{firstDaySit, addlDaySit, deliverySit, fuelSurchargeSit} } func BuildDestSITServiceItemsNoSITDepartureDate(db *pop.Connection, move models.Move, shipment models.MTOShipment, entryDate *time.Time) models.MTOServiceItems { diff --git a/pkg/factory/mto_service_item_factory_test.go b/pkg/factory/mto_service_item_factory_test.go index 6287381e981..fe98c4ddba0 100644 --- a/pkg/factory/mto_service_item_factory_test.go +++ b/pkg/factory/mto_service_item_factory_test.go @@ -346,6 +346,68 @@ func (suite *FactorySuite) TestBuildMTOServiceItem() { suite.Equal(expectedCodes, reServiceCodes) }) + suite.Run("Build SIT service items for international shipment - origin SIT", func() { + + customMove := BuildMove(suite.DB(), nil, nil) + customMTOShipment := BuildMTOShipment(suite.DB(), []Customization{ + { + Model: customMove, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + }, + }, + }, nil) + + oneMonthLater := time.Now().AddDate(0, 1, 0) + sitServiceItems := BuildOriginSITServiceItems(suite.DB(), customMove, customMTOShipment, &oneMonthLater, nil) + reServiceCodes := []models.ReServiceCode{} + + for i := range sitServiceItems { + reServiceCodes = append(reServiceCodes, sitServiceItems[i].ReService.Code) + } + expectedCodes := []models.ReServiceCode{ + models.ReServiceCodeIOFSIT, + models.ReServiceCodeIOASIT, + models.ReServiceCodeIOPSIT, + models.ReServiceCodeIOSFSC, + } + suite.Equal(expectedCodes, reServiceCodes) + }) + + suite.Run("Build SIT service items for international shipment - destination SIT", func() { + + customMove := BuildMove(suite.DB(), nil, nil) + customMTOShipment := BuildMTOShipment(suite.DB(), []Customization{ + { + Model: customMove, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + }, + }, + }, nil) + + oneMonthLater := time.Now().AddDate(0, 1, 0) + sitServiceItems := BuildDestSITServiceItems(suite.DB(), customMove, customMTOShipment, &oneMonthLater, nil) + reServiceCodes := []models.ReServiceCode{} + + for i := range sitServiceItems { + reServiceCodes = append(reServiceCodes, sitServiceItems[i].ReService.Code) + } + expectedCodes := []models.ReServiceCode{ + models.ReServiceCodeIDFSIT, + models.ReServiceCodeIDASIT, + models.ReServiceCodeIDDSIT, + models.ReServiceCodeIDSFSC, + } + suite.Equal(expectedCodes, reServiceCodes) + }) + suite.Run("Port Locations not populated by default", func() { serviceItem := BuildMTOServiceItem(suite.DB(), nil, nil) diff --git a/pkg/factory/mto_shipment_factory.go b/pkg/factory/mto_shipment_factory.go index 86819917c97..2350887c115 100644 --- a/pkg/factory/mto_shipment_factory.go +++ b/pkg/factory/mto_shipment_factory.go @@ -115,7 +115,11 @@ func buildMTOShipmentWithBuildType(db *pop.Connection, customs []Customization, if shipmentHasPickupDetails { newMTOShipment.RequestedPickupDate = models.TimePointer(time.Date(GHCTestYear, time.March, 15, 0, 0, 0, 0, time.UTC)) - newMTOShipment.ScheduledPickupDate = models.TimePointer(time.Date(GHCTestYear, time.March, 16, 0, 0, 0, 0, time.UTC)) + if cMtoShipment.ScheduledPickupDate == nil { + newMTOShipment.ScheduledPickupDate = models.TimePointer(time.Date(GHCTestYear, time.March, 16, 0, 0, 0, 0, time.UTC)) + } else { + newMTOShipment.ScheduledPickupDate = cMtoShipment.ScheduledPickupDate + } newMTOShipment.ActualPickupDate = models.TimePointer(time.Date(GHCTestYear, time.March, 16, 0, 0, 0, 0, time.UTC)) } @@ -212,8 +216,12 @@ func buildMTOShipmentWithBuildType(db *pop.Connection, customs []Customization, } if cMtoShipment.ScheduledPickupDate != nil { - requiredDeliveryDate := time.Date(GHCTestYear, time.April, 15, 0, 0, 0, 0, time.UTC) - newMTOShipment.RequiredDeliveryDate = &requiredDeliveryDate + if cMtoShipment.RequiredDeliveryDate != nil { + newMTOShipment.RequiredDeliveryDate = cMtoShipment.RequiredDeliveryDate + } else { + requiredDeliveryDate := time.Date(GHCTestYear, time.April, 15, 0, 0, 0, 0, time.UTC) + newMTOShipment.RequiredDeliveryDate = &requiredDeliveryDate + } } } @@ -234,7 +242,7 @@ func BuildBaseMTOShipment(db *pop.Connection, customs []Customization, traits [] // BuildMTOShipment creates a single MTOShipment and associated set relationships // It will make a move record, if one is not provided. // It will make pickup addresses if the shipment type is not one of (HHGOutOfNTS, PPM) -// It will make delivery addresses if the shipment type is not one of (HHGIntoNTSDom, PPM) +// It will make delivery addresses if the shipment type is not one of (HHGIntoNTS, PPM) // It will make a storage facility if the shipment type is HHGOutOfNTS func BuildMTOShipment(db *pop.Connection, customs []Customization, traits []Trait) models.MTOShipment { return buildMTOShipmentWithBuildType(db, customs, traits, mtoShipmentBuild) diff --git a/pkg/gen/ghcapi/configure_mymove.go b/pkg/gen/ghcapi/configure_mymove.go index 6000c169aaf..432343d4dab 100644 --- a/pkg/gen/ghcapi/configure_mymove.go +++ b/pkg/gen/ghcapi/configure_mymove.go @@ -4,6 +4,7 @@ package ghcapi import ( "crypto/tls" + "io" "net/http" "github.com/go-openapi/errors" @@ -64,6 +65,9 @@ func configureAPI(api *ghcoperations.MymoveAPI) http.Handler { api.BinProducer = runtime.ByteStreamProducer() api.JSONProducer = runtime.JSONProducer() + api.TextEventStreamProducer = runtime.ProducerFunc(func(w io.Writer, data interface{}) error { + return errors.NotImplemented("textEventStream producer has not yet been implemented") + }) // You may change here the memory limit for this multipart form parser. Below is the default (32 MB). // uploads.CreateUploadMaxParseMemory = 32 << 20 @@ -247,6 +251,11 @@ func configureAPI(api *ghcoperations.MymoveAPI) http.Handler { return middleware.NotImplemented("operation customer_support_remarks.GetCustomerSupportRemarksForMove has not yet been implemented") }) } + if api.QueuesGetDestinationRequestsQueueHandler == nil { + api.QueuesGetDestinationRequestsQueueHandler = queues.GetDestinationRequestsQueueHandlerFunc(func(params queues.GetDestinationRequestsQueueParams) middleware.Responder { + return middleware.NotImplemented("operation queues.GetDestinationRequestsQueue has not yet been implemented") + }) + } if api.GhcDocumentsGetDocumentHandler == nil { api.GhcDocumentsGetDocumentHandler = ghc_documents.GetDocumentHandlerFunc(func(params ghc_documents.GetDocumentParams) middleware.Responder { return middleware.NotImplemented("operation ghc_documents.GetDocument has not yet been implemented") @@ -397,6 +406,11 @@ func configureAPI(api *ghcoperations.MymoveAPI) http.Handler { return middleware.NotImplemented("operation uploads.GetUpload has not yet been implemented") }) } + if api.UploadsGetUploadStatusHandler == nil { + api.UploadsGetUploadStatusHandler = uploads.GetUploadStatusHandlerFunc(func(params uploads.GetUploadStatusParams) middleware.Responder { + return middleware.NotImplemented("operation uploads.GetUploadStatus has not yet been implemented") + }) + } if api.CalendarIsDateWeekendHolidayHandler == nil { api.CalendarIsDateWeekendHolidayHandler = calendar.IsDateWeekendHolidayHandlerFunc(func(params calendar.IsDateWeekendHolidayParams) middleware.Responder { return middleware.NotImplemented("operation calendar.IsDateWeekendHoliday has not yet been implemented") diff --git a/pkg/gen/ghcapi/doc.go b/pkg/gen/ghcapi/doc.go index 24f788c8fb2..24ba756c211 100644 --- a/pkg/gen/ghcapi/doc.go +++ b/pkg/gen/ghcapi/doc.go @@ -21,6 +21,7 @@ // Produces: // - application/pdf // - application/json +// - text/event-stream // // swagger:meta package ghcapi diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index fb4caa55f90..440c62b59a5 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -4682,6 +4682,156 @@ func init() { } } }, + "/queues/destination-requests": { + "get": { + "description": "A TOO will view this queue when they have destination requests tied to their GBLOC. This includes unapproved destination SIT service items, destination shuttle service items and destination address requests that are not yet approved by the TOO.\n", + "produces": [ + "application/json" + ], + "tags": [ + "queues" + ], + "summary": "Gets queued list of all customer moves by GBLOC that have both CONUS \u0026 OCONUS destination requests (destination SIT, destination shuttle, address requests)", + "operationId": "getDestinationRequestsQueue", + "parameters": [ + { + "type": "integer", + "description": "requested page of results", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "results per page", + "name": "perPage", + "in": "query" + }, + { + "enum": [ + "customerName", + "edipi", + "emplid", + "branch", + "locator", + "status", + "originDutyLocation", + "destinationDutyLocation", + "requestedMoveDate", + "appearedInTooAt", + "assignedTo", + "counselingOffice" + ], + "type": "string", + "description": "field that results should be sorted by", + "name": "sort", + "in": "query" + }, + { + "enum": [ + "asc", + "desc" + ], + "type": "string", + "description": "direction of sort order if applied", + "name": "order", + "in": "query" + }, + { + "type": "string", + "name": "branch", + "in": "query" + }, + { + "type": "string", + "name": "locator", + "in": "query" + }, + { + "type": "string", + "name": "customerName", + "in": "query" + }, + { + "type": "string", + "name": "edipi", + "in": "query" + }, + { + "type": "string", + "name": "emplid", + "in": "query" + }, + { + "uniqueItems": true, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi", + "name": "originDutyLocation", + "in": "query" + }, + { + "type": "string", + "name": "destinationDutyLocation", + "in": "query" + }, + { + "type": "string", + "format": "date-time", + "name": "appearedInTooAt", + "in": "query" + }, + { + "type": "string", + "description": "filters the requested pickup date of a shipment on the move", + "name": "requestedMoveDate", + "in": "query" + }, + { + "uniqueItems": true, + "type": "array", + "items": { + "enum": [ + "SUBMITTED", + "SERVICE COUNSELING COMPLETED", + "APPROVALS REQUESTED" + ], + "type": "string" + }, + "description": "Filtering for the status.", + "name": "status", + "in": "query" + }, + { + "type": "string", + "description": "Used to illustrate which user is assigned to this move.\n", + "name": "assignedTo", + "in": "query" + }, + { + "type": "string", + "description": "filters using a counselingOffice name of the move", + "name": "counselingOffice", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successfully returned all moves matching the criteria", + "schema": { + "$ref": "#/definitions/QueueMovesResult" + } + }, + "403": { + "$ref": "#/responses/PermissionDenied" + }, + "500": { + "$ref": "#/responses/ServerError" + } + } + } + }, "/queues/moves": { "get": { "description": "An office TOO user will be assigned a transportation office that will determine which moves are displayed in their queue based on the origin duty location. GHC moves will show up here onced they have reached the submitted status sent by the customer and have move task orders, shipments, and service items to approve.\n", @@ -6328,7 +6478,7 @@ func init() { "operationId": "getTransportationOfficesGBLOCs", "responses": { "200": { - "description": "Successfully retrieved GBLOCs", + "description": "Successfully retrieved transportation offices", "schema": { "$ref": "#/definitions/GBLOCs" } @@ -6540,6 +6690,58 @@ func init() { } } }, + "/uploads/{uploadID}/status": { + "get": { + "description": "Returns status of an upload based on antivirus run", + "produces": [ + "text/event-stream" + ], + "tags": [ + "uploads" + ], + "summary": "Returns status of an upload", + "operationId": "getUploadStatus", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "UUID of the upload to return status of", + "name": "uploadID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "the requested upload status", + "schema": { + "type": "string", + "enum": [ + "INFECTED", + "CLEAN", + "PROCESSING" + ], + "readOnly": true + } + }, + "400": { + "description": "invalid request", + "schema": { + "$ref": "#/definitions/InvalidRequestResponsePayload" + } + }, + "403": { + "description": "not authorized" + }, + "404": { + "description": "not found" + }, + "500": { + "description": "server error" + } + } + } + }, "/uploads/{uploadID}/update": { "patch": { "description": "Uploads represent a single digital file, such as a JPEG or PDF. The rotation is relevant to how it is displayed on the page.", @@ -7182,10 +7384,6 @@ func init() { "agency": { "$ref": "#/definitions/Affiliation" }, - "dependentsAuthorized": { - "type": "boolean", - "x-nullable": true - }, "dependentsTwelveAndOver": { "description": "Indicates the number of dependents of the age twelve or older for a move. This is only present on OCONUS moves.", "type": "integer", @@ -7264,6 +7462,10 @@ func init() { "x-nullable": true, "$ref": "#/definitions/DeptIndicator" }, + "dependentsAuthorized": { + "type": "boolean", + "x-nullable": true + }, "grade": { "$ref": "#/definitions/Grade" }, @@ -10318,6 +10520,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" @@ -14294,10 +14505,6 @@ func init() { "agency": { "$ref": "#/definitions/Affiliation" }, - "dependentsAuthorized": { - "type": "boolean", - "x-nullable": true - }, "dependentsTwelveAndOver": { "description": "Indicates the number of dependents of the age twelve or older for a move. This is only present on OCONUS moves.", "type": "integer", @@ -14644,6 +14851,10 @@ func init() { "x-nullable": true, "$ref": "#/definitions/DeptIndicator" }, + "dependentsAuthorized": { + "type": "boolean", + "x-nullable": true + }, "grade": { "$ref": "#/definitions/Grade" }, @@ -21571,6 +21782,162 @@ func init() { } } }, + "/queues/destination-requests": { + "get": { + "description": "A TOO will view this queue when they have destination requests tied to their GBLOC. This includes unapproved destination SIT service items, destination shuttle service items and destination address requests that are not yet approved by the TOO.\n", + "produces": [ + "application/json" + ], + "tags": [ + "queues" + ], + "summary": "Gets queued list of all customer moves by GBLOC that have both CONUS \u0026 OCONUS destination requests (destination SIT, destination shuttle, address requests)", + "operationId": "getDestinationRequestsQueue", + "parameters": [ + { + "type": "integer", + "description": "requested page of results", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "description": "results per page", + "name": "perPage", + "in": "query" + }, + { + "enum": [ + "customerName", + "edipi", + "emplid", + "branch", + "locator", + "status", + "originDutyLocation", + "destinationDutyLocation", + "requestedMoveDate", + "appearedInTooAt", + "assignedTo", + "counselingOffice" + ], + "type": "string", + "description": "field that results should be sorted by", + "name": "sort", + "in": "query" + }, + { + "enum": [ + "asc", + "desc" + ], + "type": "string", + "description": "direction of sort order if applied", + "name": "order", + "in": "query" + }, + { + "type": "string", + "name": "branch", + "in": "query" + }, + { + "type": "string", + "name": "locator", + "in": "query" + }, + { + "type": "string", + "name": "customerName", + "in": "query" + }, + { + "type": "string", + "name": "edipi", + "in": "query" + }, + { + "type": "string", + "name": "emplid", + "in": "query" + }, + { + "uniqueItems": true, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi", + "name": "originDutyLocation", + "in": "query" + }, + { + "type": "string", + "name": "destinationDutyLocation", + "in": "query" + }, + { + "type": "string", + "format": "date-time", + "name": "appearedInTooAt", + "in": "query" + }, + { + "type": "string", + "description": "filters the requested pickup date of a shipment on the move", + "name": "requestedMoveDate", + "in": "query" + }, + { + "uniqueItems": true, + "type": "array", + "items": { + "enum": [ + "SUBMITTED", + "SERVICE COUNSELING COMPLETED", + "APPROVALS REQUESTED" + ], + "type": "string" + }, + "description": "Filtering for the status.", + "name": "status", + "in": "query" + }, + { + "type": "string", + "description": "Used to illustrate which user is assigned to this move.\n", + "name": "assignedTo", + "in": "query" + }, + { + "type": "string", + "description": "filters using a counselingOffice name of the move", + "name": "counselingOffice", + "in": "query" + } + ], + "responses": { + "200": { + "description": "Successfully returned all moves matching the criteria", + "schema": { + "$ref": "#/definitions/QueueMovesResult" + } + }, + "403": { + "description": "The request was denied", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "500": { + "description": "A server error occurred", + "schema": { + "$ref": "#/definitions/Error" + } + } + } + } + }, "/queues/moves": { "get": { "description": "An office TOO user will be assigned a transportation office that will determine which moves are displayed in their queue based on the origin duty location. GHC moves will show up here onced they have reached the submitted status sent by the customer and have move task orders, shipments, and service items to approve.\n", @@ -23586,7 +23953,7 @@ func init() { "operationId": "getTransportationOfficesGBLOCs", "responses": { "200": { - "description": "Successfully retrieved GBLOCs", + "description": "Successfully retrieved transportation offices", "schema": { "$ref": "#/definitions/GBLOCs" } @@ -23837,6 +24204,58 @@ func init() { } } }, + "/uploads/{uploadID}/status": { + "get": { + "description": "Returns status of an upload based on antivirus run", + "produces": [ + "text/event-stream" + ], + "tags": [ + "uploads" + ], + "summary": "Returns status of an upload", + "operationId": "getUploadStatus", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "UUID of the upload to return status of", + "name": "uploadID", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "the requested upload status", + "schema": { + "type": "string", + "enum": [ + "INFECTED", + "CLEAN", + "PROCESSING" + ], + "readOnly": true + } + }, + "400": { + "description": "invalid request", + "schema": { + "$ref": "#/definitions/InvalidRequestResponsePayload" + } + }, + "403": { + "description": "not authorized" + }, + "404": { + "description": "not found" + }, + "500": { + "description": "server error" + } + } + } + }, "/uploads/{uploadID}/update": { "patch": { "description": "Uploads represent a single digital file, such as a JPEG or PDF. The rotation is relevant to how it is displayed on the page.", @@ -24483,10 +24902,6 @@ func init() { "agency": { "$ref": "#/definitions/Affiliation" }, - "dependentsAuthorized": { - "type": "boolean", - "x-nullable": true - }, "dependentsTwelveAndOver": { "description": "Indicates the number of dependents of the age twelve or older for a move. This is only present on OCONUS moves.", "type": "integer", @@ -24569,6 +24984,10 @@ func init() { "x-nullable": true, "$ref": "#/definitions/DeptIndicator" }, + "dependentsAuthorized": { + "type": "boolean", + "x-nullable": true + }, "grade": { "$ref": "#/definitions/Grade" }, @@ -27623,6 +28042,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" @@ -31727,10 +32155,6 @@ func init() { "agency": { "$ref": "#/definitions/Affiliation" }, - "dependentsAuthorized": { - "type": "boolean", - "x-nullable": true - }, "dependentsTwelveAndOver": { "description": "Indicates the number of dependents of the age twelve or older for a move. This is only present on OCONUS moves.", "type": "integer", @@ -32081,6 +32505,10 @@ func init() { "x-nullable": true, "$ref": "#/definitions/DeptIndicator" }, + "dependentsAuthorized": { + "type": "boolean", + "x-nullable": true + }, "grade": { "$ref": "#/definitions/Grade" }, diff --git a/pkg/gen/ghcapi/ghcoperations/mymove_api.go b/pkg/gen/ghcapi/ghcoperations/mymove_api.go index 772eafbd710..ee86793406f 100644 --- a/pkg/gen/ghcapi/ghcoperations/mymove_api.go +++ b/pkg/gen/ghcapi/ghcoperations/mymove_api.go @@ -7,6 +7,7 @@ package ghcoperations import ( "fmt" + "io" "net/http" "strings" @@ -70,6 +71,9 @@ func NewMymoveAPI(spec *loads.Document) *MymoveAPI { BinProducer: runtime.ByteStreamProducer(), JSONProducer: runtime.JSONProducer(), + TextEventStreamProducer: runtime.ProducerFunc(func(w io.Writer, data interface{}) error { + return errors.NotImplemented("textEventStream producer has not yet been implemented") + }), OrderAcknowledgeExcessUnaccompaniedBaggageWeightRiskHandler: order.AcknowledgeExcessUnaccompaniedBaggageWeightRiskHandlerFunc(func(params order.AcknowledgeExcessUnaccompaniedBaggageWeightRiskParams) middleware.Responder { return middleware.NotImplemented("operation order.AcknowledgeExcessUnaccompaniedBaggageWeightRisk has not yet been implemented") @@ -176,6 +180,9 @@ func NewMymoveAPI(spec *loads.Document) *MymoveAPI { CustomerSupportRemarksGetCustomerSupportRemarksForMoveHandler: customer_support_remarks.GetCustomerSupportRemarksForMoveHandlerFunc(func(params customer_support_remarks.GetCustomerSupportRemarksForMoveParams) middleware.Responder { return middleware.NotImplemented("operation customer_support_remarks.GetCustomerSupportRemarksForMove has not yet been implemented") }), + QueuesGetDestinationRequestsQueueHandler: queues.GetDestinationRequestsQueueHandlerFunc(func(params queues.GetDestinationRequestsQueueParams) middleware.Responder { + return middleware.NotImplemented("operation queues.GetDestinationRequestsQueue has not yet been implemented") + }), GhcDocumentsGetDocumentHandler: ghc_documents.GetDocumentHandlerFunc(func(params ghc_documents.GetDocumentParams) middleware.Responder { return middleware.NotImplemented("operation ghc_documents.GetDocument has not yet been implemented") }), @@ -266,6 +273,9 @@ func NewMymoveAPI(spec *loads.Document) *MymoveAPI { UploadsGetUploadHandler: uploads.GetUploadHandlerFunc(func(params uploads.GetUploadParams) middleware.Responder { return middleware.NotImplemented("operation uploads.GetUpload has not yet been implemented") }), + UploadsGetUploadStatusHandler: uploads.GetUploadStatusHandlerFunc(func(params uploads.GetUploadStatusParams) middleware.Responder { + return middleware.NotImplemented("operation uploads.GetUploadStatus has not yet been implemented") + }), CalendarIsDateWeekendHolidayHandler: calendar.IsDateWeekendHolidayHandlerFunc(func(params calendar.IsDateWeekendHolidayParams) middleware.Responder { return middleware.NotImplemented("operation calendar.IsDateWeekendHoliday has not yet been implemented") }), @@ -446,6 +456,9 @@ type MymoveAPI struct { // JSONProducer registers a producer for the following mime types: // - application/json JSONProducer runtime.Producer + // TextEventStreamProducer registers a producer for the following mime types: + // - text/event-stream + TextEventStreamProducer runtime.Producer // OrderAcknowledgeExcessUnaccompaniedBaggageWeightRiskHandler sets the operation handler for the acknowledge excess unaccompanied baggage weight risk operation OrderAcknowledgeExcessUnaccompaniedBaggageWeightRiskHandler order.AcknowledgeExcessUnaccompaniedBaggageWeightRiskHandler @@ -517,6 +530,8 @@ type MymoveAPI struct { CustomerGetCustomerHandler customer.GetCustomerHandler // CustomerSupportRemarksGetCustomerSupportRemarksForMoveHandler sets the operation handler for the get customer support remarks for move operation CustomerSupportRemarksGetCustomerSupportRemarksForMoveHandler customer_support_remarks.GetCustomerSupportRemarksForMoveHandler + // QueuesGetDestinationRequestsQueueHandler sets the operation handler for the get destination requests queue operation + QueuesGetDestinationRequestsQueueHandler queues.GetDestinationRequestsQueueHandler // GhcDocumentsGetDocumentHandler sets the operation handler for the get document operation GhcDocumentsGetDocumentHandler ghc_documents.GetDocumentHandler // MoveTaskOrderGetEntitlementsHandler sets the operation handler for the get entitlements operation @@ -577,6 +592,8 @@ type MymoveAPI struct { TransportationOfficeGetTransportationOfficesOpenHandler transportation_office.GetTransportationOfficesOpenHandler // UploadsGetUploadHandler sets the operation handler for the get upload operation UploadsGetUploadHandler uploads.GetUploadHandler + // UploadsGetUploadStatusHandler sets the operation handler for the get upload status operation + UploadsGetUploadStatusHandler uploads.GetUploadStatusHandler // CalendarIsDateWeekendHolidayHandler sets the operation handler for the is date weekend holiday operation CalendarIsDateWeekendHolidayHandler calendar.IsDateWeekendHolidayHandler // MtoServiceItemListMTOServiceItemsHandler sets the operation handler for the list m t o service items operation @@ -749,6 +766,9 @@ func (o *MymoveAPI) Validate() error { if o.JSONProducer == nil { unregistered = append(unregistered, "JSONProducer") } + if o.TextEventStreamProducer == nil { + unregistered = append(unregistered, "TextEventStreamProducer") + } if o.OrderAcknowledgeExcessUnaccompaniedBaggageWeightRiskHandler == nil { unregistered = append(unregistered, "order.AcknowledgeExcessUnaccompaniedBaggageWeightRiskHandler") @@ -855,6 +875,9 @@ func (o *MymoveAPI) Validate() error { if o.CustomerSupportRemarksGetCustomerSupportRemarksForMoveHandler == nil { unregistered = append(unregistered, "customer_support_remarks.GetCustomerSupportRemarksForMoveHandler") } + if o.QueuesGetDestinationRequestsQueueHandler == nil { + unregistered = append(unregistered, "queues.GetDestinationRequestsQueueHandler") + } if o.GhcDocumentsGetDocumentHandler == nil { unregistered = append(unregistered, "ghc_documents.GetDocumentHandler") } @@ -945,6 +968,9 @@ func (o *MymoveAPI) Validate() error { if o.UploadsGetUploadHandler == nil { unregistered = append(unregistered, "uploads.GetUploadHandler") } + if o.UploadsGetUploadStatusHandler == nil { + unregistered = append(unregistered, "uploads.GetUploadStatusHandler") + } if o.CalendarIsDateWeekendHolidayHandler == nil { unregistered = append(unregistered, "calendar.IsDateWeekendHolidayHandler") } @@ -1132,6 +1158,8 @@ func (o *MymoveAPI) ProducersFor(mediaTypes []string) map[string]runtime.Produce result["application/pdf"] = o.BinProducer case "application/json": result["application/json"] = o.JSONProducer + case "text/event-stream": + result["text/event-stream"] = o.TextEventStreamProducer } if p, ok := o.customProducers[mt]; ok { @@ -1315,6 +1343,10 @@ func (o *MymoveAPI) initHandlerCache() { if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) } + o.handlers["GET"]["/queues/destination-requests"] = queues.NewGetDestinationRequestsQueue(o.context, o.QueuesGetDestinationRequestsQueueHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } o.handlers["GET"]["/documents/{documentId}"] = ghc_documents.NewGetDocument(o.context, o.GhcDocumentsGetDocumentHandler) if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) @@ -1435,6 +1467,10 @@ func (o *MymoveAPI) initHandlerCache() { if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) } + o.handlers["GET"]["/uploads/{uploadID}/status"] = uploads.NewGetUploadStatus(o.context, o.UploadsGetUploadStatusHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } o.handlers["GET"]["/calendar/{countryCode}/is-weekend-holiday/{date}"] = calendar.NewIsDateWeekendHoliday(o.context, o.CalendarIsDateWeekendHolidayHandler) if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_destination_requests_queue.go b/pkg/gen/ghcapi/ghcoperations/queues/get_destination_requests_queue.go new file mode 100644 index 00000000000..0bc440cf200 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_destination_requests_queue.go @@ -0,0 +1,58 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package queues + +// 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" +) + +// GetDestinationRequestsQueueHandlerFunc turns a function with the right signature into a get destination requests queue handler +type GetDestinationRequestsQueueHandlerFunc func(GetDestinationRequestsQueueParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn GetDestinationRequestsQueueHandlerFunc) Handle(params GetDestinationRequestsQueueParams) middleware.Responder { + return fn(params) +} + +// GetDestinationRequestsQueueHandler interface for that can handle valid get destination requests queue params +type GetDestinationRequestsQueueHandler interface { + Handle(GetDestinationRequestsQueueParams) middleware.Responder +} + +// NewGetDestinationRequestsQueue creates a new http.Handler for the get destination requests queue operation +func NewGetDestinationRequestsQueue(ctx *middleware.Context, handler GetDestinationRequestsQueueHandler) *GetDestinationRequestsQueue { + return &GetDestinationRequestsQueue{Context: ctx, Handler: handler} +} + +/* + GetDestinationRequestsQueue swagger:route GET /queues/destination-requests queues getDestinationRequestsQueue + +Gets queued list of all customer moves by GBLOC that have both CONUS & OCONUS destination requests (destination SIT, destination shuttle, address requests) + +A TOO will view this queue when they have destination requests tied to their GBLOC. This includes unapproved destination SIT service items, destination shuttle service items and destination address requests that are not yet approved by the TOO. +*/ +type GetDestinationRequestsQueue struct { + Context *middleware.Context + Handler GetDestinationRequestsQueueHandler +} + +func (o *GetDestinationRequestsQueue) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewGetDestinationRequestsQueueParams() + 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/queues/get_destination_requests_queue_parameters.go b/pkg/gen/ghcapi/ghcoperations/queues/get_destination_requests_queue_parameters.go new file mode 100644 index 00000000000..dac60111a5d --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_destination_requests_queue_parameters.go @@ -0,0 +1,589 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package queues + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "fmt" + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// NewGetDestinationRequestsQueueParams creates a new GetDestinationRequestsQueueParams object +// +// There are no default values defined in the spec. +func NewGetDestinationRequestsQueueParams() GetDestinationRequestsQueueParams { + + return GetDestinationRequestsQueueParams{} +} + +// GetDestinationRequestsQueueParams contains all the bound params for the get destination requests queue operation +// typically these are obtained from a http.Request +// +// swagger:parameters getDestinationRequestsQueue +type GetDestinationRequestsQueueParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + In: query + */ + AppearedInTooAt *strfmt.DateTime + /*Used to illustrate which user is assigned to this move. + + In: query + */ + AssignedTo *string + /* + In: query + */ + Branch *string + /*filters using a counselingOffice name of the move + In: query + */ + CounselingOffice *string + /* + In: query + */ + CustomerName *string + /* + In: query + */ + DestinationDutyLocation *string + /* + In: query + */ + Edipi *string + /* + In: query + */ + Emplid *string + /* + In: query + */ + Locator *string + /*direction of sort order if applied + In: query + */ + Order *string + /* + Unique: true + In: query + Collection Format: multi + */ + OriginDutyLocation []string + /*requested page of results + In: query + */ + Page *int64 + /*results per page + In: query + */ + PerPage *int64 + /*filters the requested pickup date of a shipment on the move + In: query + */ + RequestedMoveDate *string + /*field that results should be sorted by + In: query + */ + Sort *string + /*Filtering for the status. + Unique: true + In: query + */ + Status []string +} + +// 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 NewGetDestinationRequestsQueueParams() beforehand. +func (o *GetDestinationRequestsQueueParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + qs := runtime.Values(r.URL.Query()) + + qAppearedInTooAt, qhkAppearedInTooAt, _ := qs.GetOK("appearedInTooAt") + if err := o.bindAppearedInTooAt(qAppearedInTooAt, qhkAppearedInTooAt, 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) + } + + qBranch, qhkBranch, _ := qs.GetOK("branch") + if err := o.bindBranch(qBranch, qhkBranch, route.Formats); err != nil { + res = append(res, err) + } + + qCounselingOffice, qhkCounselingOffice, _ := qs.GetOK("counselingOffice") + if err := o.bindCounselingOffice(qCounselingOffice, qhkCounselingOffice, route.Formats); err != nil { + res = append(res, err) + } + + qCustomerName, qhkCustomerName, _ := qs.GetOK("customerName") + if err := o.bindCustomerName(qCustomerName, qhkCustomerName, route.Formats); err != nil { + res = append(res, err) + } + + qDestinationDutyLocation, qhkDestinationDutyLocation, _ := qs.GetOK("destinationDutyLocation") + if err := o.bindDestinationDutyLocation(qDestinationDutyLocation, qhkDestinationDutyLocation, route.Formats); err != nil { + res = append(res, err) + } + + qEdipi, qhkEdipi, _ := qs.GetOK("edipi") + if err := o.bindEdipi(qEdipi, qhkEdipi, route.Formats); err != nil { + res = append(res, err) + } + + qEmplid, qhkEmplid, _ := qs.GetOK("emplid") + if err := o.bindEmplid(qEmplid, qhkEmplid, route.Formats); err != nil { + res = append(res, err) + } + + qLocator, qhkLocator, _ := qs.GetOK("locator") + if err := o.bindLocator(qLocator, qhkLocator, route.Formats); err != nil { + res = append(res, err) + } + + qOrder, qhkOrder, _ := qs.GetOK("order") + if err := o.bindOrder(qOrder, qhkOrder, route.Formats); err != nil { + res = append(res, err) + } + + qOriginDutyLocation, qhkOriginDutyLocation, _ := qs.GetOK("originDutyLocation") + if err := o.bindOriginDutyLocation(qOriginDutyLocation, qhkOriginDutyLocation, route.Formats); err != nil { + res = append(res, err) + } + + qPage, qhkPage, _ := qs.GetOK("page") + if err := o.bindPage(qPage, qhkPage, route.Formats); err != nil { + res = append(res, err) + } + + qPerPage, qhkPerPage, _ := qs.GetOK("perPage") + if err := o.bindPerPage(qPerPage, qhkPerPage, route.Formats); err != nil { + res = append(res, err) + } + + qRequestedMoveDate, qhkRequestedMoveDate, _ := qs.GetOK("requestedMoveDate") + if err := o.bindRequestedMoveDate(qRequestedMoveDate, qhkRequestedMoveDate, route.Formats); err != nil { + res = append(res, err) + } + + qSort, qhkSort, _ := qs.GetOK("sort") + if err := o.bindSort(qSort, qhkSort, route.Formats); err != nil { + res = append(res, err) + } + + qStatus, qhkStatus, _ := qs.GetOK("status") + if err := o.bindStatus(qStatus, qhkStatus, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindAppearedInTooAt binds and validates parameter AppearedInTooAt from query. +func (o *GetDestinationRequestsQueueParams) bindAppearedInTooAt(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 + } + + // Format: date-time + value, err := formats.Parse("date-time", raw) + if err != nil { + return errors.InvalidType("appearedInTooAt", "query", "strfmt.DateTime", raw) + } + o.AppearedInTooAt = (value.(*strfmt.DateTime)) + + if err := o.validateAppearedInTooAt(formats); err != nil { + return err + } + + return nil +} + +// validateAppearedInTooAt carries on validations for parameter AppearedInTooAt +func (o *GetDestinationRequestsQueueParams) validateAppearedInTooAt(formats strfmt.Registry) error { + + if err := validate.FormatOf("appearedInTooAt", "query", "date-time", o.AppearedInTooAt.String(), formats); err != nil { + return err + } + return nil +} + +// bindAssignedTo binds and validates parameter AssignedTo from query. +func (o *GetDestinationRequestsQueueParams) bindAssignedTo(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.AssignedTo = &raw + + return nil +} + +// bindBranch binds and validates parameter Branch from query. +func (o *GetDestinationRequestsQueueParams) bindBranch(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.Branch = &raw + + return nil +} + +// bindCounselingOffice binds and validates parameter CounselingOffice from query. +func (o *GetDestinationRequestsQueueParams) bindCounselingOffice(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.CounselingOffice = &raw + + return nil +} + +// bindCustomerName binds and validates parameter CustomerName from query. +func (o *GetDestinationRequestsQueueParams) bindCustomerName(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.CustomerName = &raw + + return nil +} + +// bindDestinationDutyLocation binds and validates parameter DestinationDutyLocation from query. +func (o *GetDestinationRequestsQueueParams) bindDestinationDutyLocation(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.DestinationDutyLocation = &raw + + return nil +} + +// bindEdipi binds and validates parameter Edipi from query. +func (o *GetDestinationRequestsQueueParams) bindEdipi(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.Edipi = &raw + + return nil +} + +// bindEmplid binds and validates parameter Emplid from query. +func (o *GetDestinationRequestsQueueParams) bindEmplid(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.Emplid = &raw + + return nil +} + +// bindLocator binds and validates parameter Locator from query. +func (o *GetDestinationRequestsQueueParams) bindLocator(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.Locator = &raw + + return nil +} + +// bindOrder binds and validates parameter Order from query. +func (o *GetDestinationRequestsQueueParams) bindOrder(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.Order = &raw + + if err := o.validateOrder(formats); err != nil { + return err + } + + return nil +} + +// validateOrder carries on validations for parameter Order +func (o *GetDestinationRequestsQueueParams) validateOrder(formats strfmt.Registry) error { + + if err := validate.EnumCase("order", "query", *o.Order, []interface{}{"asc", "desc"}, true); err != nil { + return err + } + + return nil +} + +// bindOriginDutyLocation binds and validates array parameter OriginDutyLocation from query. +// +// Arrays are parsed according to CollectionFormat: "multi" (defaults to "csv" when empty). +func (o *GetDestinationRequestsQueueParams) bindOriginDutyLocation(rawData []string, hasKey bool, formats strfmt.Registry) error { + // CollectionFormat: multi + originDutyLocationIC := rawData + if len(originDutyLocationIC) == 0 { + return nil + } + + var originDutyLocationIR []string + for _, originDutyLocationIV := range originDutyLocationIC { + originDutyLocationI := originDutyLocationIV + + originDutyLocationIR = append(originDutyLocationIR, originDutyLocationI) + } + + o.OriginDutyLocation = originDutyLocationIR + if err := o.validateOriginDutyLocation(formats); err != nil { + return err + } + + return nil +} + +// validateOriginDutyLocation carries on validations for parameter OriginDutyLocation +func (o *GetDestinationRequestsQueueParams) validateOriginDutyLocation(formats strfmt.Registry) error { + + // uniqueItems: true + if err := validate.UniqueItems("originDutyLocation", "query", o.OriginDutyLocation); err != nil { + return err + } + return nil +} + +// bindPage binds and validates parameter Page from query. +func (o *GetDestinationRequestsQueueParams) bindPage(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 + } + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("page", "query", "int64", raw) + } + o.Page = &value + + return nil +} + +// bindPerPage binds and validates parameter PerPage from query. +func (o *GetDestinationRequestsQueueParams) bindPerPage(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 + } + + value, err := swag.ConvertInt64(raw) + if err != nil { + return errors.InvalidType("perPage", "query", "int64", raw) + } + o.PerPage = &value + + return nil +} + +// bindRequestedMoveDate binds and validates parameter RequestedMoveDate from query. +func (o *GetDestinationRequestsQueueParams) bindRequestedMoveDate(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.RequestedMoveDate = &raw + + return nil +} + +// bindSort binds and validates parameter Sort from query. +func (o *GetDestinationRequestsQueueParams) bindSort(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.Sort = &raw + + if err := o.validateSort(formats); err != nil { + return err + } + + return nil +} + +// validateSort carries on validations for parameter Sort +func (o *GetDestinationRequestsQueueParams) validateSort(formats strfmt.Registry) error { + + if err := validate.EnumCase("sort", "query", *o.Sort, []interface{}{"customerName", "edipi", "emplid", "branch", "locator", "status", "originDutyLocation", "destinationDutyLocation", "requestedMoveDate", "appearedInTooAt", "assignedTo", "counselingOffice"}, true); err != nil { + return err + } + + return nil +} + +// bindStatus binds and validates array parameter Status from query. +// +// Arrays are parsed according to CollectionFormat: "" (defaults to "csv" when empty). +func (o *GetDestinationRequestsQueueParams) bindStatus(rawData []string, hasKey bool, formats strfmt.Registry) error { + var qvStatus string + if len(rawData) > 0 { + qvStatus = rawData[len(rawData)-1] + } + + // CollectionFormat: + statusIC := swag.SplitByFormat(qvStatus, "") + if len(statusIC) == 0 { + return nil + } + + var statusIR []string + for i, statusIV := range statusIC { + statusI := statusIV + + if err := validate.EnumCase(fmt.Sprintf("%s.%v", "status", i), "query", statusI, []interface{}{"SUBMITTED", "SERVICE COUNSELING COMPLETED", "APPROVALS REQUESTED"}, true); err != nil { + return err + } + + statusIR = append(statusIR, statusI) + } + + o.Status = statusIR + if err := o.validateStatus(formats); err != nil { + return err + } + + return nil +} + +// validateStatus carries on validations for parameter Status +func (o *GetDestinationRequestsQueueParams) validateStatus(formats strfmt.Registry) error { + + // uniqueItems: true + if err := validate.UniqueItems("status", "query", o.Status); err != nil { + return err + } + return nil +} diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_destination_requests_queue_responses.go b/pkg/gen/ghcapi/ghcoperations/queues/get_destination_requests_queue_responses.go new file mode 100644 index 00000000000..ea27ea91263 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_destination_requests_queue_responses.go @@ -0,0 +1,149 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package queues + +// 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" +) + +// GetDestinationRequestsQueueOKCode is the HTTP code returned for type GetDestinationRequestsQueueOK +const GetDestinationRequestsQueueOKCode int = 200 + +/* +GetDestinationRequestsQueueOK Successfully returned all moves matching the criteria + +swagger:response getDestinationRequestsQueueOK +*/ +type GetDestinationRequestsQueueOK struct { + + /* + In: Body + */ + Payload *ghcmessages.QueueMovesResult `json:"body,omitempty"` +} + +// NewGetDestinationRequestsQueueOK creates GetDestinationRequestsQueueOK with default headers values +func NewGetDestinationRequestsQueueOK() *GetDestinationRequestsQueueOK { + + return &GetDestinationRequestsQueueOK{} +} + +// WithPayload adds the payload to the get destination requests queue o k response +func (o *GetDestinationRequestsQueueOK) WithPayload(payload *ghcmessages.QueueMovesResult) *GetDestinationRequestsQueueOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get destination requests queue o k response +func (o *GetDestinationRequestsQueueOK) SetPayload(payload *ghcmessages.QueueMovesResult) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetDestinationRequestsQueueOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// GetDestinationRequestsQueueForbiddenCode is the HTTP code returned for type GetDestinationRequestsQueueForbidden +const GetDestinationRequestsQueueForbiddenCode int = 403 + +/* +GetDestinationRequestsQueueForbidden The request was denied + +swagger:response getDestinationRequestsQueueForbidden +*/ +type GetDestinationRequestsQueueForbidden struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewGetDestinationRequestsQueueForbidden creates GetDestinationRequestsQueueForbidden with default headers values +func NewGetDestinationRequestsQueueForbidden() *GetDestinationRequestsQueueForbidden { + + return &GetDestinationRequestsQueueForbidden{} +} + +// WithPayload adds the payload to the get destination requests queue forbidden response +func (o *GetDestinationRequestsQueueForbidden) WithPayload(payload *ghcmessages.Error) *GetDestinationRequestsQueueForbidden { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get destination requests queue forbidden response +func (o *GetDestinationRequestsQueueForbidden) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetDestinationRequestsQueueForbidden) 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 + } + } +} + +// GetDestinationRequestsQueueInternalServerErrorCode is the HTTP code returned for type GetDestinationRequestsQueueInternalServerError +const GetDestinationRequestsQueueInternalServerErrorCode int = 500 + +/* +GetDestinationRequestsQueueInternalServerError A server error occurred + +swagger:response getDestinationRequestsQueueInternalServerError +*/ +type GetDestinationRequestsQueueInternalServerError struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewGetDestinationRequestsQueueInternalServerError creates GetDestinationRequestsQueueInternalServerError with default headers values +func NewGetDestinationRequestsQueueInternalServerError() *GetDestinationRequestsQueueInternalServerError { + + return &GetDestinationRequestsQueueInternalServerError{} +} + +// WithPayload adds the payload to the get destination requests queue internal server error response +func (o *GetDestinationRequestsQueueInternalServerError) WithPayload(payload *ghcmessages.Error) *GetDestinationRequestsQueueInternalServerError { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get destination requests queue internal server error response +func (o *GetDestinationRequestsQueueInternalServerError) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetDestinationRequestsQueueInternalServerError) 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/queues/get_destination_requests_queue_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/queues/get_destination_requests_queue_urlbuilder.go new file mode 100644 index 00000000000..c8b86e869dc --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_destination_requests_queue_urlbuilder.go @@ -0,0 +1,256 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package queues + +// 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" + + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// GetDestinationRequestsQueueURL generates an URL for the get destination requests queue operation +type GetDestinationRequestsQueueURL struct { + AppearedInTooAt *strfmt.DateTime + AssignedTo *string + Branch *string + CounselingOffice *string + CustomerName *string + DestinationDutyLocation *string + Edipi *string + Emplid *string + Locator *string + Order *string + OriginDutyLocation []string + Page *int64 + PerPage *int64 + RequestedMoveDate *string + Sort *string + Status []string + + _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 *GetDestinationRequestsQueueURL) WithBasePath(bp string) *GetDestinationRequestsQueueURL { + 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 *GetDestinationRequestsQueueURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *GetDestinationRequestsQueueURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/queues/destination-requests" + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/ghc/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + qs := make(url.Values) + + var appearedInTooAtQ string + if o.AppearedInTooAt != nil { + appearedInTooAtQ = o.AppearedInTooAt.String() + } + if appearedInTooAtQ != "" { + qs.Set("appearedInTooAt", appearedInTooAtQ) + } + + var assignedToQ string + if o.AssignedTo != nil { + assignedToQ = *o.AssignedTo + } + if assignedToQ != "" { + qs.Set("assignedTo", assignedToQ) + } + + var branchQ string + if o.Branch != nil { + branchQ = *o.Branch + } + if branchQ != "" { + qs.Set("branch", branchQ) + } + + var counselingOfficeQ string + if o.CounselingOffice != nil { + counselingOfficeQ = *o.CounselingOffice + } + if counselingOfficeQ != "" { + qs.Set("counselingOffice", counselingOfficeQ) + } + + var customerNameQ string + if o.CustomerName != nil { + customerNameQ = *o.CustomerName + } + if customerNameQ != "" { + qs.Set("customerName", customerNameQ) + } + + var destinationDutyLocationQ string + if o.DestinationDutyLocation != nil { + destinationDutyLocationQ = *o.DestinationDutyLocation + } + if destinationDutyLocationQ != "" { + qs.Set("destinationDutyLocation", destinationDutyLocationQ) + } + + var edipiQ string + if o.Edipi != nil { + edipiQ = *o.Edipi + } + if edipiQ != "" { + qs.Set("edipi", edipiQ) + } + + var emplidQ string + if o.Emplid != nil { + emplidQ = *o.Emplid + } + if emplidQ != "" { + qs.Set("emplid", emplidQ) + } + + var locatorQ string + if o.Locator != nil { + locatorQ = *o.Locator + } + if locatorQ != "" { + qs.Set("locator", locatorQ) + } + + var orderQ string + if o.Order != nil { + orderQ = *o.Order + } + if orderQ != "" { + qs.Set("order", orderQ) + } + + var originDutyLocationIR []string + for _, originDutyLocationI := range o.OriginDutyLocation { + originDutyLocationIS := originDutyLocationI + if originDutyLocationIS != "" { + originDutyLocationIR = append(originDutyLocationIR, originDutyLocationIS) + } + } + + originDutyLocation := swag.JoinByFormat(originDutyLocationIR, "multi") + + for _, qsv := range originDutyLocation { + qs.Add("originDutyLocation", qsv) + } + + var pageQ string + if o.Page != nil { + pageQ = swag.FormatInt64(*o.Page) + } + if pageQ != "" { + qs.Set("page", pageQ) + } + + var perPageQ string + if o.PerPage != nil { + perPageQ = swag.FormatInt64(*o.PerPage) + } + if perPageQ != "" { + qs.Set("perPage", perPageQ) + } + + var requestedMoveDateQ string + if o.RequestedMoveDate != nil { + requestedMoveDateQ = *o.RequestedMoveDate + } + if requestedMoveDateQ != "" { + qs.Set("requestedMoveDate", requestedMoveDateQ) + } + + var sortQ string + if o.Sort != nil { + sortQ = *o.Sort + } + if sortQ != "" { + qs.Set("sort", sortQ) + } + + var statusIR []string + for _, statusI := range o.Status { + statusIS := statusI + if statusIS != "" { + statusIR = append(statusIR, statusIS) + } + } + + status := swag.JoinByFormat(statusIR, "") + + if len(status) > 0 { + qsv := status[0] + if qsv != "" { + qs.Set("status", qsv) + } + } + + _result.RawQuery = qs.Encode() + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *GetDestinationRequestsQueueURL) 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 *GetDestinationRequestsQueueURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *GetDestinationRequestsQueueURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on GetDestinationRequestsQueueURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on GetDestinationRequestsQueueURL") + } + + 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 *GetDestinationRequestsQueueURL) 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 c630be03fd6..309de84d0fa 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 GBLOCs +GetTransportationOfficesGBLOCsOK Successfully retrieved transportation offices swagger:response getTransportationOfficesGBLOCsOK */ diff --git a/pkg/gen/ghcapi/ghcoperations/uploads/get_upload_status.go b/pkg/gen/ghcapi/ghcoperations/uploads/get_upload_status.go new file mode 100644 index 00000000000..b893657d488 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/uploads/get_upload_status.go @@ -0,0 +1,58 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package uploads + +// 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" +) + +// GetUploadStatusHandlerFunc turns a function with the right signature into a get upload status handler +type GetUploadStatusHandlerFunc func(GetUploadStatusParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn GetUploadStatusHandlerFunc) Handle(params GetUploadStatusParams) middleware.Responder { + return fn(params) +} + +// GetUploadStatusHandler interface for that can handle valid get upload status params +type GetUploadStatusHandler interface { + Handle(GetUploadStatusParams) middleware.Responder +} + +// NewGetUploadStatus creates a new http.Handler for the get upload status operation +func NewGetUploadStatus(ctx *middleware.Context, handler GetUploadStatusHandler) *GetUploadStatus { + return &GetUploadStatus{Context: ctx, Handler: handler} +} + +/* + GetUploadStatus swagger:route GET /uploads/{uploadID}/status uploads getUploadStatus + +# Returns status of an upload + +Returns status of an upload based on antivirus run +*/ +type GetUploadStatus struct { + Context *middleware.Context + Handler GetUploadStatusHandler +} + +func (o *GetUploadStatus) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewGetUploadStatusParams() + 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/uploads/get_upload_status_parameters.go b/pkg/gen/ghcapi/ghcoperations/uploads/get_upload_status_parameters.go new file mode 100644 index 00000000000..fa1b3ef9329 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/uploads/get_upload_status_parameters.go @@ -0,0 +1,91 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package uploads + +// 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" +) + +// NewGetUploadStatusParams creates a new GetUploadStatusParams object +// +// There are no default values defined in the spec. +func NewGetUploadStatusParams() GetUploadStatusParams { + + return GetUploadStatusParams{} +} + +// GetUploadStatusParams contains all the bound params for the get upload status operation +// typically these are obtained from a http.Request +// +// swagger:parameters getUploadStatus +type GetUploadStatusParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /*UUID of the upload to return status of + Required: true + In: path + */ + UploadID 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 NewGetUploadStatusParams() beforehand. +func (o *GetUploadStatusParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + rUploadID, rhkUploadID, _ := route.Params.GetOK("uploadID") + if err := o.bindUploadID(rUploadID, rhkUploadID, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindUploadID binds and validates parameter UploadID from path. +func (o *GetUploadStatusParams) bindUploadID(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("uploadID", "path", "strfmt.UUID", raw) + } + o.UploadID = *(value.(*strfmt.UUID)) + + if err := o.validateUploadID(formats); err != nil { + return err + } + + return nil +} + +// validateUploadID carries on validations for parameter UploadID +func (o *GetUploadStatusParams) validateUploadID(formats strfmt.Registry) error { + + if err := validate.FormatOf("uploadID", "path", "uuid", o.UploadID.String(), formats); err != nil { + return err + } + return nil +} diff --git a/pkg/gen/ghcapi/ghcoperations/uploads/get_upload_status_responses.go b/pkg/gen/ghcapi/ghcoperations/uploads/get_upload_status_responses.go new file mode 100644 index 00000000000..894980d6a2b --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/uploads/get_upload_status_responses.go @@ -0,0 +1,177 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package uploads + +// 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" +) + +// GetUploadStatusOKCode is the HTTP code returned for type GetUploadStatusOK +const GetUploadStatusOKCode int = 200 + +/* +GetUploadStatusOK the requested upload status + +swagger:response getUploadStatusOK +*/ +type GetUploadStatusOK struct { + + /* + In: Body + */ + Payload string `json:"body,omitempty"` +} + +// NewGetUploadStatusOK creates GetUploadStatusOK with default headers values +func NewGetUploadStatusOK() *GetUploadStatusOK { + + return &GetUploadStatusOK{} +} + +// WithPayload adds the payload to the get upload status o k response +func (o *GetUploadStatusOK) WithPayload(payload string) *GetUploadStatusOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get upload status o k response +func (o *GetUploadStatusOK) SetPayload(payload string) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetUploadStatusOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } +} + +// GetUploadStatusBadRequestCode is the HTTP code returned for type GetUploadStatusBadRequest +const GetUploadStatusBadRequestCode int = 400 + +/* +GetUploadStatusBadRequest invalid request + +swagger:response getUploadStatusBadRequest +*/ +type GetUploadStatusBadRequest struct { + + /* + In: Body + */ + Payload *ghcmessages.InvalidRequestResponsePayload `json:"body,omitempty"` +} + +// NewGetUploadStatusBadRequest creates GetUploadStatusBadRequest with default headers values +func NewGetUploadStatusBadRequest() *GetUploadStatusBadRequest { + + return &GetUploadStatusBadRequest{} +} + +// WithPayload adds the payload to the get upload status bad request response +func (o *GetUploadStatusBadRequest) WithPayload(payload *ghcmessages.InvalidRequestResponsePayload) *GetUploadStatusBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get upload status bad request response +func (o *GetUploadStatusBadRequest) SetPayload(payload *ghcmessages.InvalidRequestResponsePayload) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetUploadStatusBadRequest) 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 + } + } +} + +// GetUploadStatusForbiddenCode is the HTTP code returned for type GetUploadStatusForbidden +const GetUploadStatusForbiddenCode int = 403 + +/* +GetUploadStatusForbidden not authorized + +swagger:response getUploadStatusForbidden +*/ +type GetUploadStatusForbidden struct { +} + +// NewGetUploadStatusForbidden creates GetUploadStatusForbidden with default headers values +func NewGetUploadStatusForbidden() *GetUploadStatusForbidden { + + return &GetUploadStatusForbidden{} +} + +// WriteResponse to the client +func (o *GetUploadStatusForbidden) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(403) +} + +// GetUploadStatusNotFoundCode is the HTTP code returned for type GetUploadStatusNotFound +const GetUploadStatusNotFoundCode int = 404 + +/* +GetUploadStatusNotFound not found + +swagger:response getUploadStatusNotFound +*/ +type GetUploadStatusNotFound struct { +} + +// NewGetUploadStatusNotFound creates GetUploadStatusNotFound with default headers values +func NewGetUploadStatusNotFound() *GetUploadStatusNotFound { + + return &GetUploadStatusNotFound{} +} + +// WriteResponse to the client +func (o *GetUploadStatusNotFound) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(404) +} + +// GetUploadStatusInternalServerErrorCode is the HTTP code returned for type GetUploadStatusInternalServerError +const GetUploadStatusInternalServerErrorCode int = 500 + +/* +GetUploadStatusInternalServerError server error + +swagger:response getUploadStatusInternalServerError +*/ +type GetUploadStatusInternalServerError struct { +} + +// NewGetUploadStatusInternalServerError creates GetUploadStatusInternalServerError with default headers values +func NewGetUploadStatusInternalServerError() *GetUploadStatusInternalServerError { + + return &GetUploadStatusInternalServerError{} +} + +// WriteResponse to the client +func (o *GetUploadStatusInternalServerError) 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/uploads/get_upload_status_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/uploads/get_upload_status_urlbuilder.go new file mode 100644 index 00000000000..edd3c2fd6f8 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/uploads/get_upload_status_urlbuilder.go @@ -0,0 +1,101 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package uploads + +// 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" +) + +// GetUploadStatusURL generates an URL for the get upload status operation +type GetUploadStatusURL struct { + UploadID 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 *GetUploadStatusURL) WithBasePath(bp string) *GetUploadStatusURL { + 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 *GetUploadStatusURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *GetUploadStatusURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/uploads/{uploadID}/status" + + uploadID := o.UploadID.String() + if uploadID != "" { + _path = strings.Replace(_path, "{uploadID}", uploadID, -1) + } else { + return nil, errors.New("uploadId is required on GetUploadStatusURL") + } + + _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 *GetUploadStatusURL) 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 *GetUploadStatusURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *GetUploadStatusURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on GetUploadStatusURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on GetUploadStatusURL") + } + + 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 *GetUploadStatusURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/pkg/gen/ghcmessages/counseling_update_allowance_payload.go b/pkg/gen/ghcmessages/counseling_update_allowance_payload.go index 805a206b000..5f8c46ecd7b 100644 --- a/pkg/gen/ghcmessages/counseling_update_allowance_payload.go +++ b/pkg/gen/ghcmessages/counseling_update_allowance_payload.go @@ -26,9 +26,6 @@ type CounselingUpdateAllowancePayload struct { // agency Agency *Affiliation `json:"agency,omitempty"` - // dependents authorized - DependentsAuthorized *bool `json:"dependentsAuthorized,omitempty"` - // Indicates the number of dependents of the age twelve or older for a move. This is only present on OCONUS moves. // Example: 3 DependentsTwelveAndOver *int64 `json:"dependentsTwelveAndOver,omitempty"` diff --git a/pkg/gen/ghcmessages/counseling_update_order_payload.go b/pkg/gen/ghcmessages/counseling_update_order_payload.go index 281972b5196..03f1b9618d5 100644 --- a/pkg/gen/ghcmessages/counseling_update_order_payload.go +++ b/pkg/gen/ghcmessages/counseling_update_order_payload.go @@ -23,6 +23,9 @@ type CounselingUpdateOrderPayload struct { // department indicator DepartmentIndicator *DeptIndicator `json:"departmentIndicator,omitempty"` + // dependents authorized + DependentsAuthorized *bool `json:"dependentsAuthorized,omitempty"` + // grade Grade *Grade `json:"grade,omitempty"` 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/update_allowance_payload.go b/pkg/gen/ghcmessages/update_allowance_payload.go index c0aa957934a..2c37d3a7944 100644 --- a/pkg/gen/ghcmessages/update_allowance_payload.go +++ b/pkg/gen/ghcmessages/update_allowance_payload.go @@ -26,9 +26,6 @@ type UpdateAllowancePayload struct { // agency Agency *Affiliation `json:"agency,omitempty"` - // dependents authorized - DependentsAuthorized *bool `json:"dependentsAuthorized,omitempty"` - // Indicates the number of dependents of the age twelve or older for a move. This is only present on OCONUS moves. // Example: 3 DependentsTwelveAndOver *int64 `json:"dependentsTwelveAndOver,omitempty"` diff --git a/pkg/gen/ghcmessages/update_order_payload.go b/pkg/gen/ghcmessages/update_order_payload.go index f5a09ceb70d..fa3796bfc78 100644 --- a/pkg/gen/ghcmessages/update_order_payload.go +++ b/pkg/gen/ghcmessages/update_order_payload.go @@ -23,6 +23,9 @@ type UpdateOrderPayload struct { // department indicator DepartmentIndicator *DeptIndicator `json:"departmentIndicator,omitempty"` + // dependents authorized + DependentsAuthorized *bool `json:"dependentsAuthorized,omitempty"` + // grade Grade *Grade `json:"grade,omitempty"` diff --git a/pkg/gen/primeapi/configure_mymove.go b/pkg/gen/primeapi/configure_mymove.go index c538a478d02..6def1f8afbc 100644 --- a/pkg/gen/primeapi/configure_mymove.go +++ b/pkg/gen/primeapi/configure_mymove.go @@ -11,6 +11,7 @@ import ( "github.com/go-openapi/runtime/middleware" "github.com/transcom/mymove/pkg/gen/primeapi/primeoperations" + "github.com/transcom/mymove/pkg/gen/primeapi/primeoperations/addresses" "github.com/transcom/mymove/pkg/gen/primeapi/primeoperations/move_task_order" "github.com/transcom/mymove/pkg/gen/primeapi/primeoperations/mto_service_item" "github.com/transcom/mymove/pkg/gen/primeapi/primeoperations/mto_shipment" @@ -100,6 +101,11 @@ func configureAPI(api *primeoperations.MymoveAPI) http.Handler { return middleware.NotImplemented("operation move_task_order.DownloadMoveOrder has not yet been implemented") }) } + if api.AddressesGetLocationByZipCityStateHandler == nil { + api.AddressesGetLocationByZipCityStateHandler = addresses.GetLocationByZipCityStateHandlerFunc(func(params addresses.GetLocationByZipCityStateParams) middleware.Responder { + return middleware.NotImplemented("operation addresses.GetLocationByZipCityState has not yet been implemented") + }) + } if api.MoveTaskOrderGetMoveTaskOrderHandler == nil { api.MoveTaskOrderGetMoveTaskOrderHandler = move_task_order.GetMoveTaskOrderHandlerFunc(func(params move_task_order.GetMoveTaskOrderParams) middleware.Responder { return middleware.NotImplemented("operation move_task_order.GetMoveTaskOrder has not yet been implemented") diff --git a/pkg/gen/primeapi/embedded_spec.go b/pkg/gen/primeapi/embedded_spec.go index c4dfc4fb5b2..ac5b2533fc5 100644 --- a/pkg/gen/primeapi/embedded_spec.go +++ b/pkg/gen/primeapi/embedded_spec.go @@ -36,6 +36,44 @@ func init() { }, "basePath": "/prime/v1", "paths": { + "/addresses/zip-city-lookup/{search}": { + "get": { + "description": "Find by API using full/partial postal code or city name that returns an us_post_region_cities json object containing city, state, county and postal code.", + "tags": [ + "addresses" + ], + "summary": "Returns city, state, postal code, and county associated with the specified full/partial postal code or city state string", + "operationId": "getLocationByZipCityState", + "parameters": [ + { + "type": "string", + "name": "search", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "the requested list of city, state, county, and postal code matches", + "schema": { + "$ref": "#/definitions/VLocations" + } + }, + "400": { + "$ref": "#/responses/InvalidRequest" + }, + "403": { + "$ref": "#/responses/PermissionDenied" + }, + "404": { + "$ref": "#/responses/NotFound" + }, + "500": { + "$ref": "#/responses/ServerError" + } + } + } + }, "/move-task-orders/{moveID}": { "get": { "description": "### Functionality\nThis endpoint gets an individual MoveTaskOrder by ID.\n\nIt will provide information about the Customer and any associated MTOShipments, MTOServiceItems and PaymentRequests.\n", @@ -298,7 +336,7 @@ func init() { }, "/mto-service-items": { "post": { - "description": "Creates one or more MTOServiceItems. Not all service items may be created, please see details below.\n\nThis endpoint supports different body definitions. In the modelType field below, select the modelType corresponding\n to the service item you wish to create and the documentation will update with the new definition.\n\nUpon creation these items are associated with a Move Task Order and an MTO Shipment.\nThe request must include UUIDs for the MTO and MTO Shipment connected to this service item. Some service item types require\nadditional service items to be autogenerated when added - all created service items, autogenerated included,\nwill be returned in the response.\n\nTo update a service item, please use [updateMTOServiceItem](#operation/updateMTOServiceItem) endpoint.\n\n---\n\n**` + "`" + `MTOServiceItemOriginSIT` + "`" + `**\n\nMTOServiceItemOriginSIT is a subtype of MTOServiceItem.\n\nThis model type describes a domestic origin SIT service item. Items can be created using this\nmodel type with the following codes:\n\n**DOFSIT**\n\n**1st day origin SIT service item**. When a DOFSIT is requested, the API will auto-create the following group of service items:\n * DOFSIT - Domestic origin 1st day SIT\n * DOASIT - Domestic origin Additional day SIT\n * DOPSIT - Domestic origin SIT pickup\n * DOSFSC - Domestic origin SIT fuel surcharge\n\n**DOASIT**\n\n**Addt'l days origin SIT service item**. This represents an additional day of storage for the same item.\nAdditional DOASIT service items can be created and added to an existing shipment that **includes a DOFSIT service item**.\n\n---\n\n**` + "`" + `MTOServiceItemDestSIT` + "`" + `**\n\nMTOServiceItemDestSIT is a subtype of MTOServiceItem.\n\nThis model type describes a domestic destination SIT service item. Items can be created using this\nmodel type with the following codes:\n\n**DDFSIT**\n\n**1st day destination SIT service item**.\n\nThese additional fields are optional for creating a DDFSIT:\n * ` + "`" + `firstAvailableDeliveryDate1` + "`" + `\n * string \u003cdate\u003e\n * First available date that Prime can deliver SIT service item.\n * firstAvailableDeliveryDate1, dateOfContact1, and timeMilitary1 are required together\n * ` + "`" + `dateOfContact1` + "`" + `\n * string \u003cdate\u003e\n * Date of attempted contact by the prime corresponding to ` + "`" + `timeMilitary1` + "`" + `\n * dateOfContact1, timeMilitary1, and firstAvailableDeliveryDate1 are required together\n * ` + "`" + `timeMilitary1` + "`" + `\n * string\\d{4}Z\n * Time of attempted contact corresponding to ` + "`" + `dateOfContact1` + "`" + `, in military format.\n * timeMilitary1, dateOfContact1, and firstAvailableDeliveryDate1 are required together\n * ` + "`" + `firstAvailableDeliveryDate2` + "`" + `\n * string \u003cdate\u003e\n * Second available date that Prime can deliver SIT service item.\n * firstAvailableDeliveryDate2, dateOfContact2, and timeMilitary2 are required together\n * ` + "`" + `dateOfContact2` + "`" + `\n * string \u003cdate\u003e\n * Date of attempted contact delivery by the prime corresponding to ` + "`" + `timeMilitary2` + "`" + `\n * dateOfContact2, timeMilitary2, and firstAvailableDeliveryDate2 are required together\n * ` + "`" + `timeMilitary2` + "`" + `\n * string\\d{4}Z\n * Time of attempted contact corresponding to ` + "`" + `dateOfContact2` + "`" + `, in military format.\n * timeMilitary2, dateOfContact2, and firstAvailableDeliveryDate2 are required together\n\nWhen a DDFSIT is requested, the API will auto-create the following group of service items:\n * DDFSIT - Domestic destination 1st day SIT\n * DDASIT - Domestic destination Additional day SIT\n * DDDSIT - Domestic destination SIT delivery\n * DDSFSC - Domestic destination SIT fuel surcharge\n\n**NOTE** When providing the ` + "`" + `sitEntryDate` + "`" + ` value in the payload, please ensure that the date is not BEFORE\n` + "`" + `firstAvailableDeliveryDate1` + "`" + ` or ` + "`" + `firstAvailableDeliveryDate2` + "`" + `. If it is, you will receive an error response.\n\n**DDASIT**\n\n**Addt'l days destination SIT service item**. This represents an additional day of storage for the same item.\nAdditional DDASIT service items can be created and added to an existing shipment that **includes a DDFSIT service item**.\n", + "description": "Creates one or more MTOServiceItems. Not all service items may be created, please see details below.\n\nThis endpoint supports different body definitions. In the modelType field below, select the modelType corresponding\n to the service item you wish to create and the documentation will update with the new definition.\n\nUpon creation these items are associated with a Move Task Order and an MTO Shipment.\nThe request must include UUIDs for the MTO and MTO Shipment connected to this service item. Some service item types require\nadditional service items to be autogenerated when added - all created service items, autogenerated included,\nwill be returned in the response.\n\nTo update a service item, please use [updateMTOServiceItem](#operation/updateMTOServiceItem) endpoint.\n\n---\n\n**` + "`" + `MTOServiceItemOriginSIT` + "`" + `**\n\nMTOServiceItemOriginSIT is a subtype of MTOServiceItem.\n\nThis model type describes a domestic origin SIT service item. Items can be created using this\nmodel type with the following codes:\n\n**DOFSIT**\n\n**1st day origin SIT service item**. When a DOFSIT is requested, the API will auto-create the following group of service items:\n * DOFSIT - Domestic origin 1st day SIT\n * DOASIT - Domestic origin Additional day SIT\n * DOPSIT - Domestic origin SIT pickup\n * DOSFSC - Domestic origin SIT fuel surcharge\n\n**DOASIT**\n\n**Addt'l days origin SIT service item**. This represents an additional day of storage for the same item.\nAdditional DOASIT service items can be created and added to an existing shipment that **includes a DOFSIT service item**.\n\n---\n\n**` + "`" + `MTOServiceItemDestSIT` + "`" + `**\n\nMTOServiceItemDestSIT is a subtype of MTOServiceItem.\n\nThis model type describes a domestic destination SIT service item. Items can be created using this\nmodel type with the following codes:\n\n**DDFSIT**\n\n**1st day destination SIT service item**.\n\nThese additional fields are optional for creating a DDFSIT:\n * ` + "`" + `firstAvailableDeliveryDate1` + "`" + `\n * string \u003cdate\u003e\n * First available date that Prime can deliver SIT service item.\n * firstAvailableDeliveryDate1, dateOfContact1, and timeMilitary1 are required together\n * ` + "`" + `dateOfContact1` + "`" + `\n * string \u003cdate\u003e\n * Date of attempted contact by the prime corresponding to ` + "`" + `timeMilitary1` + "`" + `\n * dateOfContact1, timeMilitary1, and firstAvailableDeliveryDate1 are required together\n * ` + "`" + `timeMilitary1` + "`" + `\n * string\\d{4}Z\n * Time of attempted contact corresponding to ` + "`" + `dateOfContact1` + "`" + `, in military format.\n * timeMilitary1, dateOfContact1, and firstAvailableDeliveryDate1 are required together\n * ` + "`" + `firstAvailableDeliveryDate2` + "`" + `\n * string \u003cdate\u003e\n * Second available date that Prime can deliver SIT service item.\n * firstAvailableDeliveryDate2, dateOfContact2, and timeMilitary2 are required together\n * ` + "`" + `dateOfContact2` + "`" + `\n * string \u003cdate\u003e\n * Date of attempted contact delivery by the prime corresponding to ` + "`" + `timeMilitary2` + "`" + `\n * dateOfContact2, timeMilitary2, and firstAvailableDeliveryDate2 are required together\n * ` + "`" + `timeMilitary2` + "`" + `\n * string\\d{4}Z\n * Time of attempted contact corresponding to ` + "`" + `dateOfContact2` + "`" + `, in military format.\n * timeMilitary2, dateOfContact2, and firstAvailableDeliveryDate2 are required together\n\nWhen a DDFSIT is requested, the API will auto-create the following group of service items:\n * DDFSIT - Domestic destination 1st day SIT\n * DDASIT - Domestic destination Additional day SIT\n * DDDSIT - Domestic destination SIT delivery\n * DDSFSC - Domestic destination SIT fuel surcharge\n\n**NOTE** When providing the ` + "`" + `sitEntryDate` + "`" + ` value in the payload, please ensure that the date is not BEFORE\n` + "`" + `firstAvailableDeliveryDate1` + "`" + ` or ` + "`" + `firstAvailableDeliveryDate2` + "`" + `. If it is, you will receive an error response.\n\n**DDASIT**\n\n**Addt'l days destination SIT service item**. This represents an additional day of storage for the same item.\nAdditional DDASIT service items can be created and added to an existing shipment that **includes a DDFSIT service item**.\n\n---\n\n**` + "`" + `MTOServiceItemInternationalOriginSIT` + "`" + `**\n\nMTOServiceItemInternationalOriginSIT is a subtype of MTOServiceItem.\n\nThis model type describes a international origin SIT service item. Items can be created using this\nmodel type with the following codes:\n\n**IOFSIT**\n\n**1st day origin SIT service item**. When a IOFSIT is requested, the API will auto-create the following group of service items:\n * IOFSIT - International origin 1st day SIT\n * IOASIT - International origin Additional day SIT\n * IOPSIT - International origin SIT pickup\n * IOSFSC - International origin SIT fuel surcharge\n\n**IOASIT**\n\n**Addt'l days origin SIT service item**. This represents an additional day of storage for the same item.\nAdditional IOASIT service items can be created and added to an existing shipment that **includes a IOFSIT service item**.\n\n---\n\n**` + "`" + `MTOServiceItemInternationalDestSIT` + "`" + `**\n\nMTOServiceItemInternationalDestSIT is a subtype of MTOServiceItem.\n\nThis model type describes a international destination SIT service item. Items can be created using this\nmodel type with the following codes:\n\n**IDFSIT**\n\n**1st day destination SIT service item**.\n\nThese additional fields are optional for creating a IDFSIT:\n * ` + "`" + `firstAvailableDeliveryDate1` + "`" + `\n * string \u003cdate\u003e\n * First available date that Prime can deliver SIT service item.\n * firstAvailableDeliveryDate1, dateOfContact1, and timeMilitary1 are required together\n * ` + "`" + `dateOfContact1` + "`" + `\n * string \u003cdate\u003e\n * Date of attempted contact by the prime corresponding to ` + "`" + `timeMilitary1` + "`" + `\n * dateOfContact1, timeMilitary1, and firstAvailableDeliveryDate1 are required together\n * ` + "`" + `timeMilitary1` + "`" + `\n * string\\d{4}Z\n * Time of attempted contact corresponding to ` + "`" + `dateOfContact1` + "`" + `, in military format.\n * timeMilitary1, dateOfContact1, and firstAvailableDeliveryDate1 are required together\n * ` + "`" + `firstAvailableDeliveryDate2` + "`" + `\n * string \u003cdate\u003e\n * Second available date that Prime can deliver SIT service item.\n * firstAvailableDeliveryDate2, dateOfContact2, and timeMilitary2 are required together\n * ` + "`" + `dateOfContact2` + "`" + `\n * string \u003cdate\u003e\n * Date of attempted contact delivery by the prime corresponding to ` + "`" + `timeMilitary2` + "`" + `\n * dateOfContact2, timeMilitary2, and firstAvailableDeliveryDate2 are required together\n * ` + "`" + `timeMilitary2` + "`" + `\n * string\\d{4}Z\n * Time of attempted contact corresponding to ` + "`" + `dateOfContact2` + "`" + `, in military format.\n * timeMilitary2, dateOfContact2, and firstAvailableDeliveryDate2 are required together\n\nWhen a IDFSIT is requested, the API will auto-create the following group of service items:\n * IDFSIT - International destination 1st day SIT\n * IDASIT - International destination Additional day SIT\n * IDDSIT - International destination SIT delivery\n * IDSFSC - International destination SIT fuel surcharge\n\n**NOTE** When providing the ` + "`" + `sitEntryDate` + "`" + ` value in the payload, please ensure that the date is not BEFORE\n` + "`" + `firstAvailableDeliveryDate1` + "`" + ` or ` + "`" + `firstAvailableDeliveryDate2` + "`" + `. If it is, you will receive an error response.\n\n**IDASIT**\n\n**Addt'l days destination SIT service item**. This represents an additional day of storage for the same item.\nAdditional IDASIT service items can be created and added to an existing shipment that **includes a IDFSIT service item**.\n", "consumes": [ "application/json" ], @@ -355,7 +393,7 @@ func init() { }, "/mto-service-items/{mtoServiceItemID}": { "patch": { - "description": "Updates MTOServiceItems after creation. Not all service items or fields may be updated, please see details below.\n\nThis endpoint supports different body definitions. In the modelType field below, select the modelType corresponding\n to the service item you wish to update and the documentation will update with the new definition.\n\n* Addresses: To update a destination service item's SIT destination final address, update the shipment delivery address.\nFor approved shipments, please use [updateShipmentDestinationAddress](#mtoShipment/updateShipmentDestinationAddress).\nFor shipments not yet approved, please use [updateMTOShipmentAddress](#mtoShipment/updateMTOShipmentAddress).\n\n* SIT Service Items: Take note that when updating ` + "`" + `sitCustomerContacted` + "`" + `, ` + "`" + `sitDepartureDate` + "`" + `, or ` + "`" + `sitRequestedDelivery` + "`" + `, we want\nthose to be updated on ` + "`" + `DOASIT` + "`" + ` (for origin SIT) and ` + "`" + `DDASIT` + "`" + ` (for destination SIT). If updating those values in other service\nitems, the office users will not have as much attention to those values.\n\nTo create a service item, please use [createMTOServiceItem](#mtoServiceItem/createMTOServiceItem)) endpoint.\n\n* Resubmitting rejected SIT/Accessorial service items: This endpoint will handle the logic of changing the status of rejected SIT/Accessorial service items from\nREJECTED to SUBMITTED. Please provide the ` + "`" + `requestedApprovalsRequestedStatus: true` + "`" + ` when resubmitting as this will give attention to the TOO to\nreview the resubmitted SIT/Accessorial service item. Another note, ` + "`" + `updateReason` + "`" + ` must have a different value than the current ` + "`" + `reason` + "`" + ` value on the service item.\nIf this value is not updated, then an error will be sent back.\n\nThe following SIT service items can be resubmitted following a rejection:\n- DDASIT\n- DDDSIT\n- DDFSIT\n- DOASIT\n- DOPSIT\n- DOFSIT\n- DDSFSC\n- DOSFSC\n\nThe following Accessorial service items can be resubmitted following a rejection:\n- IOSHUT\n- IDSHUT\n\nAt a MINIMUM, the payload for resubmitting a rejected SIT/Accessorial service item must look like this:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"reServiceCode\": \"DDFSIT\",\n \"updateReason\": \"A reason that differs from the previous reason\",\n \"modelType\": \"UpdateMTOServiceItemSIT\",\n \"requestApprovalsRequestedStatus\": true\n}\n` + "`" + `` + "`" + `` + "`" + `\n\nThe following service items allow you to update the Port that the shipment will use:\n- PODFSC (Port of Debarkation can be updated)\n- POEFSC (Port of Embarkation can be updated)\n\nAt a MINIMUM, the payload for updating the port should contain the reServiceCode (PODFSC or POEFSC), modelType (UpdateMTOServiceItemInternationalPortFSC), portCode, and id for the service item.\nPlease see the example payload below:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"id\": \"1ed224b6-c65e-4616-b88e-8304d26c9562\",\n \"modelType\": \"UpdateMTOServiceItemInternationalPortFSC\",\n \"portCode\": \"SEA\",\n \"reServiceCode\": \"POEFSC\"\n}\n` + "`" + `` + "`" + `` + "`" + `\n", + "description": "Updates MTOServiceItems after creation. Not all service items or fields may be updated, please see details below.\n\nThis endpoint supports different body definitions. In the modelType field below, select the modelType corresponding\n to the service item you wish to update and the documentation will update with the new definition.\n\n* Addresses: To update a destination service item's SIT destination final address, update the shipment delivery address.\nFor approved shipments, please use [updateShipmentDestinationAddress](#mtoShipment/updateShipmentDestinationAddress).\nFor shipments not yet approved, please use [updateMTOShipmentAddress](#mtoShipment/updateMTOShipmentAddress).\n\n* SIT Service Items: Take note that when updating ` + "`" + `sitCustomerContacted` + "`" + `, ` + "`" + `sitDepartureDate` + "`" + `, or ` + "`" + `sitRequestedDelivery` + "`" + `, we want\nthose to be updated on ` + "`" + `DOASIT` + "`" + ` (for origin SIT) and ` + "`" + `DDASIT` + "`" + ` (for destination SIT). If updating those values in other service\nitems, the office users will not have as much attention to those values.\n\nTo create a service item, please use [createMTOServiceItem](#mtoServiceItem/createMTOServiceItem)) endpoint.\n\n* Resubmitting rejected SIT/Accessorial service items: This endpoint will handle the logic of changing the status of rejected SIT/Accessorial service items from\nREJECTED to SUBMITTED. Please provide the ` + "`" + `requestedApprovalsRequestedStatus: true` + "`" + ` when resubmitting as this will give attention to the TOO to\nreview the resubmitted SIT/Accessorial service item. Another note, ` + "`" + `updateReason` + "`" + ` must have a different value than the current ` + "`" + `reason` + "`" + ` value on the service item.\nIf this value is not updated, then an error will be sent back.\n\nThe following SIT service items can be resubmitted following a rejection:\n- DDASIT\n- DDDSIT\n- DDFSIT\n- DOASIT\n- DOPSIT\n- DOFSIT\n- DDSFSC\n- DOSFSC\n- IDASIT\n- IDDSIT\n- IDFSIT\n- IOASIT\n- IOPSIT\n- IOFSIT\n- IDSFSC\n- IOSFSC\n\nThe following Accessorial service items can be resubmitted following a rejection:\n- IOSHUT\n- IDSHUT\n\nAt a MINIMUM, the payload for resubmitting a rejected SIT/Accessorial service item must look like this:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"reServiceCode\": \"DDFSIT\",\n \"updateReason\": \"A reason that differs from the previous reason\",\n \"modelType\": \"UpdateMTOServiceItemSIT\",\n \"requestApprovalsRequestedStatus\": true\n}\n` + "`" + `` + "`" + `` + "`" + `\n\nThe following service items allow you to update the Port that the shipment will use:\n- PODFSC (Port of Debarkation can be updated)\n- POEFSC (Port of Embarkation can be updated)\n\nAt a MINIMUM, the payload for updating the port should contain the reServiceCode (PODFSC or POEFSC), modelType (UpdateMTOServiceItemInternationalPortFSC), portCode, and id for the service item.\nPlease see the example payload below:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"id\": \"1ed224b6-c65e-4616-b88e-8304d26c9562\",\n \"modelType\": \"UpdateMTOServiceItemInternationalPortFSC\",\n \"portCode\": \"SEA\",\n \"reServiceCode\": \"POEFSC\"\n}\n` + "`" + `` + "`" + `` + "`" + `\n\nThe following crating/uncrating service items can be resubmitted following a rejection:\n- ICRT\n- IUCRT\n\nAt a MINIMUM, the payload for resubmitting a rejected crating/uncrating service item must look like this:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"item\": {\n \"length\": 10000,\n \"width\": 10000,\n \"height\": 10000\n },\n \"crate\": {\n \"length\": 20000,\n \"width\": 20000,\n \"height\": 20000\n },\n \"updateReason\": \"A reason that differs from the previous reason\",\n \"modelType\": \"UpdateMTOServiceItemCrating\",\n \"requestApprovalsRequestedStatus\": true\n}\n` + "`" + `` + "`" + `` + "`" + `\n", "consumes": [ "application/json" ], @@ -2330,6 +2368,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": [ @@ -2402,6 +2484,102 @@ func init() { } ] }, + "MTOServiceItemInternationalDestSIT": { + "description": "Describes a international destination SIT service item. Subtype of a MTOServiceItem.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reServiceCode", + "sitEntryDate", + "reason" + ], + "properties": { + "dateOfContact1": { + "description": "Date of attempted contact by the prime corresponding to ` + "`" + `timeMilitary1` + "`" + `.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "dateOfContact2": { + "description": "Date of attempted contact by the prime corresponding to ` + "`" + `timeMilitary2` + "`" + `.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "firstAvailableDeliveryDate1": { + "description": "First available date that Prime can deliver SIT service item.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "firstAvailableDeliveryDate2": { + "description": "Second available date that Prime can deliver SIT service item.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "reServiceCode": { + "description": "Service code allowed for this model type.", + "type": "string", + "enum": [ + "IDFSIT", + "IDASIT" + ] + }, + "reason": { + "description": "The reason item has been placed in SIT.\n", + "type": "string", + "x-nullable": true, + "x-omitempty": false + }, + "sitCustomerContacted": { + "description": "Date when the customer contacted the prime for a delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "sitDepartureDate": { + "description": "Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "sitDestinationFinalAddress": { + "$ref": "#/definitions/Address" + }, + "sitEntryDate": { + "description": "Entry date for the SIT", + "type": "string", + "format": "date" + }, + "sitRequestedDelivery": { + "description": "Date when the customer has requested delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "timeMilitary1": { + "description": "Time of attempted contact corresponding to ` + "`" + `dateOfContact1` + "`" + `, in military format.", + "type": "string", + "pattern": "\\d{4}Z", + "x-nullable": true, + "example": "1400Z" + }, + "timeMilitary2": { + "description": "Time of attempted contact corresponding to ` + "`" + `dateOfContact2` + "`" + `, in military format.", + "type": "string", + "pattern": "\\d{4}Z", + "x-nullable": true, + "example": "1400Z" + } + } + } + ] + }, "MTOServiceItemInternationalFuelSurcharge": { "description": "Describes a international Port of Embarkation/Debarkation fuel surcharge service item subtype of a MTOServiceItem.", "allOf": [ @@ -2427,6 +2605,76 @@ func init() { } ] }, + "MTOServiceItemInternationalOriginSIT": { + "description": "Describes a international origin SIT service item. Subtype of a MTOServiceItem.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reServiceCode", + "reason", + "sitPostalCode", + "sitEntryDate" + ], + "properties": { + "reServiceCode": { + "description": "Service code allowed for this model type.", + "type": "string", + "enum": [ + "IOFSIT", + "IOASIT" + ] + }, + "reason": { + "description": "Explanation of why Prime is picking up SIT item.", + "type": "string", + "example": "Storage items need to be picked up" + }, + "requestApprovalsRequestedStatus": { + "type": "boolean" + }, + "sitCustomerContacted": { + "description": "Date when the customer contacted the prime for a delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "sitDepartureDate": { + "description": "Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "sitEntryDate": { + "description": "Entry date for the SIT", + "type": "string", + "format": "date" + }, + "sitHHGActualOrigin": { + "$ref": "#/definitions/Address" + }, + "sitHHGOriginalOrigin": { + "$ref": "#/definitions/Address" + }, + "sitPostalCode": { + "type": "string", + "format": "zip", + "pattern": "^(\\d{5}([\\-]\\d{4})?)$", + "example": "90210" + }, + "sitRequestedDelivery": { + "description": "Date when the customer has requested delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + } + } + } + ] + }, "MTOServiceItemInternationalShuttle": { "description": "Describes an international shuttle service item.", "allOf": [ @@ -2486,13 +2734,16 @@ 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 * IOFSIT, IOASIT - MTOServiceItemInternationalOriginSIT\n * IDFSIT, IDASIT - MTOServiceItemInternationalDestSIT\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", + "MTOServiceItemInternationalOriginSIT", + "MTOServiceItemInternationalDestSIT", "MTOServiceItemShuttle", + "MTOServiceItemDomesticShuttle", "MTOServiceItemInternationalShuttle", "MTOServiceItemDomesticCrating", "MTOServiceItemInternationalCrating", @@ -3805,7 +4056,7 @@ func init() { "example": "c56a4180-65aa-42ec-a945-5fd21dec0538" }, "params": { - "description": "This should be populated for the following service items:\n * DOASIT(Domestic origin Additional day SIT)\n * DDASIT(Domestic destination Additional day SIT)\n\nBoth take in the following param keys:\n * ` + "`" + `SITPaymentRequestStart` + "`" + `\n * ` + "`" + `SITPaymentRequestEnd` + "`" + `\n\nThe value of each is a date string in the format \"YYYY-MM-DD\" (e.g. \"2023-01-15\")\n", + "description": "This should be populated for the following service items:\n * DOASIT(Domestic origin Additional day SIT)\n * DDASIT(Domestic destination Additional day SIT)\n * IOASIT(International origin Additional day SIT)\n * IDASIT(International destination Additional day SIT)\n\nBoth take in the following param keys:\n * ` + "`" + `SITPaymentRequestStart` + "`" + `\n * ` + "`" + `SITPaymentRequestEnd` + "`" + `\n\nThe value of each is a date string in the format \"YYYY-MM-DD\" (e.g. \"2023-01-15\")\n", "type": "array", "items": { "type": "object", @@ -4069,8 +4320,8 @@ func init() { }, "discriminator": "modelType" }, - "UpdateMTOServiceItemInternationalPortFSC": { - "description": "Subtype used to provide the port for fuel surcharge. This is not creating a new service item but rather updating an existing service item.\n", + "UpdateMTOServiceItemCrating": { + "description": "Subtype used to provide the size and types for crating. This is not creating a new service item but rather updating an existing service item.\n", "allOf": [ { "$ref": "#/definitions/UpdateMTOServiceItem" @@ -4078,22 +4329,83 @@ func init() { { "type": "object", "properties": { - "portCode": { - "description": "Port used for the shipment. Relevant for moving (PODFSC \u0026 POEFSC) service items.", + "crate": { + "description": "The dimensions for the crate the item will be shipped in.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItemDimension" + } + ] + }, + "description": { + "description": "A description of the item being crated.", "type": "string", "x-nullable": true, - "x-omitempty": false, - "example": "PDX" + "example": "Decorated horse head to be crated." }, - "reServiceCode": { - "description": "Service code allowed for this model type.", - "type": "string", - "enum": [ - "PODFSC", - "POEFSC" - ] - } - } + "externalCrate": { + "type": "boolean", + "x-nullable": true + }, + "item": { + "description": "The dimensions of the item being crated.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItemDimension" + } + ] + }, + "reServiceCode": { + "description": "Service code allowed for this model type.", + "type": "string", + "enum": [ + "ICRT", + "IUCRT" + ] + }, + "requestApprovalsRequestedStatus": { + "description": "Indicates if \"Approvals Requested\" status is being requested.", + "type": "boolean", + "x-nullable": true + }, + "standaloneCrate": { + "type": "boolean", + "x-nullable": true + }, + "updateReason": { + "description": "Reason for updating service item.", + "type": "string", + "x-nullable": true + } + } + } + ] + }, + "UpdateMTOServiceItemInternationalPortFSC": { + "description": "Subtype used to provide the port for fuel surcharge. This is not creating a new service item but rather updating an existing service item.\n", + "allOf": [ + { + "$ref": "#/definitions/UpdateMTOServiceItem" + }, + { + "type": "object", + "properties": { + "portCode": { + "description": "Port used for the shipment. Relevant for moving (PODFSC \u0026 POEFSC) service items.", + "type": "string", + "x-nullable": true, + "x-omitempty": false, + "example": "PDX" + }, + "reServiceCode": { + "description": "Service code allowed for this model type.", + "type": "string", + "enum": [ + "PODFSC", + "POEFSC" + ] + } + } } ] }, @@ -4138,13 +4450,14 @@ func init() { ] }, "UpdateMTOServiceItemModelType": { - "description": "Using this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DDDSIT - UpdateMTOServiceItemSIT\n * DDFSIT - UpdateMTOServiceItemSIT\n * DDASIT - UpdateMTOServiceItemSIT\n * DOPSIT - UpdateMTOServiceItemSIT\n * DOASIT - UpdateMTOServiceItemSIT\n * DOFSIT - UpdateMTOServiceItemSIT\n * DOSFSC - UpdateMTOServiceItemSIT\n * DDSFSC - UpdateMTOServiceItemSIT\n * DDSHUT - UpdateMTOServiceItemShuttle\n * DOSHUT - UpdateMTOServiceItemShuttle\n * PODFSC - UpdateMTOServiceItemInternationalPortFSC\n * POEFSC - UpdateMTOServiceItemInternationalPortFSC\n * IDSHUT - UpdateMTOServiceItemInternationalShuttle\n * IOSHUT - UpdateMTOServiceItemInternationalShuttle\n\nThe documentation will then update with the supported fields.\n", + "description": "Using this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DDDSIT - UpdateMTOServiceItemSIT\n * DDFSIT - UpdateMTOServiceItemSIT\n * DDASIT - UpdateMTOServiceItemSIT\n * DOPSIT - UpdateMTOServiceItemSIT\n * DOASIT - UpdateMTOServiceItemSIT\n * DOFSIT - UpdateMTOServiceItemSIT\n * DOSFSC - UpdateMTOServiceItemSIT\n * DDSFSC - UpdateMTOServiceItemSIT\n * IDDSIT - UpdateMTOServiceItemSIT\n * IDFSIT - UpdateMTOServiceItemSIT\n * IDASIT - UpdateMTOServiceItemSIT\n * IOPSIT - UpdateMTOServiceItemSIT\n * IOASIT - UpdateMTOServiceItemSIT\n * IOFSIT - UpdateMTOServiceItemSIT\n * IOSFSC - UpdateMTOServiceItemSIT\n * IDSFSC - UpdateMTOServiceItemSIT\n * DDSHUT - UpdateMTOServiceItemShuttle\n * DOSHUT - UpdateMTOServiceItemShuttle\n * PODFSC - UpdateMTOServiceItemInternationalPortFSC\n * POEFSC - UpdateMTOServiceItemInternationalPortFSC\n * IDSHUT - UpdateMTOServiceItemInternationalShuttle\n * IOSHUT - UpdateMTOServiceItemInternationalShuttle\n * ICRT - UpdateMTOServiceItemCrating\n * IUCRT - UpdateMTOServiceItemCrating\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "UpdateMTOServiceItemSIT", "UpdateMTOServiceItemShuttle", "UpdateMTOServiceItemInternationalPortFSC", - "UpdateMTOServiceItemInternationalShuttle" + "UpdateMTOServiceItemInternationalShuttle", + "UpdateMTOServiceItemCrating" ] }, "UpdateMTOServiceItemSIT": { @@ -4191,7 +4504,15 @@ func init() { "DOPSIT", "DOASIT", "DOFSIT", - "DOSFSC" + "DOSFSC", + "IDDSIT", + "IDASIT", + "IDFSIT", + "IDSFSC", + "IOPSIT", + "IOASIT", + "IOFSIT", + "IOSFSC" ] }, "requestApprovalsRequestedStatus": { @@ -4607,6 +4928,151 @@ func init() { } } }, + "VLocation": { + "description": "A postal code, city, and state lookup", + "type": "object", + "properties": { + "city": { + "type": "string", + "title": "City", + "example": "Anytown" + }, + "county": { + "type": "string", + "title": "County", + "x-nullable": true, + "example": "LOS ANGELES" + }, + "postalCode": { + "type": "string", + "format": "zip", + "title": "ZIP", + "pattern": "^(\\d{5}?)$", + "example": "90210" + }, + "state": { + "type": "string", + "title": "State", + "enum": [ + "AL", + "AK", + "AR", + "AZ", + "CA", + "CO", + "CT", + "DC", + "DE", + "FL", + "GA", + "HI", + "IA", + "ID", + "IL", + "IN", + "KS", + "KY", + "LA", + "MA", + "MD", + "ME", + "MI", + "MN", + "MO", + "MS", + "MT", + "NC", + "ND", + "NE", + "NH", + "NJ", + "NM", + "NV", + "NY", + "OH", + "OK", + "OR", + "PA", + "RI", + "SC", + "SD", + "TN", + "TX", + "UT", + "VA", + "VT", + "WA", + "WI", + "WV", + "WY" + ], + "x-display-value": { + "AK": "AK", + "AL": "AL", + "AR": "AR", + "AZ": "AZ", + "CA": "CA", + "CO": "CO", + "CT": "CT", + "DC": "DC", + "DE": "DE", + "FL": "FL", + "GA": "GA", + "HI": "HI", + "IA": "IA", + "ID": "ID", + "IL": "IL", + "IN": "IN", + "KS": "KS", + "KY": "KY", + "LA": "LA", + "MA": "MA", + "MD": "MD", + "ME": "ME", + "MI": "MI", + "MN": "MN", + "MO": "MO", + "MS": "MS", + "MT": "MT", + "NC": "NC", + "ND": "ND", + "NE": "NE", + "NH": "NH", + "NJ": "NJ", + "NM": "NM", + "NV": "NV", + "NY": "NY", + "OH": "OH", + "OK": "OK", + "OR": "OR", + "PA": "PA", + "RI": "RI", + "SC": "SC", + "SD": "SD", + "TN": "TN", + "TX": "TX", + "UT": "UT", + "VA": "VA", + "VT": "VT", + "WA": "WA", + "WI": "WI", + "WV": "WV", + "WY": "WY" + } + }, + "usPostRegionCitiesID": { + "type": "string", + "format": "uuid", + "example": "c56a4180-65aa-42ec-a945-5fd21dec0538" + } + } + }, + "VLocations": { + "type": "array", + "items": { + "$ref": "#/definitions/VLocation" + } + }, "ValidationError": { "allOf": [ { @@ -4741,6 +5207,56 @@ func init() { }, "basePath": "/prime/v1", "paths": { + "/addresses/zip-city-lookup/{search}": { + "get": { + "description": "Find by API using full/partial postal code or city name that returns an us_post_region_cities json object containing city, state, county and postal code.", + "tags": [ + "addresses" + ], + "summary": "Returns city, state, postal code, and county associated with the specified full/partial postal code or city state string", + "operationId": "getLocationByZipCityState", + "parameters": [ + { + "type": "string", + "name": "search", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "the requested list of city, state, county, and postal code matches", + "schema": { + "$ref": "#/definitions/VLocations" + } + }, + "400": { + "description": "The request payload is invalid.", + "schema": { + "$ref": "#/definitions/ClientError" + } + }, + "403": { + "description": "The request was denied.", + "schema": { + "$ref": "#/definitions/ClientError" + } + }, + "404": { + "description": "The requested resource wasn't found.", + "schema": { + "$ref": "#/definitions/ClientError" + } + }, + "500": { + "description": "A server error occurred.", + "schema": { + "$ref": "#/definitions/Error" + } + } + } + } + }, "/move-task-orders/{moveID}": { "get": { "description": "### Functionality\nThis endpoint gets an individual MoveTaskOrder by ID.\n\nIt will provide information about the Customer and any associated MTOShipments, MTOServiceItems and PaymentRequests.\n", @@ -5079,7 +5595,7 @@ func init() { }, "/mto-service-items": { "post": { - "description": "Creates one or more MTOServiceItems. Not all service items may be created, please see details below.\n\nThis endpoint supports different body definitions. In the modelType field below, select the modelType corresponding\n to the service item you wish to create and the documentation will update with the new definition.\n\nUpon creation these items are associated with a Move Task Order and an MTO Shipment.\nThe request must include UUIDs for the MTO and MTO Shipment connected to this service item. Some service item types require\nadditional service items to be autogenerated when added - all created service items, autogenerated included,\nwill be returned in the response.\n\nTo update a service item, please use [updateMTOServiceItem](#operation/updateMTOServiceItem) endpoint.\n\n---\n\n**` + "`" + `MTOServiceItemOriginSIT` + "`" + `**\n\nMTOServiceItemOriginSIT is a subtype of MTOServiceItem.\n\nThis model type describes a domestic origin SIT service item. Items can be created using this\nmodel type with the following codes:\n\n**DOFSIT**\n\n**1st day origin SIT service item**. When a DOFSIT is requested, the API will auto-create the following group of service items:\n * DOFSIT - Domestic origin 1st day SIT\n * DOASIT - Domestic origin Additional day SIT\n * DOPSIT - Domestic origin SIT pickup\n * DOSFSC - Domestic origin SIT fuel surcharge\n\n**DOASIT**\n\n**Addt'l days origin SIT service item**. This represents an additional day of storage for the same item.\nAdditional DOASIT service items can be created and added to an existing shipment that **includes a DOFSIT service item**.\n\n---\n\n**` + "`" + `MTOServiceItemDestSIT` + "`" + `**\n\nMTOServiceItemDestSIT is a subtype of MTOServiceItem.\n\nThis model type describes a domestic destination SIT service item. Items can be created using this\nmodel type with the following codes:\n\n**DDFSIT**\n\n**1st day destination SIT service item**.\n\nThese additional fields are optional for creating a DDFSIT:\n * ` + "`" + `firstAvailableDeliveryDate1` + "`" + `\n * string \u003cdate\u003e\n * First available date that Prime can deliver SIT service item.\n * firstAvailableDeliveryDate1, dateOfContact1, and timeMilitary1 are required together\n * ` + "`" + `dateOfContact1` + "`" + `\n * string \u003cdate\u003e\n * Date of attempted contact by the prime corresponding to ` + "`" + `timeMilitary1` + "`" + `\n * dateOfContact1, timeMilitary1, and firstAvailableDeliveryDate1 are required together\n * ` + "`" + `timeMilitary1` + "`" + `\n * string\\d{4}Z\n * Time of attempted contact corresponding to ` + "`" + `dateOfContact1` + "`" + `, in military format.\n * timeMilitary1, dateOfContact1, and firstAvailableDeliveryDate1 are required together\n * ` + "`" + `firstAvailableDeliveryDate2` + "`" + `\n * string \u003cdate\u003e\n * Second available date that Prime can deliver SIT service item.\n * firstAvailableDeliveryDate2, dateOfContact2, and timeMilitary2 are required together\n * ` + "`" + `dateOfContact2` + "`" + `\n * string \u003cdate\u003e\n * Date of attempted contact delivery by the prime corresponding to ` + "`" + `timeMilitary2` + "`" + `\n * dateOfContact2, timeMilitary2, and firstAvailableDeliveryDate2 are required together\n * ` + "`" + `timeMilitary2` + "`" + `\n * string\\d{4}Z\n * Time of attempted contact corresponding to ` + "`" + `dateOfContact2` + "`" + `, in military format.\n * timeMilitary2, dateOfContact2, and firstAvailableDeliveryDate2 are required together\n\nWhen a DDFSIT is requested, the API will auto-create the following group of service items:\n * DDFSIT - Domestic destination 1st day SIT\n * DDASIT - Domestic destination Additional day SIT\n * DDDSIT - Domestic destination SIT delivery\n * DDSFSC - Domestic destination SIT fuel surcharge\n\n**NOTE** When providing the ` + "`" + `sitEntryDate` + "`" + ` value in the payload, please ensure that the date is not BEFORE\n` + "`" + `firstAvailableDeliveryDate1` + "`" + ` or ` + "`" + `firstAvailableDeliveryDate2` + "`" + `. If it is, you will receive an error response.\n\n**DDASIT**\n\n**Addt'l days destination SIT service item**. This represents an additional day of storage for the same item.\nAdditional DDASIT service items can be created and added to an existing shipment that **includes a DDFSIT service item**.\n", + "description": "Creates one or more MTOServiceItems. Not all service items may be created, please see details below.\n\nThis endpoint supports different body definitions. In the modelType field below, select the modelType corresponding\n to the service item you wish to create and the documentation will update with the new definition.\n\nUpon creation these items are associated with a Move Task Order and an MTO Shipment.\nThe request must include UUIDs for the MTO and MTO Shipment connected to this service item. Some service item types require\nadditional service items to be autogenerated when added - all created service items, autogenerated included,\nwill be returned in the response.\n\nTo update a service item, please use [updateMTOServiceItem](#operation/updateMTOServiceItem) endpoint.\n\n---\n\n**` + "`" + `MTOServiceItemOriginSIT` + "`" + `**\n\nMTOServiceItemOriginSIT is a subtype of MTOServiceItem.\n\nThis model type describes a domestic origin SIT service item. Items can be created using this\nmodel type with the following codes:\n\n**DOFSIT**\n\n**1st day origin SIT service item**. When a DOFSIT is requested, the API will auto-create the following group of service items:\n * DOFSIT - Domestic origin 1st day SIT\n * DOASIT - Domestic origin Additional day SIT\n * DOPSIT - Domestic origin SIT pickup\n * DOSFSC - Domestic origin SIT fuel surcharge\n\n**DOASIT**\n\n**Addt'l days origin SIT service item**. This represents an additional day of storage for the same item.\nAdditional DOASIT service items can be created and added to an existing shipment that **includes a DOFSIT service item**.\n\n---\n\n**` + "`" + `MTOServiceItemDestSIT` + "`" + `**\n\nMTOServiceItemDestSIT is a subtype of MTOServiceItem.\n\nThis model type describes a domestic destination SIT service item. Items can be created using this\nmodel type with the following codes:\n\n**DDFSIT**\n\n**1st day destination SIT service item**.\n\nThese additional fields are optional for creating a DDFSIT:\n * ` + "`" + `firstAvailableDeliveryDate1` + "`" + `\n * string \u003cdate\u003e\n * First available date that Prime can deliver SIT service item.\n * firstAvailableDeliveryDate1, dateOfContact1, and timeMilitary1 are required together\n * ` + "`" + `dateOfContact1` + "`" + `\n * string \u003cdate\u003e\n * Date of attempted contact by the prime corresponding to ` + "`" + `timeMilitary1` + "`" + `\n * dateOfContact1, timeMilitary1, and firstAvailableDeliveryDate1 are required together\n * ` + "`" + `timeMilitary1` + "`" + `\n * string\\d{4}Z\n * Time of attempted contact corresponding to ` + "`" + `dateOfContact1` + "`" + `, in military format.\n * timeMilitary1, dateOfContact1, and firstAvailableDeliveryDate1 are required together\n * ` + "`" + `firstAvailableDeliveryDate2` + "`" + `\n * string \u003cdate\u003e\n * Second available date that Prime can deliver SIT service item.\n * firstAvailableDeliveryDate2, dateOfContact2, and timeMilitary2 are required together\n * ` + "`" + `dateOfContact2` + "`" + `\n * string \u003cdate\u003e\n * Date of attempted contact delivery by the prime corresponding to ` + "`" + `timeMilitary2` + "`" + `\n * dateOfContact2, timeMilitary2, and firstAvailableDeliveryDate2 are required together\n * ` + "`" + `timeMilitary2` + "`" + `\n * string\\d{4}Z\n * Time of attempted contact corresponding to ` + "`" + `dateOfContact2` + "`" + `, in military format.\n * timeMilitary2, dateOfContact2, and firstAvailableDeliveryDate2 are required together\n\nWhen a DDFSIT is requested, the API will auto-create the following group of service items:\n * DDFSIT - Domestic destination 1st day SIT\n * DDASIT - Domestic destination Additional day SIT\n * DDDSIT - Domestic destination SIT delivery\n * DDSFSC - Domestic destination SIT fuel surcharge\n\n**NOTE** When providing the ` + "`" + `sitEntryDate` + "`" + ` value in the payload, please ensure that the date is not BEFORE\n` + "`" + `firstAvailableDeliveryDate1` + "`" + ` or ` + "`" + `firstAvailableDeliveryDate2` + "`" + `. If it is, you will receive an error response.\n\n**DDASIT**\n\n**Addt'l days destination SIT service item**. This represents an additional day of storage for the same item.\nAdditional DDASIT service items can be created and added to an existing shipment that **includes a DDFSIT service item**.\n\n---\n\n**` + "`" + `MTOServiceItemInternationalOriginSIT` + "`" + `**\n\nMTOServiceItemInternationalOriginSIT is a subtype of MTOServiceItem.\n\nThis model type describes a international origin SIT service item. Items can be created using this\nmodel type with the following codes:\n\n**IOFSIT**\n\n**1st day origin SIT service item**. When a IOFSIT is requested, the API will auto-create the following group of service items:\n * IOFSIT - International origin 1st day SIT\n * IOASIT - International origin Additional day SIT\n * IOPSIT - International origin SIT pickup\n * IOSFSC - International origin SIT fuel surcharge\n\n**IOASIT**\n\n**Addt'l days origin SIT service item**. This represents an additional day of storage for the same item.\nAdditional IOASIT service items can be created and added to an existing shipment that **includes a IOFSIT service item**.\n\n---\n\n**` + "`" + `MTOServiceItemInternationalDestSIT` + "`" + `**\n\nMTOServiceItemInternationalDestSIT is a subtype of MTOServiceItem.\n\nThis model type describes a international destination SIT service item. Items can be created using this\nmodel type with the following codes:\n\n**IDFSIT**\n\n**1st day destination SIT service item**.\n\nThese additional fields are optional for creating a IDFSIT:\n * ` + "`" + `firstAvailableDeliveryDate1` + "`" + `\n * string \u003cdate\u003e\n * First available date that Prime can deliver SIT service item.\n * firstAvailableDeliveryDate1, dateOfContact1, and timeMilitary1 are required together\n * ` + "`" + `dateOfContact1` + "`" + `\n * string \u003cdate\u003e\n * Date of attempted contact by the prime corresponding to ` + "`" + `timeMilitary1` + "`" + `\n * dateOfContact1, timeMilitary1, and firstAvailableDeliveryDate1 are required together\n * ` + "`" + `timeMilitary1` + "`" + `\n * string\\d{4}Z\n * Time of attempted contact corresponding to ` + "`" + `dateOfContact1` + "`" + `, in military format.\n * timeMilitary1, dateOfContact1, and firstAvailableDeliveryDate1 are required together\n * ` + "`" + `firstAvailableDeliveryDate2` + "`" + `\n * string \u003cdate\u003e\n * Second available date that Prime can deliver SIT service item.\n * firstAvailableDeliveryDate2, dateOfContact2, and timeMilitary2 are required together\n * ` + "`" + `dateOfContact2` + "`" + `\n * string \u003cdate\u003e\n * Date of attempted contact delivery by the prime corresponding to ` + "`" + `timeMilitary2` + "`" + `\n * dateOfContact2, timeMilitary2, and firstAvailableDeliveryDate2 are required together\n * ` + "`" + `timeMilitary2` + "`" + `\n * string\\d{4}Z\n * Time of attempted contact corresponding to ` + "`" + `dateOfContact2` + "`" + `, in military format.\n * timeMilitary2, dateOfContact2, and firstAvailableDeliveryDate2 are required together\n\nWhen a IDFSIT is requested, the API will auto-create the following group of service items:\n * IDFSIT - International destination 1st day SIT\n * IDASIT - International destination Additional day SIT\n * IDDSIT - International destination SIT delivery\n * IDSFSC - International destination SIT fuel surcharge\n\n**NOTE** When providing the ` + "`" + `sitEntryDate` + "`" + ` value in the payload, please ensure that the date is not BEFORE\n` + "`" + `firstAvailableDeliveryDate1` + "`" + ` or ` + "`" + `firstAvailableDeliveryDate2` + "`" + `. If it is, you will receive an error response.\n\n**IDASIT**\n\n**Addt'l days destination SIT service item**. This represents an additional day of storage for the same item.\nAdditional IDASIT service items can be created and added to an existing shipment that **includes a IDFSIT service item**.\n", "consumes": [ "application/json" ], @@ -5157,7 +5673,7 @@ func init() { }, "/mto-service-items/{mtoServiceItemID}": { "patch": { - "description": "Updates MTOServiceItems after creation. Not all service items or fields may be updated, please see details below.\n\nThis endpoint supports different body definitions. In the modelType field below, select the modelType corresponding\n to the service item you wish to update and the documentation will update with the new definition.\n\n* Addresses: To update a destination service item's SIT destination final address, update the shipment delivery address.\nFor approved shipments, please use [updateShipmentDestinationAddress](#mtoShipment/updateShipmentDestinationAddress).\nFor shipments not yet approved, please use [updateMTOShipmentAddress](#mtoShipment/updateMTOShipmentAddress).\n\n* SIT Service Items: Take note that when updating ` + "`" + `sitCustomerContacted` + "`" + `, ` + "`" + `sitDepartureDate` + "`" + `, or ` + "`" + `sitRequestedDelivery` + "`" + `, we want\nthose to be updated on ` + "`" + `DOASIT` + "`" + ` (for origin SIT) and ` + "`" + `DDASIT` + "`" + ` (for destination SIT). If updating those values in other service\nitems, the office users will not have as much attention to those values.\n\nTo create a service item, please use [createMTOServiceItem](#mtoServiceItem/createMTOServiceItem)) endpoint.\n\n* Resubmitting rejected SIT/Accessorial service items: This endpoint will handle the logic of changing the status of rejected SIT/Accessorial service items from\nREJECTED to SUBMITTED. Please provide the ` + "`" + `requestedApprovalsRequestedStatus: true` + "`" + ` when resubmitting as this will give attention to the TOO to\nreview the resubmitted SIT/Accessorial service item. Another note, ` + "`" + `updateReason` + "`" + ` must have a different value than the current ` + "`" + `reason` + "`" + ` value on the service item.\nIf this value is not updated, then an error will be sent back.\n\nThe following SIT service items can be resubmitted following a rejection:\n- DDASIT\n- DDDSIT\n- DDFSIT\n- DOASIT\n- DOPSIT\n- DOFSIT\n- DDSFSC\n- DOSFSC\n\nThe following Accessorial service items can be resubmitted following a rejection:\n- IOSHUT\n- IDSHUT\n\nAt a MINIMUM, the payload for resubmitting a rejected SIT/Accessorial service item must look like this:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"reServiceCode\": \"DDFSIT\",\n \"updateReason\": \"A reason that differs from the previous reason\",\n \"modelType\": \"UpdateMTOServiceItemSIT\",\n \"requestApprovalsRequestedStatus\": true\n}\n` + "`" + `` + "`" + `` + "`" + `\n\nThe following service items allow you to update the Port that the shipment will use:\n- PODFSC (Port of Debarkation can be updated)\n- POEFSC (Port of Embarkation can be updated)\n\nAt a MINIMUM, the payload for updating the port should contain the reServiceCode (PODFSC or POEFSC), modelType (UpdateMTOServiceItemInternationalPortFSC), portCode, and id for the service item.\nPlease see the example payload below:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"id\": \"1ed224b6-c65e-4616-b88e-8304d26c9562\",\n \"modelType\": \"UpdateMTOServiceItemInternationalPortFSC\",\n \"portCode\": \"SEA\",\n \"reServiceCode\": \"POEFSC\"\n}\n` + "`" + `` + "`" + `` + "`" + `\n", + "description": "Updates MTOServiceItems after creation. Not all service items or fields may be updated, please see details below.\n\nThis endpoint supports different body definitions. In the modelType field below, select the modelType corresponding\n to the service item you wish to update and the documentation will update with the new definition.\n\n* Addresses: To update a destination service item's SIT destination final address, update the shipment delivery address.\nFor approved shipments, please use [updateShipmentDestinationAddress](#mtoShipment/updateShipmentDestinationAddress).\nFor shipments not yet approved, please use [updateMTOShipmentAddress](#mtoShipment/updateMTOShipmentAddress).\n\n* SIT Service Items: Take note that when updating ` + "`" + `sitCustomerContacted` + "`" + `, ` + "`" + `sitDepartureDate` + "`" + `, or ` + "`" + `sitRequestedDelivery` + "`" + `, we want\nthose to be updated on ` + "`" + `DOASIT` + "`" + ` (for origin SIT) and ` + "`" + `DDASIT` + "`" + ` (for destination SIT). If updating those values in other service\nitems, the office users will not have as much attention to those values.\n\nTo create a service item, please use [createMTOServiceItem](#mtoServiceItem/createMTOServiceItem)) endpoint.\n\n* Resubmitting rejected SIT/Accessorial service items: This endpoint will handle the logic of changing the status of rejected SIT/Accessorial service items from\nREJECTED to SUBMITTED. Please provide the ` + "`" + `requestedApprovalsRequestedStatus: true` + "`" + ` when resubmitting as this will give attention to the TOO to\nreview the resubmitted SIT/Accessorial service item. Another note, ` + "`" + `updateReason` + "`" + ` must have a different value than the current ` + "`" + `reason` + "`" + ` value on the service item.\nIf this value is not updated, then an error will be sent back.\n\nThe following SIT service items can be resubmitted following a rejection:\n- DDASIT\n- DDDSIT\n- DDFSIT\n- DOASIT\n- DOPSIT\n- DOFSIT\n- DDSFSC\n- DOSFSC\n- IDASIT\n- IDDSIT\n- IDFSIT\n- IOASIT\n- IOPSIT\n- IOFSIT\n- IDSFSC\n- IOSFSC\n\nThe following Accessorial service items can be resubmitted following a rejection:\n- IOSHUT\n- IDSHUT\n\nAt a MINIMUM, the payload for resubmitting a rejected SIT/Accessorial service item must look like this:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"reServiceCode\": \"DDFSIT\",\n \"updateReason\": \"A reason that differs from the previous reason\",\n \"modelType\": \"UpdateMTOServiceItemSIT\",\n \"requestApprovalsRequestedStatus\": true\n}\n` + "`" + `` + "`" + `` + "`" + `\n\nThe following service items allow you to update the Port that the shipment will use:\n- PODFSC (Port of Debarkation can be updated)\n- POEFSC (Port of Embarkation can be updated)\n\nAt a MINIMUM, the payload for updating the port should contain the reServiceCode (PODFSC or POEFSC), modelType (UpdateMTOServiceItemInternationalPortFSC), portCode, and id for the service item.\nPlease see the example payload below:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"id\": \"1ed224b6-c65e-4616-b88e-8304d26c9562\",\n \"modelType\": \"UpdateMTOServiceItemInternationalPortFSC\",\n \"portCode\": \"SEA\",\n \"reServiceCode\": \"POEFSC\"\n}\n` + "`" + `` + "`" + `` + "`" + `\n\nThe following crating/uncrating service items can be resubmitted following a rejection:\n- ICRT\n- IUCRT\n\nAt a MINIMUM, the payload for resubmitting a rejected crating/uncrating service item must look like this:\n` + "`" + `` + "`" + `` + "`" + `json\n{\n \"item\": {\n \"length\": 10000,\n \"width\": 10000,\n \"height\": 10000\n },\n \"crate\": {\n \"length\": 20000,\n \"width\": 20000,\n \"height\": 20000\n },\n \"updateReason\": \"A reason that differs from the previous reason\",\n \"modelType\": \"UpdateMTOServiceItemCrating\",\n \"requestApprovalsRequestedStatus\": true\n}\n` + "`" + `` + "`" + `` + "`" + `\n", "consumes": [ "application/json" ], @@ -7407,8 +7923,8 @@ func init() { } ] }, - "MTOServiceItemInternationalCrating": { - "description": "Describes a international crating/uncrating service item subtype of a MTOServiceItem.", + "MTOServiceItemDomesticShuttle": { + "description": "Describes a domestic shuttle service item.", "allOf": [ { "$ref": "#/definitions/MTOServiceItem" @@ -7416,15 +7932,59 @@ func init() { { "type": "object", "required": [ - "reServiceCode", - "item", - "crate", - "description" + "reason", + "reServiceCode" ], "properties": { - "crate": { - "description": "The dimensions for the crate the item will be shipped in.", - "allOf": [ + "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": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reServiceCode", + "item", + "crate", + "description" + ], + "properties": { + "crate": { + "description": "The dimensions for the crate the item will be shipped in.", + "allOf": [ { "$ref": "#/definitions/MTOServiceItemDimension" } @@ -7479,6 +8039,102 @@ func init() { } ] }, + "MTOServiceItemInternationalDestSIT": { + "description": "Describes a international destination SIT service item. Subtype of a MTOServiceItem.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reServiceCode", + "sitEntryDate", + "reason" + ], + "properties": { + "dateOfContact1": { + "description": "Date of attempted contact by the prime corresponding to ` + "`" + `timeMilitary1` + "`" + `.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "dateOfContact2": { + "description": "Date of attempted contact by the prime corresponding to ` + "`" + `timeMilitary2` + "`" + `.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "firstAvailableDeliveryDate1": { + "description": "First available date that Prime can deliver SIT service item.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "firstAvailableDeliveryDate2": { + "description": "Second available date that Prime can deliver SIT service item.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "reServiceCode": { + "description": "Service code allowed for this model type.", + "type": "string", + "enum": [ + "IDFSIT", + "IDASIT" + ] + }, + "reason": { + "description": "The reason item has been placed in SIT.\n", + "type": "string", + "x-nullable": true, + "x-omitempty": false + }, + "sitCustomerContacted": { + "description": "Date when the customer contacted the prime for a delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "sitDepartureDate": { + "description": "Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "sitDestinationFinalAddress": { + "$ref": "#/definitions/Address" + }, + "sitEntryDate": { + "description": "Entry date for the SIT", + "type": "string", + "format": "date" + }, + "sitRequestedDelivery": { + "description": "Date when the customer has requested delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "timeMilitary1": { + "description": "Time of attempted contact corresponding to ` + "`" + `dateOfContact1` + "`" + `, in military format.", + "type": "string", + "pattern": "\\d{4}Z", + "x-nullable": true, + "example": "1400Z" + }, + "timeMilitary2": { + "description": "Time of attempted contact corresponding to ` + "`" + `dateOfContact2` + "`" + `, in military format.", + "type": "string", + "pattern": "\\d{4}Z", + "x-nullable": true, + "example": "1400Z" + } + } + } + ] + }, "MTOServiceItemInternationalFuelSurcharge": { "description": "Describes a international Port of Embarkation/Debarkation fuel surcharge service item subtype of a MTOServiceItem.", "allOf": [ @@ -7504,6 +8160,76 @@ func init() { } ] }, + "MTOServiceItemInternationalOriginSIT": { + "description": "Describes a international origin SIT service item. Subtype of a MTOServiceItem.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reServiceCode", + "reason", + "sitPostalCode", + "sitEntryDate" + ], + "properties": { + "reServiceCode": { + "description": "Service code allowed for this model type.", + "type": "string", + "enum": [ + "IOFSIT", + "IOASIT" + ] + }, + "reason": { + "description": "Explanation of why Prime is picking up SIT item.", + "type": "string", + "example": "Storage items need to be picked up" + }, + "requestApprovalsRequestedStatus": { + "type": "boolean" + }, + "sitCustomerContacted": { + "description": "Date when the customer contacted the prime for a delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "sitDepartureDate": { + "description": "Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "sitEntryDate": { + "description": "Entry date for the SIT", + "type": "string", + "format": "date" + }, + "sitHHGActualOrigin": { + "$ref": "#/definitions/Address" + }, + "sitHHGOriginalOrigin": { + "$ref": "#/definitions/Address" + }, + "sitPostalCode": { + "type": "string", + "format": "zip", + "pattern": "^(\\d{5}([\\-]\\d{4})?)$", + "example": "90210" + }, + "sitRequestedDelivery": { + "description": "Date when the customer has requested delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + } + } + } + ] + }, "MTOServiceItemInternationalShuttle": { "description": "Describes an international shuttle service item.", "allOf": [ @@ -7563,13 +8289,16 @@ 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 * IOFSIT, IOASIT - MTOServiceItemInternationalOriginSIT\n * IDFSIT, IDASIT - MTOServiceItemInternationalDestSIT\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", + "MTOServiceItemInternationalOriginSIT", + "MTOServiceItemInternationalDestSIT", "MTOServiceItemShuttle", + "MTOServiceItemDomesticShuttle", "MTOServiceItemInternationalShuttle", "MTOServiceItemDomesticCrating", "MTOServiceItemInternationalCrating", @@ -8882,7 +9611,7 @@ func init() { "example": "c56a4180-65aa-42ec-a945-5fd21dec0538" }, "params": { - "description": "This should be populated for the following service items:\n * DOASIT(Domestic origin Additional day SIT)\n * DDASIT(Domestic destination Additional day SIT)\n\nBoth take in the following param keys:\n * ` + "`" + `SITPaymentRequestStart` + "`" + `\n * ` + "`" + `SITPaymentRequestEnd` + "`" + `\n\nThe value of each is a date string in the format \"YYYY-MM-DD\" (e.g. \"2023-01-15\")\n", + "description": "This should be populated for the following service items:\n * DOASIT(Domestic origin Additional day SIT)\n * DDASIT(Domestic destination Additional day SIT)\n * IOASIT(International origin Additional day SIT)\n * IDASIT(International destination Additional day SIT)\n\nBoth take in the following param keys:\n * ` + "`" + `SITPaymentRequestStart` + "`" + `\n * ` + "`" + `SITPaymentRequestEnd` + "`" + `\n\nThe value of each is a date string in the format \"YYYY-MM-DD\" (e.g. \"2023-01-15\")\n", "type": "array", "items": { "$ref": "#/definitions/ServiceItemParamsItems0" @@ -9151,6 +9880,67 @@ func init() { }, "discriminator": "modelType" }, + "UpdateMTOServiceItemCrating": { + "description": "Subtype used to provide the size and types for crating. This is not creating a new service item but rather updating an existing service item.\n", + "allOf": [ + { + "$ref": "#/definitions/UpdateMTOServiceItem" + }, + { + "type": "object", + "properties": { + "crate": { + "description": "The dimensions for the crate the item will be shipped in.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItemDimension" + } + ] + }, + "description": { + "description": "A description of the item being crated.", + "type": "string", + "x-nullable": true, + "example": "Decorated horse head to be crated." + }, + "externalCrate": { + "type": "boolean", + "x-nullable": true + }, + "item": { + "description": "The dimensions of the item being crated.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItemDimension" + } + ] + }, + "reServiceCode": { + "description": "Service code allowed for this model type.", + "type": "string", + "enum": [ + "ICRT", + "IUCRT" + ] + }, + "requestApprovalsRequestedStatus": { + "description": "Indicates if \"Approvals Requested\" status is being requested.", + "type": "boolean", + "x-nullable": true + }, + "standaloneCrate": { + "type": "boolean", + "x-nullable": true + }, + "updateReason": { + "description": "Reason for updating service item.", + "type": "string", + "x-nullable": true + } + } + } + ] + }, "UpdateMTOServiceItemInternationalPortFSC": { "description": "Subtype used to provide the port for fuel surcharge. This is not creating a new service item but rather updating an existing service item.\n", "allOf": [ @@ -9220,13 +10010,14 @@ func init() { ] }, "UpdateMTOServiceItemModelType": { - "description": "Using this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DDDSIT - UpdateMTOServiceItemSIT\n * DDFSIT - UpdateMTOServiceItemSIT\n * DDASIT - UpdateMTOServiceItemSIT\n * DOPSIT - UpdateMTOServiceItemSIT\n * DOASIT - UpdateMTOServiceItemSIT\n * DOFSIT - UpdateMTOServiceItemSIT\n * DOSFSC - UpdateMTOServiceItemSIT\n * DDSFSC - UpdateMTOServiceItemSIT\n * DDSHUT - UpdateMTOServiceItemShuttle\n * DOSHUT - UpdateMTOServiceItemShuttle\n * PODFSC - UpdateMTOServiceItemInternationalPortFSC\n * POEFSC - UpdateMTOServiceItemInternationalPortFSC\n * IDSHUT - UpdateMTOServiceItemInternationalShuttle\n * IOSHUT - UpdateMTOServiceItemInternationalShuttle\n\nThe documentation will then update with the supported fields.\n", + "description": "Using this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DDDSIT - UpdateMTOServiceItemSIT\n * DDFSIT - UpdateMTOServiceItemSIT\n * DDASIT - UpdateMTOServiceItemSIT\n * DOPSIT - UpdateMTOServiceItemSIT\n * DOASIT - UpdateMTOServiceItemSIT\n * DOFSIT - UpdateMTOServiceItemSIT\n * DOSFSC - UpdateMTOServiceItemSIT\n * DDSFSC - UpdateMTOServiceItemSIT\n * IDDSIT - UpdateMTOServiceItemSIT\n * IDFSIT - UpdateMTOServiceItemSIT\n * IDASIT - UpdateMTOServiceItemSIT\n * IOPSIT - UpdateMTOServiceItemSIT\n * IOASIT - UpdateMTOServiceItemSIT\n * IOFSIT - UpdateMTOServiceItemSIT\n * IOSFSC - UpdateMTOServiceItemSIT\n * IDSFSC - UpdateMTOServiceItemSIT\n * DDSHUT - UpdateMTOServiceItemShuttle\n * DOSHUT - UpdateMTOServiceItemShuttle\n * PODFSC - UpdateMTOServiceItemInternationalPortFSC\n * POEFSC - UpdateMTOServiceItemInternationalPortFSC\n * IDSHUT - UpdateMTOServiceItemInternationalShuttle\n * IOSHUT - UpdateMTOServiceItemInternationalShuttle\n * ICRT - UpdateMTOServiceItemCrating\n * IUCRT - UpdateMTOServiceItemCrating\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "UpdateMTOServiceItemSIT", "UpdateMTOServiceItemShuttle", "UpdateMTOServiceItemInternationalPortFSC", - "UpdateMTOServiceItemInternationalShuttle" + "UpdateMTOServiceItemInternationalShuttle", + "UpdateMTOServiceItemCrating" ] }, "UpdateMTOServiceItemSIT": { @@ -9273,7 +10064,15 @@ func init() { "DOPSIT", "DOASIT", "DOFSIT", - "DOSFSC" + "DOSFSC", + "IDDSIT", + "IDASIT", + "IDFSIT", + "IDSFSC", + "IOPSIT", + "IOASIT", + "IOFSIT", + "IOSFSC" ] }, "requestApprovalsRequestedStatus": { @@ -9689,6 +10488,151 @@ func init() { } } }, + "VLocation": { + "description": "A postal code, city, and state lookup", + "type": "object", + "properties": { + "city": { + "type": "string", + "title": "City", + "example": "Anytown" + }, + "county": { + "type": "string", + "title": "County", + "x-nullable": true, + "example": "LOS ANGELES" + }, + "postalCode": { + "type": "string", + "format": "zip", + "title": "ZIP", + "pattern": "^(\\d{5}?)$", + "example": "90210" + }, + "state": { + "type": "string", + "title": "State", + "enum": [ + "AL", + "AK", + "AR", + "AZ", + "CA", + "CO", + "CT", + "DC", + "DE", + "FL", + "GA", + "HI", + "IA", + "ID", + "IL", + "IN", + "KS", + "KY", + "LA", + "MA", + "MD", + "ME", + "MI", + "MN", + "MO", + "MS", + "MT", + "NC", + "ND", + "NE", + "NH", + "NJ", + "NM", + "NV", + "NY", + "OH", + "OK", + "OR", + "PA", + "RI", + "SC", + "SD", + "TN", + "TX", + "UT", + "VA", + "VT", + "WA", + "WI", + "WV", + "WY" + ], + "x-display-value": { + "AK": "AK", + "AL": "AL", + "AR": "AR", + "AZ": "AZ", + "CA": "CA", + "CO": "CO", + "CT": "CT", + "DC": "DC", + "DE": "DE", + "FL": "FL", + "GA": "GA", + "HI": "HI", + "IA": "IA", + "ID": "ID", + "IL": "IL", + "IN": "IN", + "KS": "KS", + "KY": "KY", + "LA": "LA", + "MA": "MA", + "MD": "MD", + "ME": "ME", + "MI": "MI", + "MN": "MN", + "MO": "MO", + "MS": "MS", + "MT": "MT", + "NC": "NC", + "ND": "ND", + "NE": "NE", + "NH": "NH", + "NJ": "NJ", + "NM": "NM", + "NV": "NV", + "NY": "NY", + "OH": "OH", + "OK": "OK", + "OR": "OR", + "PA": "PA", + "RI": "RI", + "SC": "SC", + "SD": "SD", + "TN": "TN", + "TX": "TX", + "UT": "UT", + "VA": "VA", + "VT": "VT", + "WA": "WA", + "WI": "WI", + "WV": "WV", + "WY": "WY" + } + }, + "usPostRegionCitiesID": { + "type": "string", + "format": "uuid", + "example": "c56a4180-65aa-42ec-a945-5fd21dec0538" + } + } + }, + "VLocations": { + "type": "array", + "items": { + "$ref": "#/definitions/VLocation" + } + }, "ValidationError": { "allOf": [ { diff --git a/pkg/gen/primeapi/primeoperations/addresses/get_location_by_zip_city_state.go b/pkg/gen/primeapi/primeoperations/addresses/get_location_by_zip_city_state.go new file mode 100644 index 00000000000..d202a9066f8 --- /dev/null +++ b/pkg/gen/primeapi/primeoperations/addresses/get_location_by_zip_city_state.go @@ -0,0 +1,58 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package addresses + +// 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" +) + +// GetLocationByZipCityStateHandlerFunc turns a function with the right signature into a get location by zip city state handler +type GetLocationByZipCityStateHandlerFunc func(GetLocationByZipCityStateParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn GetLocationByZipCityStateHandlerFunc) Handle(params GetLocationByZipCityStateParams) middleware.Responder { + return fn(params) +} + +// GetLocationByZipCityStateHandler interface for that can handle valid get location by zip city state params +type GetLocationByZipCityStateHandler interface { + Handle(GetLocationByZipCityStateParams) middleware.Responder +} + +// NewGetLocationByZipCityState creates a new http.Handler for the get location by zip city state operation +func NewGetLocationByZipCityState(ctx *middleware.Context, handler GetLocationByZipCityStateHandler) *GetLocationByZipCityState { + return &GetLocationByZipCityState{Context: ctx, Handler: handler} +} + +/* + GetLocationByZipCityState swagger:route GET /addresses/zip-city-lookup/{search} addresses getLocationByZipCityState + +Returns city, state, postal code, and county associated with the specified full/partial postal code or city state string + +Find by API using full/partial postal code or city name that returns an us_post_region_cities json object containing city, state, county and postal code. +*/ +type GetLocationByZipCityState struct { + Context *middleware.Context + Handler GetLocationByZipCityStateHandler +} + +func (o *GetLocationByZipCityState) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewGetLocationByZipCityStateParams() + 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/primeapi/primeoperations/addresses/get_location_by_zip_city_state_parameters.go b/pkg/gen/primeapi/primeoperations/addresses/get_location_by_zip_city_state_parameters.go new file mode 100644 index 00000000000..0e8106fb581 --- /dev/null +++ b/pkg/gen/primeapi/primeoperations/addresses/get_location_by_zip_city_state_parameters.go @@ -0,0 +1,71 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package addresses + +// 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" +) + +// NewGetLocationByZipCityStateParams creates a new GetLocationByZipCityStateParams object +// +// There are no default values defined in the spec. +func NewGetLocationByZipCityStateParams() GetLocationByZipCityStateParams { + + return GetLocationByZipCityStateParams{} +} + +// GetLocationByZipCityStateParams contains all the bound params for the get location by zip city state operation +// typically these are obtained from a http.Request +// +// swagger:parameters getLocationByZipCityState +type GetLocationByZipCityStateParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /* + Required: true + In: path + */ + Search string +} + +// 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 NewGetLocationByZipCityStateParams() beforehand. +func (o *GetLocationByZipCityStateParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + rSearch, rhkSearch, _ := route.Params.GetOK("search") + if err := o.bindSearch(rSearch, rhkSearch, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindSearch binds and validates parameter Search from path. +func (o *GetLocationByZipCityStateParams) bindSearch(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 + o.Search = raw + + return nil +} diff --git a/pkg/gen/primeapi/primeoperations/addresses/get_location_by_zip_city_state_responses.go b/pkg/gen/primeapi/primeoperations/addresses/get_location_by_zip_city_state_responses.go new file mode 100644 index 00000000000..96eca32d7a9 --- /dev/null +++ b/pkg/gen/primeapi/primeoperations/addresses/get_location_by_zip_city_state_responses.go @@ -0,0 +1,242 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package addresses + +// 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/primemessages" +) + +// GetLocationByZipCityStateOKCode is the HTTP code returned for type GetLocationByZipCityStateOK +const GetLocationByZipCityStateOKCode int = 200 + +/* +GetLocationByZipCityStateOK the requested list of city, state, county, and postal code matches + +swagger:response getLocationByZipCityStateOK +*/ +type GetLocationByZipCityStateOK struct { + + /* + In: Body + */ + Payload primemessages.VLocations `json:"body,omitempty"` +} + +// NewGetLocationByZipCityStateOK creates GetLocationByZipCityStateOK with default headers values +func NewGetLocationByZipCityStateOK() *GetLocationByZipCityStateOK { + + return &GetLocationByZipCityStateOK{} +} + +// WithPayload adds the payload to the get location by zip city state o k response +func (o *GetLocationByZipCityStateOK) WithPayload(payload primemessages.VLocations) *GetLocationByZipCityStateOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get location by zip city state o k response +func (o *GetLocationByZipCityStateOK) SetPayload(payload primemessages.VLocations) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetLocationByZipCityStateOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + payload := o.Payload + if payload == nil { + // return empty array + payload = primemessages.VLocations{} + } + + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } +} + +// GetLocationByZipCityStateBadRequestCode is the HTTP code returned for type GetLocationByZipCityStateBadRequest +const GetLocationByZipCityStateBadRequestCode int = 400 + +/* +GetLocationByZipCityStateBadRequest The request payload is invalid. + +swagger:response getLocationByZipCityStateBadRequest +*/ +type GetLocationByZipCityStateBadRequest struct { + + /* + In: Body + */ + Payload *primemessages.ClientError `json:"body,omitempty"` +} + +// NewGetLocationByZipCityStateBadRequest creates GetLocationByZipCityStateBadRequest with default headers values +func NewGetLocationByZipCityStateBadRequest() *GetLocationByZipCityStateBadRequest { + + return &GetLocationByZipCityStateBadRequest{} +} + +// WithPayload adds the payload to the get location by zip city state bad request response +func (o *GetLocationByZipCityStateBadRequest) WithPayload(payload *primemessages.ClientError) *GetLocationByZipCityStateBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get location by zip city state bad request response +func (o *GetLocationByZipCityStateBadRequest) SetPayload(payload *primemessages.ClientError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetLocationByZipCityStateBadRequest) 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 + } + } +} + +// GetLocationByZipCityStateForbiddenCode is the HTTP code returned for type GetLocationByZipCityStateForbidden +const GetLocationByZipCityStateForbiddenCode int = 403 + +/* +GetLocationByZipCityStateForbidden The request was denied. + +swagger:response getLocationByZipCityStateForbidden +*/ +type GetLocationByZipCityStateForbidden struct { + + /* + In: Body + */ + Payload *primemessages.ClientError `json:"body,omitempty"` +} + +// NewGetLocationByZipCityStateForbidden creates GetLocationByZipCityStateForbidden with default headers values +func NewGetLocationByZipCityStateForbidden() *GetLocationByZipCityStateForbidden { + + return &GetLocationByZipCityStateForbidden{} +} + +// WithPayload adds the payload to the get location by zip city state forbidden response +func (o *GetLocationByZipCityStateForbidden) WithPayload(payload *primemessages.ClientError) *GetLocationByZipCityStateForbidden { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get location by zip city state forbidden response +func (o *GetLocationByZipCityStateForbidden) SetPayload(payload *primemessages.ClientError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetLocationByZipCityStateForbidden) 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 + } + } +} + +// GetLocationByZipCityStateNotFoundCode is the HTTP code returned for type GetLocationByZipCityStateNotFound +const GetLocationByZipCityStateNotFoundCode int = 404 + +/* +GetLocationByZipCityStateNotFound The requested resource wasn't found. + +swagger:response getLocationByZipCityStateNotFound +*/ +type GetLocationByZipCityStateNotFound struct { + + /* + In: Body + */ + Payload *primemessages.ClientError `json:"body,omitempty"` +} + +// NewGetLocationByZipCityStateNotFound creates GetLocationByZipCityStateNotFound with default headers values +func NewGetLocationByZipCityStateNotFound() *GetLocationByZipCityStateNotFound { + + return &GetLocationByZipCityStateNotFound{} +} + +// WithPayload adds the payload to the get location by zip city state not found response +func (o *GetLocationByZipCityStateNotFound) WithPayload(payload *primemessages.ClientError) *GetLocationByZipCityStateNotFound { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get location by zip city state not found response +func (o *GetLocationByZipCityStateNotFound) SetPayload(payload *primemessages.ClientError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetLocationByZipCityStateNotFound) 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 + } + } +} + +// GetLocationByZipCityStateInternalServerErrorCode is the HTTP code returned for type GetLocationByZipCityStateInternalServerError +const GetLocationByZipCityStateInternalServerErrorCode int = 500 + +/* +GetLocationByZipCityStateInternalServerError A server error occurred. + +swagger:response getLocationByZipCityStateInternalServerError +*/ +type GetLocationByZipCityStateInternalServerError struct { + + /* + In: Body + */ + Payload *primemessages.Error `json:"body,omitempty"` +} + +// NewGetLocationByZipCityStateInternalServerError creates GetLocationByZipCityStateInternalServerError with default headers values +func NewGetLocationByZipCityStateInternalServerError() *GetLocationByZipCityStateInternalServerError { + + return &GetLocationByZipCityStateInternalServerError{} +} + +// WithPayload adds the payload to the get location by zip city state internal server error response +func (o *GetLocationByZipCityStateInternalServerError) WithPayload(payload *primemessages.Error) *GetLocationByZipCityStateInternalServerError { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the get location by zip city state internal server error response +func (o *GetLocationByZipCityStateInternalServerError) SetPayload(payload *primemessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *GetLocationByZipCityStateInternalServerError) 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/primeapi/primeoperations/addresses/get_location_by_zip_city_state_urlbuilder.go b/pkg/gen/primeapi/primeoperations/addresses/get_location_by_zip_city_state_urlbuilder.go new file mode 100644 index 00000000000..1ea3bc879de --- /dev/null +++ b/pkg/gen/primeapi/primeoperations/addresses/get_location_by_zip_city_state_urlbuilder.go @@ -0,0 +1,99 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package addresses + +// 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" +) + +// GetLocationByZipCityStateURL generates an URL for the get location by zip city state operation +type GetLocationByZipCityStateURL struct { + Search string + + _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 *GetLocationByZipCityStateURL) WithBasePath(bp string) *GetLocationByZipCityStateURL { + 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 *GetLocationByZipCityStateURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *GetLocationByZipCityStateURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/addresses/zip-city-lookup/{search}" + + search := o.Search + if search != "" { + _path = strings.Replace(_path, "{search}", search, -1) + } else { + return nil, errors.New("search is required on GetLocationByZipCityStateURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/prime/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 *GetLocationByZipCityStateURL) 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 *GetLocationByZipCityStateURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *GetLocationByZipCityStateURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on GetLocationByZipCityStateURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on GetLocationByZipCityStateURL") + } + + 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 *GetLocationByZipCityStateURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/pkg/gen/primeapi/primeoperations/mto_service_item/create_m_t_o_service_item.go b/pkg/gen/primeapi/primeoperations/mto_service_item/create_m_t_o_service_item.go index 85456161f0c..88adde61f94 100644 --- a/pkg/gen/primeapi/primeoperations/mto_service_item/create_m_t_o_service_item.go +++ b/pkg/gen/primeapi/primeoperations/mto_service_item/create_m_t_o_service_item.go @@ -121,6 +121,81 @@ When a DDFSIT is requested, the API will auto-create the following group of serv **Addt'l days destination SIT service item**. This represents an additional day of storage for the same item. Additional DDASIT service items can be created and added to an existing shipment that **includes a DDFSIT service item**. + +--- + +**`MTOServiceItemInternationalOriginSIT`** + +MTOServiceItemInternationalOriginSIT is a subtype of MTOServiceItem. + +This model type describes a international origin SIT service item. Items can be created using this +model type with the following codes: + +**IOFSIT** + +**1st day origin SIT service item**. When a IOFSIT is requested, the API will auto-create the following group of service items: + - IOFSIT - International origin 1st day SIT + - IOASIT - International origin Additional day SIT + - IOPSIT - International origin SIT pickup + - IOSFSC - International origin SIT fuel surcharge + +**IOASIT** + +**Addt'l days origin SIT service item**. This represents an additional day of storage for the same item. +Additional IOASIT service items can be created and added to an existing shipment that **includes a IOFSIT service item**. + +--- + +**`MTOServiceItemInternationalDestSIT`** + +MTOServiceItemInternationalDestSIT is a subtype of MTOServiceItem. + +This model type describes a international destination SIT service item. Items can be created using this +model type with the following codes: + +**IDFSIT** + +**1st day destination SIT service item**. + +These additional fields are optional for creating a IDFSIT: + - `firstAvailableDeliveryDate1` + - string + - First available date that Prime can deliver SIT service item. + - firstAvailableDeliveryDate1, dateOfContact1, and timeMilitary1 are required together + - `dateOfContact1` + - string + - Date of attempted contact by the prime corresponding to `timeMilitary1` + - dateOfContact1, timeMilitary1, and firstAvailableDeliveryDate1 are required together + - `timeMilitary1` + - string\d{4}Z + - Time of attempted contact corresponding to `dateOfContact1`, in military format. + - timeMilitary1, dateOfContact1, and firstAvailableDeliveryDate1 are required together + - `firstAvailableDeliveryDate2` + - string + - Second available date that Prime can deliver SIT service item. + - firstAvailableDeliveryDate2, dateOfContact2, and timeMilitary2 are required together + - `dateOfContact2` + - string + - Date of attempted contact delivery by the prime corresponding to `timeMilitary2` + - dateOfContact2, timeMilitary2, and firstAvailableDeliveryDate2 are required together + - `timeMilitary2` + - string\d{4}Z + - Time of attempted contact corresponding to `dateOfContact2`, in military format. + - timeMilitary2, dateOfContact2, and firstAvailableDeliveryDate2 are required together + +When a IDFSIT is requested, the API will auto-create the following group of service items: + - IDFSIT - International destination 1st day SIT + - IDASIT - International destination Additional day SIT + - IDDSIT - International destination SIT delivery + - IDSFSC - International destination SIT fuel surcharge + +**NOTE** When providing the `sitEntryDate` value in the payload, please ensure that the date is not BEFORE +`firstAvailableDeliveryDate1` or `firstAvailableDeliveryDate2`. If it is, you will receive an error response. + +**IDASIT** + +**Addt'l days destination SIT service item**. This represents an additional day of storage for the same item. +Additional IDASIT service items can be created and added to an existing shipment that **includes a IDFSIT service item**. */ type CreateMTOServiceItem struct { Context *middleware.Context diff --git a/pkg/gen/primeapi/primeoperations/mto_service_item/update_m_t_o_service_item.go b/pkg/gen/primeapi/primeoperations/mto_service_item/update_m_t_o_service_item.go index 8bfdb75c0f7..c6215639acd 100644 --- a/pkg/gen/primeapi/primeoperations/mto_service_item/update_m_t_o_service_item.go +++ b/pkg/gen/primeapi/primeoperations/mto_service_item/update_m_t_o_service_item.go @@ -64,6 +64,14 @@ The following SIT service items can be resubmitted following a rejection: - DOFSIT - DDSFSC - DOSFSC +- IDASIT +- IDDSIT +- IDFSIT +- IOASIT +- IOPSIT +- IOFSIT +- IDSFSC +- IOSFSC The following Accessorial service items can be resubmitted following a rejection: - IOSHUT @@ -96,6 +104,31 @@ Please see the example payload below: "reServiceCode": "POEFSC" } +``` + +The following crating/uncrating service items can be resubmitted following a rejection: +- ICRT +- IUCRT + +At a MINIMUM, the payload for resubmitting a rejected crating/uncrating service item must look like this: +```json + + { + "item": { + "length": 10000, + "width": 10000, + "height": 10000 + }, + "crate": { + "length": 20000, + "width": 20000, + "height": 20000 + }, + "updateReason": "A reason that differs from the previous reason", + "modelType": "UpdateMTOServiceItemCrating", + "requestApprovalsRequestedStatus": true + } + ``` */ type UpdateMTOServiceItem struct { diff --git a/pkg/gen/primeapi/primeoperations/mymove_api.go b/pkg/gen/primeapi/primeoperations/mymove_api.go index b9e44b2190c..6ded41a6c0d 100644 --- a/pkg/gen/primeapi/primeoperations/mymove_api.go +++ b/pkg/gen/primeapi/primeoperations/mymove_api.go @@ -19,6 +19,7 @@ import ( "github.com/go-openapi/strfmt" "github.com/go-openapi/swag" + "github.com/transcom/mymove/pkg/gen/primeapi/primeoperations/addresses" "github.com/transcom/mymove/pkg/gen/primeapi/primeoperations/move_task_order" "github.com/transcom/mymove/pkg/gen/primeapi/primeoperations/mto_service_item" "github.com/transcom/mymove/pkg/gen/primeapi/primeoperations/mto_shipment" @@ -79,6 +80,9 @@ func NewMymoveAPI(spec *loads.Document) *MymoveAPI { MoveTaskOrderDownloadMoveOrderHandler: move_task_order.DownloadMoveOrderHandlerFunc(func(params move_task_order.DownloadMoveOrderParams) middleware.Responder { return middleware.NotImplemented("operation move_task_order.DownloadMoveOrder has not yet been implemented") }), + AddressesGetLocationByZipCityStateHandler: addresses.GetLocationByZipCityStateHandlerFunc(func(params addresses.GetLocationByZipCityStateParams) middleware.Responder { + return middleware.NotImplemented("operation addresses.GetLocationByZipCityState has not yet been implemented") + }), MoveTaskOrderGetMoveTaskOrderHandler: move_task_order.GetMoveTaskOrderHandlerFunc(func(params move_task_order.GetMoveTaskOrderParams) middleware.Responder { return middleware.NotImplemented("operation move_task_order.GetMoveTaskOrder has not yet been implemented") }), @@ -177,6 +181,8 @@ type MymoveAPI struct { MtoShipmentDeleteMTOShipmentHandler mto_shipment.DeleteMTOShipmentHandler // MoveTaskOrderDownloadMoveOrderHandler sets the operation handler for the download move order operation MoveTaskOrderDownloadMoveOrderHandler move_task_order.DownloadMoveOrderHandler + // AddressesGetLocationByZipCityStateHandler sets the operation handler for the get location by zip city state operation + AddressesGetLocationByZipCityStateHandler addresses.GetLocationByZipCityStateHandler // MoveTaskOrderGetMoveTaskOrderHandler sets the operation handler for the get move task order operation MoveTaskOrderGetMoveTaskOrderHandler move_task_order.GetMoveTaskOrderHandler // MoveTaskOrderListMovesHandler sets the operation handler for the list moves operation @@ -310,6 +316,9 @@ func (o *MymoveAPI) Validate() error { if o.MoveTaskOrderDownloadMoveOrderHandler == nil { unregistered = append(unregistered, "move_task_order.DownloadMoveOrderHandler") } + if o.AddressesGetLocationByZipCityStateHandler == nil { + unregistered = append(unregistered, "addresses.GetLocationByZipCityStateHandler") + } if o.MoveTaskOrderGetMoveTaskOrderHandler == nil { unregistered = append(unregistered, "move_task_order.GetMoveTaskOrderHandler") } @@ -475,6 +484,10 @@ func (o *MymoveAPI) initHandlerCache() { if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) } + o.handlers["GET"]["/addresses/zip-city-lookup/{search}"] = addresses.NewGetLocationByZipCityState(o.context, o.AddressesGetLocationByZipCityStateHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } o.handlers["GET"]["/move-task-orders/{moveID}"] = move_task_order.NewGetMoveTaskOrder(o.context, o.MoveTaskOrderGetMoveTaskOrderHandler) if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) diff --git a/pkg/gen/primeclient/addresses/addresses_client.go b/pkg/gen/primeclient/addresses/addresses_client.go new file mode 100644 index 00000000000..64fddbf9f02 --- /dev/null +++ b/pkg/gen/primeclient/addresses/addresses_client.go @@ -0,0 +1,81 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package addresses + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "fmt" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" +) + +// New creates a new addresses API client. +func New(transport runtime.ClientTransport, formats strfmt.Registry) ClientService { + return &Client{transport: transport, formats: formats} +} + +/* +Client for addresses API +*/ +type Client struct { + transport runtime.ClientTransport + formats strfmt.Registry +} + +// ClientOption is the option for Client methods +type ClientOption func(*runtime.ClientOperation) + +// ClientService is the interface for Client methods +type ClientService interface { + GetLocationByZipCityState(params *GetLocationByZipCityStateParams, opts ...ClientOption) (*GetLocationByZipCityStateOK, error) + + SetTransport(transport runtime.ClientTransport) +} + +/* +GetLocationByZipCityState returns city state postal code and county associated with the specified full partial postal code or city state string + +Find by API using full/partial postal code or city name that returns an us_post_region_cities json object containing city, state, county and postal code. +*/ +func (a *Client) GetLocationByZipCityState(params *GetLocationByZipCityStateParams, opts ...ClientOption) (*GetLocationByZipCityStateOK, error) { + // TODO: Validate the params before sending + if params == nil { + params = NewGetLocationByZipCityStateParams() + } + op := &runtime.ClientOperation{ + ID: "getLocationByZipCityState", + Method: "GET", + PathPattern: "/addresses/zip-city-lookup/{search}", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"http"}, + Params: params, + Reader: &GetLocationByZipCityStateReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + success, ok := result.(*GetLocationByZipCityStateOK) + if ok { + return success, nil + } + // unexpected success response + // safeguard: normally, absent a default response, unknown success responses return an error above: so this is a codegen issue + msg := fmt.Sprintf("unexpected success response for getLocationByZipCityState: API contract not enforced by server. Client expected to get an error, but got: %T", result) + panic(msg) +} + +// SetTransport changes the transport on the client +func (a *Client) SetTransport(transport runtime.ClientTransport) { + a.transport = transport +} diff --git a/pkg/gen/primeclient/addresses/get_location_by_zip_city_state_parameters.go b/pkg/gen/primeclient/addresses/get_location_by_zip_city_state_parameters.go new file mode 100644 index 00000000000..494619925b4 --- /dev/null +++ b/pkg/gen/primeclient/addresses/get_location_by_zip_city_state_parameters.go @@ -0,0 +1,148 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package addresses + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" +) + +// NewGetLocationByZipCityStateParams creates a new GetLocationByZipCityStateParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewGetLocationByZipCityStateParams() *GetLocationByZipCityStateParams { + return &GetLocationByZipCityStateParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewGetLocationByZipCityStateParamsWithTimeout creates a new GetLocationByZipCityStateParams object +// with the ability to set a timeout on a request. +func NewGetLocationByZipCityStateParamsWithTimeout(timeout time.Duration) *GetLocationByZipCityStateParams { + return &GetLocationByZipCityStateParams{ + timeout: timeout, + } +} + +// NewGetLocationByZipCityStateParamsWithContext creates a new GetLocationByZipCityStateParams object +// with the ability to set a context for a request. +func NewGetLocationByZipCityStateParamsWithContext(ctx context.Context) *GetLocationByZipCityStateParams { + return &GetLocationByZipCityStateParams{ + Context: ctx, + } +} + +// NewGetLocationByZipCityStateParamsWithHTTPClient creates a new GetLocationByZipCityStateParams object +// with the ability to set a custom HTTPClient for a request. +func NewGetLocationByZipCityStateParamsWithHTTPClient(client *http.Client) *GetLocationByZipCityStateParams { + return &GetLocationByZipCityStateParams{ + HTTPClient: client, + } +} + +/* +GetLocationByZipCityStateParams contains all the parameters to send to the API endpoint + + for the get location by zip city state operation. + + Typically these are written to a http.Request. +*/ +type GetLocationByZipCityStateParams struct { + + // Search. + Search string + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the get location by zip city state params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *GetLocationByZipCityStateParams) WithDefaults() *GetLocationByZipCityStateParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the get location by zip city state params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *GetLocationByZipCityStateParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the get location by zip city state params +func (o *GetLocationByZipCityStateParams) WithTimeout(timeout time.Duration) *GetLocationByZipCityStateParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the get location by zip city state params +func (o *GetLocationByZipCityStateParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the get location by zip city state params +func (o *GetLocationByZipCityStateParams) WithContext(ctx context.Context) *GetLocationByZipCityStateParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the get location by zip city state params +func (o *GetLocationByZipCityStateParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the get location by zip city state params +func (o *GetLocationByZipCityStateParams) WithHTTPClient(client *http.Client) *GetLocationByZipCityStateParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the get location by zip city state params +func (o *GetLocationByZipCityStateParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithSearch adds the search to the get location by zip city state params +func (o *GetLocationByZipCityStateParams) WithSearch(search string) *GetLocationByZipCityStateParams { + o.SetSearch(search) + return o +} + +// SetSearch adds the search to the get location by zip city state params +func (o *GetLocationByZipCityStateParams) SetSearch(search string) { + o.Search = search +} + +// WriteToRequest writes these params to a swagger request +func (o *GetLocationByZipCityStateParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + + // path param search + if err := r.SetPathParam("search", o.Search); err != nil { + return err + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/pkg/gen/primeclient/addresses/get_location_by_zip_city_state_responses.go b/pkg/gen/primeclient/addresses/get_location_by_zip_city_state_responses.go new file mode 100644 index 00000000000..a077d9cc5d5 --- /dev/null +++ b/pkg/gen/primeclient/addresses/get_location_by_zip_city_state_responses.go @@ -0,0 +1,397 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package addresses + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "fmt" + "io" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + + "github.com/transcom/mymove/pkg/gen/primemessages" +) + +// GetLocationByZipCityStateReader is a Reader for the GetLocationByZipCityState structure. +type GetLocationByZipCityStateReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *GetLocationByZipCityStateReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (interface{}, error) { + switch response.Code() { + case 200: + result := NewGetLocationByZipCityStateOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + case 400: + result := NewGetLocationByZipCityStateBadRequest() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + case 403: + result := NewGetLocationByZipCityStateForbidden() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + case 404: + result := NewGetLocationByZipCityStateNotFound() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + case 500: + result := NewGetLocationByZipCityStateInternalServerError() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + default: + return nil, runtime.NewAPIError("[GET /addresses/zip-city-lookup/{search}] getLocationByZipCityState", response, response.Code()) + } +} + +// NewGetLocationByZipCityStateOK creates a GetLocationByZipCityStateOK with default headers values +func NewGetLocationByZipCityStateOK() *GetLocationByZipCityStateOK { + return &GetLocationByZipCityStateOK{} +} + +/* +GetLocationByZipCityStateOK describes a response with status code 200, with default header values. + +the requested list of city, state, county, and postal code matches +*/ +type GetLocationByZipCityStateOK struct { + Payload primemessages.VLocations +} + +// IsSuccess returns true when this get location by zip city state o k response has a 2xx status code +func (o *GetLocationByZipCityStateOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this get location by zip city state o k response has a 3xx status code +func (o *GetLocationByZipCityStateOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this get location by zip city state o k response has a 4xx status code +func (o *GetLocationByZipCityStateOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this get location by zip city state o k response has a 5xx status code +func (o *GetLocationByZipCityStateOK) IsServerError() bool { + return false +} + +// IsCode returns true when this get location by zip city state o k response a status code equal to that given +func (o *GetLocationByZipCityStateOK) IsCode(code int) bool { + return code == 200 +} + +// Code gets the status code for the get location by zip city state o k response +func (o *GetLocationByZipCityStateOK) Code() int { + return 200 +} + +func (o *GetLocationByZipCityStateOK) Error() string { + return fmt.Sprintf("[GET /addresses/zip-city-lookup/{search}][%d] getLocationByZipCityStateOK %+v", 200, o.Payload) +} + +func (o *GetLocationByZipCityStateOK) String() string { + return fmt.Sprintf("[GET /addresses/zip-city-lookup/{search}][%d] getLocationByZipCityStateOK %+v", 200, o.Payload) +} + +func (o *GetLocationByZipCityStateOK) GetPayload() primemessages.VLocations { + return o.Payload +} + +func (o *GetLocationByZipCityStateOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + // response payload + if err := consumer.Consume(response.Body(), &o.Payload); err != nil && err != io.EOF { + return err + } + + return nil +} + +// NewGetLocationByZipCityStateBadRequest creates a GetLocationByZipCityStateBadRequest with default headers values +func NewGetLocationByZipCityStateBadRequest() *GetLocationByZipCityStateBadRequest { + return &GetLocationByZipCityStateBadRequest{} +} + +/* +GetLocationByZipCityStateBadRequest describes a response with status code 400, with default header values. + +The request payload is invalid. +*/ +type GetLocationByZipCityStateBadRequest struct { + Payload *primemessages.ClientError +} + +// IsSuccess returns true when this get location by zip city state bad request response has a 2xx status code +func (o *GetLocationByZipCityStateBadRequest) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this get location by zip city state bad request response has a 3xx status code +func (o *GetLocationByZipCityStateBadRequest) IsRedirect() bool { + return false +} + +// IsClientError returns true when this get location by zip city state bad request response has a 4xx status code +func (o *GetLocationByZipCityStateBadRequest) IsClientError() bool { + return true +} + +// IsServerError returns true when this get location by zip city state bad request response has a 5xx status code +func (o *GetLocationByZipCityStateBadRequest) IsServerError() bool { + return false +} + +// IsCode returns true when this get location by zip city state bad request response a status code equal to that given +func (o *GetLocationByZipCityStateBadRequest) IsCode(code int) bool { + return code == 400 +} + +// Code gets the status code for the get location by zip city state bad request response +func (o *GetLocationByZipCityStateBadRequest) Code() int { + return 400 +} + +func (o *GetLocationByZipCityStateBadRequest) Error() string { + return fmt.Sprintf("[GET /addresses/zip-city-lookup/{search}][%d] getLocationByZipCityStateBadRequest %+v", 400, o.Payload) +} + +func (o *GetLocationByZipCityStateBadRequest) String() string { + return fmt.Sprintf("[GET /addresses/zip-city-lookup/{search}][%d] getLocationByZipCityStateBadRequest %+v", 400, o.Payload) +} + +func (o *GetLocationByZipCityStateBadRequest) GetPayload() *primemessages.ClientError { + return o.Payload +} + +func (o *GetLocationByZipCityStateBadRequest) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(primemessages.ClientError) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF { + return err + } + + return nil +} + +// NewGetLocationByZipCityStateForbidden creates a GetLocationByZipCityStateForbidden with default headers values +func NewGetLocationByZipCityStateForbidden() *GetLocationByZipCityStateForbidden { + return &GetLocationByZipCityStateForbidden{} +} + +/* +GetLocationByZipCityStateForbidden describes a response with status code 403, with default header values. + +The request was denied. +*/ +type GetLocationByZipCityStateForbidden struct { + Payload *primemessages.ClientError +} + +// IsSuccess returns true when this get location by zip city state forbidden response has a 2xx status code +func (o *GetLocationByZipCityStateForbidden) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this get location by zip city state forbidden response has a 3xx status code +func (o *GetLocationByZipCityStateForbidden) IsRedirect() bool { + return false +} + +// IsClientError returns true when this get location by zip city state forbidden response has a 4xx status code +func (o *GetLocationByZipCityStateForbidden) IsClientError() bool { + return true +} + +// IsServerError returns true when this get location by zip city state forbidden response has a 5xx status code +func (o *GetLocationByZipCityStateForbidden) IsServerError() bool { + return false +} + +// IsCode returns true when this get location by zip city state forbidden response a status code equal to that given +func (o *GetLocationByZipCityStateForbidden) IsCode(code int) bool { + return code == 403 +} + +// Code gets the status code for the get location by zip city state forbidden response +func (o *GetLocationByZipCityStateForbidden) Code() int { + return 403 +} + +func (o *GetLocationByZipCityStateForbidden) Error() string { + return fmt.Sprintf("[GET /addresses/zip-city-lookup/{search}][%d] getLocationByZipCityStateForbidden %+v", 403, o.Payload) +} + +func (o *GetLocationByZipCityStateForbidden) String() string { + return fmt.Sprintf("[GET /addresses/zip-city-lookup/{search}][%d] getLocationByZipCityStateForbidden %+v", 403, o.Payload) +} + +func (o *GetLocationByZipCityStateForbidden) GetPayload() *primemessages.ClientError { + return o.Payload +} + +func (o *GetLocationByZipCityStateForbidden) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(primemessages.ClientError) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF { + return err + } + + return nil +} + +// NewGetLocationByZipCityStateNotFound creates a GetLocationByZipCityStateNotFound with default headers values +func NewGetLocationByZipCityStateNotFound() *GetLocationByZipCityStateNotFound { + return &GetLocationByZipCityStateNotFound{} +} + +/* +GetLocationByZipCityStateNotFound describes a response with status code 404, with default header values. + +The requested resource wasn't found. +*/ +type GetLocationByZipCityStateNotFound struct { + Payload *primemessages.ClientError +} + +// IsSuccess returns true when this get location by zip city state not found response has a 2xx status code +func (o *GetLocationByZipCityStateNotFound) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this get location by zip city state not found response has a 3xx status code +func (o *GetLocationByZipCityStateNotFound) IsRedirect() bool { + return false +} + +// IsClientError returns true when this get location by zip city state not found response has a 4xx status code +func (o *GetLocationByZipCityStateNotFound) IsClientError() bool { + return true +} + +// IsServerError returns true when this get location by zip city state not found response has a 5xx status code +func (o *GetLocationByZipCityStateNotFound) IsServerError() bool { + return false +} + +// IsCode returns true when this get location by zip city state not found response a status code equal to that given +func (o *GetLocationByZipCityStateNotFound) IsCode(code int) bool { + return code == 404 +} + +// Code gets the status code for the get location by zip city state not found response +func (o *GetLocationByZipCityStateNotFound) Code() int { + return 404 +} + +func (o *GetLocationByZipCityStateNotFound) Error() string { + return fmt.Sprintf("[GET /addresses/zip-city-lookup/{search}][%d] getLocationByZipCityStateNotFound %+v", 404, o.Payload) +} + +func (o *GetLocationByZipCityStateNotFound) String() string { + return fmt.Sprintf("[GET /addresses/zip-city-lookup/{search}][%d] getLocationByZipCityStateNotFound %+v", 404, o.Payload) +} + +func (o *GetLocationByZipCityStateNotFound) GetPayload() *primemessages.ClientError { + return o.Payload +} + +func (o *GetLocationByZipCityStateNotFound) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(primemessages.ClientError) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF { + return err + } + + return nil +} + +// NewGetLocationByZipCityStateInternalServerError creates a GetLocationByZipCityStateInternalServerError with default headers values +func NewGetLocationByZipCityStateInternalServerError() *GetLocationByZipCityStateInternalServerError { + return &GetLocationByZipCityStateInternalServerError{} +} + +/* +GetLocationByZipCityStateInternalServerError describes a response with status code 500, with default header values. + +A server error occurred. +*/ +type GetLocationByZipCityStateInternalServerError struct { + Payload *primemessages.Error +} + +// IsSuccess returns true when this get location by zip city state internal server error response has a 2xx status code +func (o *GetLocationByZipCityStateInternalServerError) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this get location by zip city state internal server error response has a 3xx status code +func (o *GetLocationByZipCityStateInternalServerError) IsRedirect() bool { + return false +} + +// IsClientError returns true when this get location by zip city state internal server error response has a 4xx status code +func (o *GetLocationByZipCityStateInternalServerError) IsClientError() bool { + return false +} + +// IsServerError returns true when this get location by zip city state internal server error response has a 5xx status code +func (o *GetLocationByZipCityStateInternalServerError) IsServerError() bool { + return true +} + +// IsCode returns true when this get location by zip city state internal server error response a status code equal to that given +func (o *GetLocationByZipCityStateInternalServerError) IsCode(code int) bool { + return code == 500 +} + +// Code gets the status code for the get location by zip city state internal server error response +func (o *GetLocationByZipCityStateInternalServerError) Code() int { + return 500 +} + +func (o *GetLocationByZipCityStateInternalServerError) Error() string { + return fmt.Sprintf("[GET /addresses/zip-city-lookup/{search}][%d] getLocationByZipCityStateInternalServerError %+v", 500, o.Payload) +} + +func (o *GetLocationByZipCityStateInternalServerError) String() string { + return fmt.Sprintf("[GET /addresses/zip-city-lookup/{search}][%d] getLocationByZipCityStateInternalServerError %+v", 500, o.Payload) +} + +func (o *GetLocationByZipCityStateInternalServerError) GetPayload() *primemessages.Error { + return o.Payload +} + +func (o *GetLocationByZipCityStateInternalServerError) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + o.Payload = new(primemessages.Error) + + // response payload + if err := consumer.Consume(response.Body(), o.Payload); err != nil && err != io.EOF { + return err + } + + return nil +} diff --git a/pkg/gen/primeclient/mto_service_item/mto_service_item_client.go b/pkg/gen/primeclient/mto_service_item/mto_service_item_client.go index 9fe2fa1212d..af261a8501c 100644 --- a/pkg/gen/primeclient/mto_service_item/mto_service_item_client.go +++ b/pkg/gen/primeclient/mto_service_item/mto_service_item_client.go @@ -129,6 +129,81 @@ When a DDFSIT is requested, the API will auto-create the following group of serv **Addt'l days destination SIT service item**. This represents an additional day of storage for the same item. Additional DDASIT service items can be created and added to an existing shipment that **includes a DDFSIT service item**. + +--- + +**`MTOServiceItemInternationalOriginSIT`** + +MTOServiceItemInternationalOriginSIT is a subtype of MTOServiceItem. + +This model type describes a international origin SIT service item. Items can be created using this +model type with the following codes: + +**IOFSIT** + +**1st day origin SIT service item**. When a IOFSIT is requested, the API will auto-create the following group of service items: + - IOFSIT - International origin 1st day SIT + - IOASIT - International origin Additional day SIT + - IOPSIT - International origin SIT pickup + - IOSFSC - International origin SIT fuel surcharge + +**IOASIT** + +**Addt'l days origin SIT service item**. This represents an additional day of storage for the same item. +Additional IOASIT service items can be created and added to an existing shipment that **includes a IOFSIT service item**. + +--- + +**`MTOServiceItemInternationalDestSIT`** + +MTOServiceItemInternationalDestSIT is a subtype of MTOServiceItem. + +This model type describes a international destination SIT service item. Items can be created using this +model type with the following codes: + +**IDFSIT** + +**1st day destination SIT service item**. + +These additional fields are optional for creating a IDFSIT: + - `firstAvailableDeliveryDate1` + - string + - First available date that Prime can deliver SIT service item. + - firstAvailableDeliveryDate1, dateOfContact1, and timeMilitary1 are required together + - `dateOfContact1` + - string + - Date of attempted contact by the prime corresponding to `timeMilitary1` + - dateOfContact1, timeMilitary1, and firstAvailableDeliveryDate1 are required together + - `timeMilitary1` + - string\d{4}Z + - Time of attempted contact corresponding to `dateOfContact1`, in military format. + - timeMilitary1, dateOfContact1, and firstAvailableDeliveryDate1 are required together + - `firstAvailableDeliveryDate2` + - string + - Second available date that Prime can deliver SIT service item. + - firstAvailableDeliveryDate2, dateOfContact2, and timeMilitary2 are required together + - `dateOfContact2` + - string + - Date of attempted contact delivery by the prime corresponding to `timeMilitary2` + - dateOfContact2, timeMilitary2, and firstAvailableDeliveryDate2 are required together + - `timeMilitary2` + - string\d{4}Z + - Time of attempted contact corresponding to `dateOfContact2`, in military format. + - timeMilitary2, dateOfContact2, and firstAvailableDeliveryDate2 are required together + +When a IDFSIT is requested, the API will auto-create the following group of service items: + - IDFSIT - International destination 1st day SIT + - IDASIT - International destination Additional day SIT + - IDDSIT - International destination SIT delivery + - IDSFSC - International destination SIT fuel surcharge + +**NOTE** When providing the `sitEntryDate` value in the payload, please ensure that the date is not BEFORE +`firstAvailableDeliveryDate1` or `firstAvailableDeliveryDate2`. If it is, you will receive an error response. + +**IDASIT** + +**Addt'l days destination SIT service item**. This represents an additional day of storage for the same item. +Additional IDASIT service items can be created and added to an existing shipment that **includes a IDFSIT service item**. */ func (a *Client) CreateMTOServiceItem(params *CreateMTOServiceItemParams, opts ...ClientOption) (*CreateMTOServiceItemOK, error) { // TODO: Validate the params before sending @@ -247,6 +322,14 @@ The following SIT service items can be resubmitted following a rejection: - DOFSIT - DDSFSC - DOSFSC +- IDASIT +- IDDSIT +- IDFSIT +- IOASIT +- IOPSIT +- IOFSIT +- IDSFSC +- IOSFSC The following Accessorial service items can be resubmitted following a rejection: - IOSHUT @@ -279,6 +362,31 @@ Please see the example payload below: "reServiceCode": "POEFSC" } +``` + +The following crating/uncrating service items can be resubmitted following a rejection: +- ICRT +- IUCRT + +At a MINIMUM, the payload for resubmitting a rejected crating/uncrating service item must look like this: +```json + + { + "item": { + "length": 10000, + "width": 10000, + "height": 10000 + }, + "crate": { + "length": 20000, + "width": 20000, + "height": 20000 + }, + "updateReason": "A reason that differs from the previous reason", + "modelType": "UpdateMTOServiceItemCrating", + "requestApprovalsRequestedStatus": true + } + ``` */ func (a *Client) UpdateMTOServiceItem(params *UpdateMTOServiceItemParams, opts ...ClientOption) (*UpdateMTOServiceItemOK, error) { diff --git a/pkg/gen/primeclient/mymove_client.go b/pkg/gen/primeclient/mymove_client.go index 5a6cf119393..5f38f83617d 100644 --- a/pkg/gen/primeclient/mymove_client.go +++ b/pkg/gen/primeclient/mymove_client.go @@ -10,6 +10,7 @@ import ( httptransport "github.com/go-openapi/runtime/client" "github.com/go-openapi/strfmt" + "github.com/transcom/mymove/pkg/gen/primeclient/addresses" "github.com/transcom/mymove/pkg/gen/primeclient/move_task_order" "github.com/transcom/mymove/pkg/gen/primeclient/mto_service_item" "github.com/transcom/mymove/pkg/gen/primeclient/mto_shipment" @@ -58,6 +59,7 @@ func New(transport runtime.ClientTransport, formats strfmt.Registry) *Mymove { cli := new(Mymove) cli.Transport = transport + cli.Addresses = addresses.New(transport, formats) cli.MoveTaskOrder = move_task_order.New(transport, formats) cli.MtoServiceItem = mto_service_item.New(transport, formats) cli.MtoShipment = mto_shipment.New(transport, formats) @@ -106,6 +108,8 @@ func (cfg *TransportConfig) WithSchemes(schemes []string) *TransportConfig { // Mymove is a client for mymove type Mymove struct { + Addresses addresses.ClientService + MoveTaskOrder move_task_order.ClientService MtoServiceItem mto_service_item.ClientService @@ -120,6 +124,7 @@ type Mymove struct { // SetTransport changes the transport on the client and all its subresources func (c *Mymove) SetTransport(transport runtime.ClientTransport) { c.Transport = transport + c.Addresses.SetTransport(transport) c.MoveTaskOrder.SetTransport(transport) c.MtoServiceItem.SetTransport(transport) c.MtoShipment.SetTransport(transport) diff --git a/pkg/gen/primemessages/m_t_o_service_item.go b/pkg/gen/primemessages/m_t_o_service_item.go index 5ed0f248ae0..568ee5954c6 100644 --- a/pkg/gen/primemessages/m_t_o_service_item.go +++ b/pkg/gen/primemessages/m_t_o_service_item.go @@ -273,18 +273,36 @@ 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 { return nil, err } return &result, nil + case "MTOServiceItemInternationalDestSIT": + var result MTOServiceItemInternationalDestSIT + if err := consumer.Consume(buf2, &result); err != nil { + return nil, err + } + return &result, nil case "MTOServiceItemInternationalFuelSurcharge": var result MTOServiceItemInternationalFuelSurcharge if err := consumer.Consume(buf2, &result); err != nil { return nil, err } return &result, nil + case "MTOServiceItemInternationalOriginSIT": + var result MTOServiceItemInternationalOriginSIT + if err := consumer.Consume(buf2, &result); err != nil { + return nil, err + } + return &result, nil case "MTOServiceItemInternationalShuttle": var result MTOServiceItemInternationalShuttle 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_international_dest_s_i_t.go b/pkg/gen/primemessages/m_t_o_service_item_international_dest_s_i_t.go new file mode 100644 index 00000000000..07ab443e34d --- /dev/null +++ b/pkg/gen/primemessages/m_t_o_service_item_international_dest_s_i_t.go @@ -0,0 +1,987 @@ +// 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" +) + +// MTOServiceItemInternationalDestSIT Describes a international destination SIT service item. Subtype of a MTOServiceItem. +// +// swagger:model MTOServiceItemInternationalDestSIT +type MTOServiceItemInternationalDestSIT struct { + eTagField string + + idField strfmt.UUID + + lockedPriceCentsField *int64 + + moveTaskOrderIdField *strfmt.UUID + + mtoShipmentIdField strfmt.UUID + + reServiceNameField string + + rejectionReasonField *string + + serviceRequestDocumentsField ServiceRequestDocuments + + statusField MTOServiceItemStatus + + // Date of attempted contact by the prime corresponding to `timeMilitary1`. + // Format: date + DateOfContact1 *strfmt.Date `json:"dateOfContact1,omitempty"` + + // Date of attempted contact by the prime corresponding to `timeMilitary2`. + // Format: date + DateOfContact2 *strfmt.Date `json:"dateOfContact2,omitempty"` + + // First available date that Prime can deliver SIT service item. + // Format: date + FirstAvailableDeliveryDate1 *strfmt.Date `json:"firstAvailableDeliveryDate1,omitempty"` + + // Second available date that Prime can deliver SIT service item. + // Format: date + FirstAvailableDeliveryDate2 *strfmt.Date `json:"firstAvailableDeliveryDate2,omitempty"` + + // Service code allowed for this model type. + // Required: true + // Enum: [IDFSIT IDASIT] + ReServiceCode *string `json:"reServiceCode"` + + // The reason item has been placed in SIT. + // + // Required: true + Reason *string `json:"reason"` + + // Date when the customer contacted the prime for a delivery out of SIT. + // Format: date + SitCustomerContacted *strfmt.Date `json:"sitCustomerContacted,omitempty"` + + // Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date. + // Format: date + SitDepartureDate *strfmt.Date `json:"sitDepartureDate,omitempty"` + + // sit destination final address + SitDestinationFinalAddress *Address `json:"sitDestinationFinalAddress,omitempty"` + + // Entry date for the SIT + // Required: true + // Format: date + SitEntryDate *strfmt.Date `json:"sitEntryDate"` + + // Date when the customer has requested delivery out of SIT. + // Format: date + SitRequestedDelivery *strfmt.Date `json:"sitRequestedDelivery,omitempty"` + + // Time of attempted contact corresponding to `dateOfContact1`, in military format. + // Example: 1400Z + // Pattern: \d{4}Z + TimeMilitary1 *string `json:"timeMilitary1,omitempty"` + + // Time of attempted contact corresponding to `dateOfContact2`, in military format. + // Example: 1400Z + // Pattern: \d{4}Z + TimeMilitary2 *string `json:"timeMilitary2,omitempty"` +} + +// ETag gets the e tag of this subtype +func (m *MTOServiceItemInternationalDestSIT) ETag() string { + return m.eTagField +} + +// SetETag sets the e tag of this subtype +func (m *MTOServiceItemInternationalDestSIT) SetETag(val string) { + m.eTagField = val +} + +// ID gets the id of this subtype +func (m *MTOServiceItemInternationalDestSIT) ID() strfmt.UUID { + return m.idField +} + +// SetID sets the id of this subtype +func (m *MTOServiceItemInternationalDestSIT) SetID(val strfmt.UUID) { + m.idField = val +} + +// LockedPriceCents gets the locked price cents of this subtype +func (m *MTOServiceItemInternationalDestSIT) LockedPriceCents() *int64 { + return m.lockedPriceCentsField +} + +// SetLockedPriceCents sets the locked price cents of this subtype +func (m *MTOServiceItemInternationalDestSIT) SetLockedPriceCents(val *int64) { + m.lockedPriceCentsField = val +} + +// ModelType gets the model type of this subtype +func (m *MTOServiceItemInternationalDestSIT) ModelType() MTOServiceItemModelType { + return "MTOServiceItemInternationalDestSIT" +} + +// SetModelType sets the model type of this subtype +func (m *MTOServiceItemInternationalDestSIT) SetModelType(val MTOServiceItemModelType) { +} + +// MoveTaskOrderID gets the move task order ID of this subtype +func (m *MTOServiceItemInternationalDestSIT) MoveTaskOrderID() *strfmt.UUID { + return m.moveTaskOrderIdField +} + +// SetMoveTaskOrderID sets the move task order ID of this subtype +func (m *MTOServiceItemInternationalDestSIT) SetMoveTaskOrderID(val *strfmt.UUID) { + m.moveTaskOrderIdField = val +} + +// MtoShipmentID gets the mto shipment ID of this subtype +func (m *MTOServiceItemInternationalDestSIT) MtoShipmentID() strfmt.UUID { + return m.mtoShipmentIdField +} + +// SetMtoShipmentID sets the mto shipment ID of this subtype +func (m *MTOServiceItemInternationalDestSIT) SetMtoShipmentID(val strfmt.UUID) { + m.mtoShipmentIdField = val +} + +// ReServiceName gets the re service name of this subtype +func (m *MTOServiceItemInternationalDestSIT) ReServiceName() string { + return m.reServiceNameField +} + +// SetReServiceName sets the re service name of this subtype +func (m *MTOServiceItemInternationalDestSIT) SetReServiceName(val string) { + m.reServiceNameField = val +} + +// RejectionReason gets the rejection reason of this subtype +func (m *MTOServiceItemInternationalDestSIT) RejectionReason() *string { + return m.rejectionReasonField +} + +// SetRejectionReason sets the rejection reason of this subtype +func (m *MTOServiceItemInternationalDestSIT) SetRejectionReason(val *string) { + m.rejectionReasonField = val +} + +// ServiceRequestDocuments gets the service request documents of this subtype +func (m *MTOServiceItemInternationalDestSIT) ServiceRequestDocuments() ServiceRequestDocuments { + return m.serviceRequestDocumentsField +} + +// SetServiceRequestDocuments sets the service request documents of this subtype +func (m *MTOServiceItemInternationalDestSIT) SetServiceRequestDocuments(val ServiceRequestDocuments) { + m.serviceRequestDocumentsField = val +} + +// Status gets the status of this subtype +func (m *MTOServiceItemInternationalDestSIT) Status() MTOServiceItemStatus { + return m.statusField +} + +// SetStatus sets the status of this subtype +func (m *MTOServiceItemInternationalDestSIT) SetStatus(val MTOServiceItemStatus) { + m.statusField = val +} + +// UnmarshalJSON unmarshals this object with a polymorphic type from a JSON structure +func (m *MTOServiceItemInternationalDestSIT) UnmarshalJSON(raw []byte) error { + var data struct { + + // Date of attempted contact by the prime corresponding to `timeMilitary1`. + // Format: date + DateOfContact1 *strfmt.Date `json:"dateOfContact1,omitempty"` + + // Date of attempted contact by the prime corresponding to `timeMilitary2`. + // Format: date + DateOfContact2 *strfmt.Date `json:"dateOfContact2,omitempty"` + + // First available date that Prime can deliver SIT service item. + // Format: date + FirstAvailableDeliveryDate1 *strfmt.Date `json:"firstAvailableDeliveryDate1,omitempty"` + + // Second available date that Prime can deliver SIT service item. + // Format: date + FirstAvailableDeliveryDate2 *strfmt.Date `json:"firstAvailableDeliveryDate2,omitempty"` + + // Service code allowed for this model type. + // Required: true + // Enum: [IDFSIT IDASIT] + ReServiceCode *string `json:"reServiceCode"` + + // The reason item has been placed in SIT. + // + // Required: true + Reason *string `json:"reason"` + + // Date when the customer contacted the prime for a delivery out of SIT. + // Format: date + SitCustomerContacted *strfmt.Date `json:"sitCustomerContacted,omitempty"` + + // Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date. + // Format: date + SitDepartureDate *strfmt.Date `json:"sitDepartureDate,omitempty"` + + // sit destination final address + SitDestinationFinalAddress *Address `json:"sitDestinationFinalAddress,omitempty"` + + // Entry date for the SIT + // Required: true + // Format: date + SitEntryDate *strfmt.Date `json:"sitEntryDate"` + + // Date when the customer has requested delivery out of SIT. + // Format: date + SitRequestedDelivery *strfmt.Date `json:"sitRequestedDelivery,omitempty"` + + // Time of attempted contact corresponding to `dateOfContact1`, in military format. + // Example: 1400Z + // Pattern: \d{4}Z + TimeMilitary1 *string `json:"timeMilitary1,omitempty"` + + // Time of attempted contact corresponding to `dateOfContact2`, in military format. + // Example: 1400Z + // Pattern: \d{4}Z + TimeMilitary2 *string `json:"timeMilitary2,omitempty"` + } + 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 MTOServiceItemInternationalDestSIT + + 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.DateOfContact1 = data.DateOfContact1 + result.DateOfContact2 = data.DateOfContact2 + result.FirstAvailableDeliveryDate1 = data.FirstAvailableDeliveryDate1 + result.FirstAvailableDeliveryDate2 = data.FirstAvailableDeliveryDate2 + result.ReServiceCode = data.ReServiceCode + result.Reason = data.Reason + result.SitCustomerContacted = data.SitCustomerContacted + result.SitDepartureDate = data.SitDepartureDate + result.SitDestinationFinalAddress = data.SitDestinationFinalAddress + result.SitEntryDate = data.SitEntryDate + result.SitRequestedDelivery = data.SitRequestedDelivery + result.TimeMilitary1 = data.TimeMilitary1 + result.TimeMilitary2 = data.TimeMilitary2 + + *m = result + + return nil +} + +// MarshalJSON marshals this object with a polymorphic type to a JSON structure +func (m MTOServiceItemInternationalDestSIT) MarshalJSON() ([]byte, error) { + var b1, b2, b3 []byte + var err error + b1, err = json.Marshal(struct { + + // Date of attempted contact by the prime corresponding to `timeMilitary1`. + // Format: date + DateOfContact1 *strfmt.Date `json:"dateOfContact1,omitempty"` + + // Date of attempted contact by the prime corresponding to `timeMilitary2`. + // Format: date + DateOfContact2 *strfmt.Date `json:"dateOfContact2,omitempty"` + + // First available date that Prime can deliver SIT service item. + // Format: date + FirstAvailableDeliveryDate1 *strfmt.Date `json:"firstAvailableDeliveryDate1,omitempty"` + + // Second available date that Prime can deliver SIT service item. + // Format: date + FirstAvailableDeliveryDate2 *strfmt.Date `json:"firstAvailableDeliveryDate2,omitempty"` + + // Service code allowed for this model type. + // Required: true + // Enum: [IDFSIT IDASIT] + ReServiceCode *string `json:"reServiceCode"` + + // The reason item has been placed in SIT. + // + // Required: true + Reason *string `json:"reason"` + + // Date when the customer contacted the prime for a delivery out of SIT. + // Format: date + SitCustomerContacted *strfmt.Date `json:"sitCustomerContacted,omitempty"` + + // Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date. + // Format: date + SitDepartureDate *strfmt.Date `json:"sitDepartureDate,omitempty"` + + // sit destination final address + SitDestinationFinalAddress *Address `json:"sitDestinationFinalAddress,omitempty"` + + // Entry date for the SIT + // Required: true + // Format: date + SitEntryDate *strfmt.Date `json:"sitEntryDate"` + + // Date when the customer has requested delivery out of SIT. + // Format: date + SitRequestedDelivery *strfmt.Date `json:"sitRequestedDelivery,omitempty"` + + // Time of attempted contact corresponding to `dateOfContact1`, in military format. + // Example: 1400Z + // Pattern: \d{4}Z + TimeMilitary1 *string `json:"timeMilitary1,omitempty"` + + // Time of attempted contact corresponding to `dateOfContact2`, in military format. + // Example: 1400Z + // Pattern: \d{4}Z + TimeMilitary2 *string `json:"timeMilitary2,omitempty"` + }{ + + DateOfContact1: m.DateOfContact1, + + DateOfContact2: m.DateOfContact2, + + FirstAvailableDeliveryDate1: m.FirstAvailableDeliveryDate1, + + FirstAvailableDeliveryDate2: m.FirstAvailableDeliveryDate2, + + ReServiceCode: m.ReServiceCode, + + Reason: m.Reason, + + SitCustomerContacted: m.SitCustomerContacted, + + SitDepartureDate: m.SitDepartureDate, + + SitDestinationFinalAddress: m.SitDestinationFinalAddress, + + SitEntryDate: m.SitEntryDate, + + SitRequestedDelivery: m.SitRequestedDelivery, + + TimeMilitary1: m.TimeMilitary1, + + TimeMilitary2: m.TimeMilitary2, + }) + 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 international dest s i t +func (m *MTOServiceItemInternationalDestSIT) 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.validateDateOfContact1(formats); err != nil { + res = append(res, err) + } + + if err := m.validateDateOfContact2(formats); err != nil { + res = append(res, err) + } + + if err := m.validateFirstAvailableDeliveryDate1(formats); err != nil { + res = append(res, err) + } + + if err := m.validateFirstAvailableDeliveryDate2(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 err := m.validateSitCustomerContacted(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSitDepartureDate(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSitDestinationFinalAddress(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSitEntryDate(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSitRequestedDelivery(formats); err != nil { + res = append(res, err) + } + + if err := m.validateTimeMilitary1(formats); err != nil { + res = append(res, err) + } + + if err := m.validateTimeMilitary2(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) 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 +} + +func (m *MTOServiceItemInternationalDestSIT) validateDateOfContact1(formats strfmt.Registry) error { + + if swag.IsZero(m.DateOfContact1) { // not required + return nil + } + + if err := validate.FormatOf("dateOfContact1", "body", "date", m.DateOfContact1.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) validateDateOfContact2(formats strfmt.Registry) error { + + if swag.IsZero(m.DateOfContact2) { // not required + return nil + } + + if err := validate.FormatOf("dateOfContact2", "body", "date", m.DateOfContact2.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) validateFirstAvailableDeliveryDate1(formats strfmt.Registry) error { + + if swag.IsZero(m.FirstAvailableDeliveryDate1) { // not required + return nil + } + + if err := validate.FormatOf("firstAvailableDeliveryDate1", "body", "date", m.FirstAvailableDeliveryDate1.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) validateFirstAvailableDeliveryDate2(formats strfmt.Registry) error { + + if swag.IsZero(m.FirstAvailableDeliveryDate2) { // not required + return nil + } + + if err := validate.FormatOf("firstAvailableDeliveryDate2", "body", "date", m.FirstAvailableDeliveryDate2.String(), formats); err != nil { + return err + } + + return nil +} + +var mTOServiceItemInternationalDestSITTypeReServiceCodePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["IDFSIT","IDASIT"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + mTOServiceItemInternationalDestSITTypeReServiceCodePropEnum = append(mTOServiceItemInternationalDestSITTypeReServiceCodePropEnum, v) + } +} + +// property enum +func (m *MTOServiceItemInternationalDestSIT) validateReServiceCodeEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, mTOServiceItemInternationalDestSITTypeReServiceCodePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) validateReason(formats strfmt.Registry) error { + + if err := validate.Required("reason", "body", m.Reason); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) validateSitCustomerContacted(formats strfmt.Registry) error { + + if swag.IsZero(m.SitCustomerContacted) { // not required + return nil + } + + if err := validate.FormatOf("sitCustomerContacted", "body", "date", m.SitCustomerContacted.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) validateSitDepartureDate(formats strfmt.Registry) error { + + if swag.IsZero(m.SitDepartureDate) { // not required + return nil + } + + if err := validate.FormatOf("sitDepartureDate", "body", "date", m.SitDepartureDate.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) validateSitDestinationFinalAddress(formats strfmt.Registry) error { + + if swag.IsZero(m.SitDestinationFinalAddress) { // not required + return nil + } + + if m.SitDestinationFinalAddress != nil { + if err := m.SitDestinationFinalAddress.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("sitDestinationFinalAddress") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("sitDestinationFinalAddress") + } + return err + } + } + + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) validateSitEntryDate(formats strfmt.Registry) error { + + if err := validate.Required("sitEntryDate", "body", m.SitEntryDate); err != nil { + return err + } + + if err := validate.FormatOf("sitEntryDate", "body", "date", m.SitEntryDate.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) validateSitRequestedDelivery(formats strfmt.Registry) error { + + if swag.IsZero(m.SitRequestedDelivery) { // not required + return nil + } + + if err := validate.FormatOf("sitRequestedDelivery", "body", "date", m.SitRequestedDelivery.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) validateTimeMilitary1(formats strfmt.Registry) error { + + if swag.IsZero(m.TimeMilitary1) { // not required + return nil + } + + if err := validate.Pattern("timeMilitary1", "body", *m.TimeMilitary1, `\d{4}Z`); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) validateTimeMilitary2(formats strfmt.Registry) error { + + if swag.IsZero(m.TimeMilitary2) { // not required + return nil + } + + if err := validate.Pattern("timeMilitary2", "body", *m.TimeMilitary2, `\d{4}Z`); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this m t o service item international dest s i t based on the context it is used +func (m *MTOServiceItemInternationalDestSIT) 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 err := m.contextValidateSitDestinationFinalAddress(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) 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 +} + +func (m *MTOServiceItemInternationalDestSIT) contextValidateSitDestinationFinalAddress(ctx context.Context, formats strfmt.Registry) error { + + if m.SitDestinationFinalAddress != nil { + + if swag.IsZero(m.SitDestinationFinalAddress) { // not required + return nil + } + + if err := m.SitDestinationFinalAddress.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("sitDestinationFinalAddress") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("sitDestinationFinalAddress") + } + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (m *MTOServiceItemInternationalDestSIT) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *MTOServiceItemInternationalDestSIT) UnmarshalBinary(b []byte) error { + var res MTOServiceItemInternationalDestSIT + 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_international_origin_s_i_t.go b/pkg/gen/primemessages/m_t_o_service_item_international_origin_s_i_t.go new file mode 100644 index 00000000000..96392736360 --- /dev/null +++ b/pkg/gen/primemessages/m_t_o_service_item_international_origin_s_i_t.go @@ -0,0 +1,900 @@ +// 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" +) + +// MTOServiceItemInternationalOriginSIT Describes a international origin SIT service item. Subtype of a MTOServiceItem. +// +// swagger:model MTOServiceItemInternationalOriginSIT +type MTOServiceItemInternationalOriginSIT struct { + eTagField string + + idField strfmt.UUID + + lockedPriceCentsField *int64 + + moveTaskOrderIdField *strfmt.UUID + + mtoShipmentIdField strfmt.UUID + + reServiceNameField string + + rejectionReasonField *string + + serviceRequestDocumentsField ServiceRequestDocuments + + statusField MTOServiceItemStatus + + // Service code allowed for this model type. + // Required: true + // Enum: [IOFSIT IOASIT] + ReServiceCode *string `json:"reServiceCode"` + + // Explanation of why Prime is picking up SIT item. + // Example: Storage items need to be picked up + // Required: true + Reason *string `json:"reason"` + + // request approvals requested status + RequestApprovalsRequestedStatus bool `json:"requestApprovalsRequestedStatus,omitempty"` + + // Date when the customer contacted the prime for a delivery out of SIT. + // Format: date + SitCustomerContacted *strfmt.Date `json:"sitCustomerContacted,omitempty"` + + // Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date. + // Format: date + SitDepartureDate *strfmt.Date `json:"sitDepartureDate,omitempty"` + + // Entry date for the SIT + // Required: true + // Format: date + SitEntryDate *strfmt.Date `json:"sitEntryDate"` + + // sit h h g actual origin + SitHHGActualOrigin *Address `json:"sitHHGActualOrigin,omitempty"` + + // sit h h g original origin + SitHHGOriginalOrigin *Address `json:"sitHHGOriginalOrigin,omitempty"` + + // sit postal code + // Example: 90210 + // Required: true + // Pattern: ^(\d{5}([\-]\d{4})?)$ + SitPostalCode *string `json:"sitPostalCode"` + + // Date when the customer has requested delivery out of SIT. + // Format: date + SitRequestedDelivery *strfmt.Date `json:"sitRequestedDelivery,omitempty"` +} + +// ETag gets the e tag of this subtype +func (m *MTOServiceItemInternationalOriginSIT) ETag() string { + return m.eTagField +} + +// SetETag sets the e tag of this subtype +func (m *MTOServiceItemInternationalOriginSIT) SetETag(val string) { + m.eTagField = val +} + +// ID gets the id of this subtype +func (m *MTOServiceItemInternationalOriginSIT) ID() strfmt.UUID { + return m.idField +} + +// SetID sets the id of this subtype +func (m *MTOServiceItemInternationalOriginSIT) SetID(val strfmt.UUID) { + m.idField = val +} + +// LockedPriceCents gets the locked price cents of this subtype +func (m *MTOServiceItemInternationalOriginSIT) LockedPriceCents() *int64 { + return m.lockedPriceCentsField +} + +// SetLockedPriceCents sets the locked price cents of this subtype +func (m *MTOServiceItemInternationalOriginSIT) SetLockedPriceCents(val *int64) { + m.lockedPriceCentsField = val +} + +// ModelType gets the model type of this subtype +func (m *MTOServiceItemInternationalOriginSIT) ModelType() MTOServiceItemModelType { + return "MTOServiceItemInternationalOriginSIT" +} + +// SetModelType sets the model type of this subtype +func (m *MTOServiceItemInternationalOriginSIT) SetModelType(val MTOServiceItemModelType) { +} + +// MoveTaskOrderID gets the move task order ID of this subtype +func (m *MTOServiceItemInternationalOriginSIT) MoveTaskOrderID() *strfmt.UUID { + return m.moveTaskOrderIdField +} + +// SetMoveTaskOrderID sets the move task order ID of this subtype +func (m *MTOServiceItemInternationalOriginSIT) SetMoveTaskOrderID(val *strfmt.UUID) { + m.moveTaskOrderIdField = val +} + +// MtoShipmentID gets the mto shipment ID of this subtype +func (m *MTOServiceItemInternationalOriginSIT) MtoShipmentID() strfmt.UUID { + return m.mtoShipmentIdField +} + +// SetMtoShipmentID sets the mto shipment ID of this subtype +func (m *MTOServiceItemInternationalOriginSIT) SetMtoShipmentID(val strfmt.UUID) { + m.mtoShipmentIdField = val +} + +// ReServiceName gets the re service name of this subtype +func (m *MTOServiceItemInternationalOriginSIT) ReServiceName() string { + return m.reServiceNameField +} + +// SetReServiceName sets the re service name of this subtype +func (m *MTOServiceItemInternationalOriginSIT) SetReServiceName(val string) { + m.reServiceNameField = val +} + +// RejectionReason gets the rejection reason of this subtype +func (m *MTOServiceItemInternationalOriginSIT) RejectionReason() *string { + return m.rejectionReasonField +} + +// SetRejectionReason sets the rejection reason of this subtype +func (m *MTOServiceItemInternationalOriginSIT) SetRejectionReason(val *string) { + m.rejectionReasonField = val +} + +// ServiceRequestDocuments gets the service request documents of this subtype +func (m *MTOServiceItemInternationalOriginSIT) ServiceRequestDocuments() ServiceRequestDocuments { + return m.serviceRequestDocumentsField +} + +// SetServiceRequestDocuments sets the service request documents of this subtype +func (m *MTOServiceItemInternationalOriginSIT) SetServiceRequestDocuments(val ServiceRequestDocuments) { + m.serviceRequestDocumentsField = val +} + +// Status gets the status of this subtype +func (m *MTOServiceItemInternationalOriginSIT) Status() MTOServiceItemStatus { + return m.statusField +} + +// SetStatus sets the status of this subtype +func (m *MTOServiceItemInternationalOriginSIT) SetStatus(val MTOServiceItemStatus) { + m.statusField = val +} + +// UnmarshalJSON unmarshals this object with a polymorphic type from a JSON structure +func (m *MTOServiceItemInternationalOriginSIT) UnmarshalJSON(raw []byte) error { + var data struct { + + // Service code allowed for this model type. + // Required: true + // Enum: [IOFSIT IOASIT] + ReServiceCode *string `json:"reServiceCode"` + + // Explanation of why Prime is picking up SIT item. + // Example: Storage items need to be picked up + // Required: true + Reason *string `json:"reason"` + + // request approvals requested status + RequestApprovalsRequestedStatus bool `json:"requestApprovalsRequestedStatus,omitempty"` + + // Date when the customer contacted the prime for a delivery out of SIT. + // Format: date + SitCustomerContacted *strfmt.Date `json:"sitCustomerContacted,omitempty"` + + // Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date. + // Format: date + SitDepartureDate *strfmt.Date `json:"sitDepartureDate,omitempty"` + + // Entry date for the SIT + // Required: true + // Format: date + SitEntryDate *strfmt.Date `json:"sitEntryDate"` + + // sit h h g actual origin + SitHHGActualOrigin *Address `json:"sitHHGActualOrigin,omitempty"` + + // sit h h g original origin + SitHHGOriginalOrigin *Address `json:"sitHHGOriginalOrigin,omitempty"` + + // sit postal code + // Example: 90210 + // Required: true + // Pattern: ^(\d{5}([\-]\d{4})?)$ + SitPostalCode *string `json:"sitPostalCode"` + + // Date when the customer has requested delivery out of SIT. + // Format: date + SitRequestedDelivery *strfmt.Date `json:"sitRequestedDelivery,omitempty"` + } + 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 MTOServiceItemInternationalOriginSIT + + 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.ReServiceCode = data.ReServiceCode + result.Reason = data.Reason + result.RequestApprovalsRequestedStatus = data.RequestApprovalsRequestedStatus + result.SitCustomerContacted = data.SitCustomerContacted + result.SitDepartureDate = data.SitDepartureDate + result.SitEntryDate = data.SitEntryDate + result.SitHHGActualOrigin = data.SitHHGActualOrigin + result.SitHHGOriginalOrigin = data.SitHHGOriginalOrigin + result.SitPostalCode = data.SitPostalCode + result.SitRequestedDelivery = data.SitRequestedDelivery + + *m = result + + return nil +} + +// MarshalJSON marshals this object with a polymorphic type to a JSON structure +func (m MTOServiceItemInternationalOriginSIT) MarshalJSON() ([]byte, error) { + var b1, b2, b3 []byte + var err error + b1, err = json.Marshal(struct { + + // Service code allowed for this model type. + // Required: true + // Enum: [IOFSIT IOASIT] + ReServiceCode *string `json:"reServiceCode"` + + // Explanation of why Prime is picking up SIT item. + // Example: Storage items need to be picked up + // Required: true + Reason *string `json:"reason"` + + // request approvals requested status + RequestApprovalsRequestedStatus bool `json:"requestApprovalsRequestedStatus,omitempty"` + + // Date when the customer contacted the prime for a delivery out of SIT. + // Format: date + SitCustomerContacted *strfmt.Date `json:"sitCustomerContacted,omitempty"` + + // Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date. + // Format: date + SitDepartureDate *strfmt.Date `json:"sitDepartureDate,omitempty"` + + // Entry date for the SIT + // Required: true + // Format: date + SitEntryDate *strfmt.Date `json:"sitEntryDate"` + + // sit h h g actual origin + SitHHGActualOrigin *Address `json:"sitHHGActualOrigin,omitempty"` + + // sit h h g original origin + SitHHGOriginalOrigin *Address `json:"sitHHGOriginalOrigin,omitempty"` + + // sit postal code + // Example: 90210 + // Required: true + // Pattern: ^(\d{5}([\-]\d{4})?)$ + SitPostalCode *string `json:"sitPostalCode"` + + // Date when the customer has requested delivery out of SIT. + // Format: date + SitRequestedDelivery *strfmt.Date `json:"sitRequestedDelivery,omitempty"` + }{ + + ReServiceCode: m.ReServiceCode, + + Reason: m.Reason, + + RequestApprovalsRequestedStatus: m.RequestApprovalsRequestedStatus, + + SitCustomerContacted: m.SitCustomerContacted, + + SitDepartureDate: m.SitDepartureDate, + + SitEntryDate: m.SitEntryDate, + + SitHHGActualOrigin: m.SitHHGActualOrigin, + + SitHHGOriginalOrigin: m.SitHHGOriginalOrigin, + + SitPostalCode: m.SitPostalCode, + + SitRequestedDelivery: m.SitRequestedDelivery, + }) + 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 international origin s i t +func (m *MTOServiceItemInternationalOriginSIT) 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 err := m.validateSitCustomerContacted(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSitDepartureDate(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSitEntryDate(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSitHHGActualOrigin(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSitHHGOriginalOrigin(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSitPostalCode(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSitRequestedDelivery(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) 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 mTOServiceItemInternationalOriginSITTypeReServiceCodePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["IOFSIT","IOASIT"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + mTOServiceItemInternationalOriginSITTypeReServiceCodePropEnum = append(mTOServiceItemInternationalOriginSITTypeReServiceCodePropEnum, v) + } +} + +// property enum +func (m *MTOServiceItemInternationalOriginSIT) validateReServiceCodeEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, mTOServiceItemInternationalOriginSITTypeReServiceCodePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) validateReason(formats strfmt.Registry) error { + + if err := validate.Required("reason", "body", m.Reason); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) validateSitCustomerContacted(formats strfmt.Registry) error { + + if swag.IsZero(m.SitCustomerContacted) { // not required + return nil + } + + if err := validate.FormatOf("sitCustomerContacted", "body", "date", m.SitCustomerContacted.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) validateSitDepartureDate(formats strfmt.Registry) error { + + if swag.IsZero(m.SitDepartureDate) { // not required + return nil + } + + if err := validate.FormatOf("sitDepartureDate", "body", "date", m.SitDepartureDate.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) validateSitEntryDate(formats strfmt.Registry) error { + + if err := validate.Required("sitEntryDate", "body", m.SitEntryDate); err != nil { + return err + } + + if err := validate.FormatOf("sitEntryDate", "body", "date", m.SitEntryDate.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) validateSitHHGActualOrigin(formats strfmt.Registry) error { + + if swag.IsZero(m.SitHHGActualOrigin) { // not required + return nil + } + + if m.SitHHGActualOrigin != nil { + if err := m.SitHHGActualOrigin.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("sitHHGActualOrigin") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("sitHHGActualOrigin") + } + return err + } + } + + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) validateSitHHGOriginalOrigin(formats strfmt.Registry) error { + + if swag.IsZero(m.SitHHGOriginalOrigin) { // not required + return nil + } + + if m.SitHHGOriginalOrigin != nil { + if err := m.SitHHGOriginalOrigin.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("sitHHGOriginalOrigin") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("sitHHGOriginalOrigin") + } + return err + } + } + + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) validateSitPostalCode(formats strfmt.Registry) error { + + if err := validate.Required("sitPostalCode", "body", m.SitPostalCode); err != nil { + return err + } + + if err := validate.Pattern("sitPostalCode", "body", *m.SitPostalCode, `^(\d{5}([\-]\d{4})?)$`); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) validateSitRequestedDelivery(formats strfmt.Registry) error { + + if swag.IsZero(m.SitRequestedDelivery) { // not required + return nil + } + + if err := validate.FormatOf("sitRequestedDelivery", "body", "date", m.SitRequestedDelivery.String(), formats); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this m t o service item international origin s i t based on the context it is used +func (m *MTOServiceItemInternationalOriginSIT) 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 err := m.contextValidateSitHHGActualOrigin(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateSitHHGOriginalOrigin(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) 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 +} + +func (m *MTOServiceItemInternationalOriginSIT) contextValidateSitHHGActualOrigin(ctx context.Context, formats strfmt.Registry) error { + + if m.SitHHGActualOrigin != nil { + + if swag.IsZero(m.SitHHGActualOrigin) { // not required + return nil + } + + if err := m.SitHHGActualOrigin.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("sitHHGActualOrigin") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("sitHHGActualOrigin") + } + return err + } + } + + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) contextValidateSitHHGOriginalOrigin(ctx context.Context, formats strfmt.Registry) error { + + if m.SitHHGOriginalOrigin != nil { + + if swag.IsZero(m.SitHHGOriginalOrigin) { // not required + return nil + } + + if err := m.SitHHGOriginalOrigin.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("sitHHGOriginalOrigin") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("sitHHGOriginalOrigin") + } + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (m *MTOServiceItemInternationalOriginSIT) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *MTOServiceItemInternationalOriginSIT) UnmarshalBinary(b []byte) error { + var res MTOServiceItemInternationalOriginSIT + 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..0e273fe2bd3 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 @@ -19,7 +19,10 @@ import ( // Using this list, choose the correct modelType in the dropdown, corresponding to the service item type. // - DOFSIT, DOASIT - MTOServiceItemOriginSIT // - DDFSIT, DDASIT - MTOServiceItemDestSIT +// - IOFSIT, IOASIT - MTOServiceItemInternationalOriginSIT +// - IDFSIT, IDASIT - MTOServiceItemInternationalDestSIT // - DOSHUT, DDSHUT - MTOServiceItemShuttle +// - DOSHUT, DDSHUT - MTOServiceItemDomesticShuttle // - IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle // - DCRT, DUCRT - MTOServiceItemDomesticCrating // - ICRT, IUCRT - MTOServiceItemInternationalCrating @@ -50,9 +53,18 @@ const ( // MTOServiceItemModelTypeMTOServiceItemDestSIT captures enum value "MTOServiceItemDestSIT" MTOServiceItemModelTypeMTOServiceItemDestSIT MTOServiceItemModelType = "MTOServiceItemDestSIT" + // MTOServiceItemModelTypeMTOServiceItemInternationalOriginSIT captures enum value "MTOServiceItemInternationalOriginSIT" + MTOServiceItemModelTypeMTOServiceItemInternationalOriginSIT MTOServiceItemModelType = "MTOServiceItemInternationalOriginSIT" + + // MTOServiceItemModelTypeMTOServiceItemInternationalDestSIT captures enum value "MTOServiceItemInternationalDestSIT" + MTOServiceItemModelTypeMTOServiceItemInternationalDestSIT MTOServiceItemModelType = "MTOServiceItemInternationalDestSIT" + // 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 +83,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","MTOServiceItemInternationalOriginSIT","MTOServiceItemInternationalDestSIT","MTOServiceItemShuttle","MTOServiceItemDomesticShuttle","MTOServiceItemInternationalShuttle","MTOServiceItemDomesticCrating","MTOServiceItemInternationalCrating","MTOSerivceItemInternationalFuelSurcharge"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/primemessages/service_item.go b/pkg/gen/primemessages/service_item.go index 9f41d0b13e2..6c29dbc0d40 100644 --- a/pkg/gen/primemessages/service_item.go +++ b/pkg/gen/primemessages/service_item.go @@ -32,6 +32,8 @@ type ServiceItem struct { // This should be populated for the following service items: // * DOASIT(Domestic origin Additional day SIT) // * DDASIT(Domestic destination Additional day SIT) + // * IOASIT(International origin Additional day SIT) + // * IDASIT(International destination Additional day SIT) // // Both take in the following param keys: // * `SITPaymentRequestStart` diff --git a/pkg/gen/primemessages/update_m_t_o_service_item.go b/pkg/gen/primemessages/update_m_t_o_service_item.go index 36fc059bd0a..7a5abce67d9 100644 --- a/pkg/gen/primemessages/update_m_t_o_service_item.go +++ b/pkg/gen/primemessages/update_m_t_o_service_item.go @@ -117,6 +117,12 @@ func unmarshalUpdateMTOServiceItem(data []byte, consumer runtime.Consumer) (Upda return nil, err } return &result, nil + case "UpdateMTOServiceItemCrating": + var result UpdateMTOServiceItemCrating + if err := consumer.Consume(buf2, &result); err != nil { + return nil, err + } + return &result, nil case "UpdateMTOServiceItemInternationalPortFSC": var result UpdateMTOServiceItemInternationalPortFSC if err := consumer.Consume(buf2, &result); err != nil { diff --git a/pkg/gen/primemessages/update_m_t_o_service_item_crating.go b/pkg/gen/primemessages/update_m_t_o_service_item_crating.go new file mode 100644 index 00000000000..45396947c3a --- /dev/null +++ b/pkg/gen/primemessages/update_m_t_o_service_item_crating.go @@ -0,0 +1,377 @@ +// 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" +) + +// UpdateMTOServiceItemCrating Subtype used to provide the size and types for crating. This is not creating a new service item but rather updating an existing service item. +// +// swagger:model UpdateMTOServiceItemCrating +type UpdateMTOServiceItemCrating struct { + idField strfmt.UUID + + // The dimensions for the crate the item will be shipped in. + Crate struct { + MTOServiceItemDimension + } `json:"crate,omitempty"` + + // A description of the item being crated. + // Example: Decorated horse head to be crated. + Description *string `json:"description,omitempty"` + + // external crate + ExternalCrate *bool `json:"externalCrate,omitempty"` + + // The dimensions of the item being crated. + Item struct { + MTOServiceItemDimension + } `json:"item,omitempty"` + + // Service code allowed for this model type. + // Enum: [ICRT IUCRT] + ReServiceCode string `json:"reServiceCode,omitempty"` + + // Indicates if "Approvals Requested" status is being requested. + RequestApprovalsRequestedStatus *bool `json:"requestApprovalsRequestedStatus,omitempty"` + + // standalone crate + StandaloneCrate *bool `json:"standaloneCrate,omitempty"` + + // Reason for updating service item. + UpdateReason *string `json:"updateReason,omitempty"` +} + +// ID gets the id of this subtype +func (m *UpdateMTOServiceItemCrating) ID() strfmt.UUID { + return m.idField +} + +// SetID sets the id of this subtype +func (m *UpdateMTOServiceItemCrating) SetID(val strfmt.UUID) { + m.idField = val +} + +// ModelType gets the model type of this subtype +func (m *UpdateMTOServiceItemCrating) ModelType() UpdateMTOServiceItemModelType { + return "UpdateMTOServiceItemCrating" +} + +// SetModelType sets the model type of this subtype +func (m *UpdateMTOServiceItemCrating) SetModelType(val UpdateMTOServiceItemModelType) { +} + +// UnmarshalJSON unmarshals this object with a polymorphic type from a JSON structure +func (m *UpdateMTOServiceItemCrating) UnmarshalJSON(raw []byte) error { + var data struct { + + // The dimensions for the crate the item will be shipped in. + Crate struct { + MTOServiceItemDimension + } `json:"crate,omitempty"` + + // A description of the item being crated. + // Example: Decorated horse head to be crated. + Description *string `json:"description,omitempty"` + + // external crate + ExternalCrate *bool `json:"externalCrate,omitempty"` + + // The dimensions of the item being crated. + Item struct { + MTOServiceItemDimension + } `json:"item,omitempty"` + + // Service code allowed for this model type. + // Enum: [ICRT IUCRT] + ReServiceCode string `json:"reServiceCode,omitempty"` + + // Indicates if "Approvals Requested" status is being requested. + RequestApprovalsRequestedStatus *bool `json:"requestApprovalsRequestedStatus,omitempty"` + + // standalone crate + StandaloneCrate *bool `json:"standaloneCrate,omitempty"` + + // Reason for updating service item. + UpdateReason *string `json:"updateReason,omitempty"` + } + 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.*/ + + ID strfmt.UUID `json:"id,omitempty"` + + ModelType UpdateMTOServiceItemModelType `json:"modelType"` + } + buf = bytes.NewBuffer(raw) + dec = json.NewDecoder(buf) + dec.UseNumber() + + if err := dec.Decode(&base); err != nil { + return err + } + + var result UpdateMTOServiceItemCrating + + 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.Crate = data.Crate + result.Description = data.Description + result.ExternalCrate = data.ExternalCrate + result.Item = data.Item + result.ReServiceCode = data.ReServiceCode + result.RequestApprovalsRequestedStatus = data.RequestApprovalsRequestedStatus + result.StandaloneCrate = data.StandaloneCrate + result.UpdateReason = data.UpdateReason + + *m = result + + return nil +} + +// MarshalJSON marshals this object with a polymorphic type to a JSON structure +func (m UpdateMTOServiceItemCrating) MarshalJSON() ([]byte, error) { + var b1, b2, b3 []byte + var err error + b1, err = json.Marshal(struct { + + // The dimensions for the crate the item will be shipped in. + Crate struct { + MTOServiceItemDimension + } `json:"crate,omitempty"` + + // A description of the item being crated. + // Example: Decorated horse head to be crated. + Description *string `json:"description,omitempty"` + + // external crate + ExternalCrate *bool `json:"externalCrate,omitempty"` + + // The dimensions of the item being crated. + Item struct { + MTOServiceItemDimension + } `json:"item,omitempty"` + + // Service code allowed for this model type. + // Enum: [ICRT IUCRT] + ReServiceCode string `json:"reServiceCode,omitempty"` + + // Indicates if "Approvals Requested" status is being requested. + RequestApprovalsRequestedStatus *bool `json:"requestApprovalsRequestedStatus,omitempty"` + + // standalone crate + StandaloneCrate *bool `json:"standaloneCrate,omitempty"` + + // Reason for updating service item. + UpdateReason *string `json:"updateReason,omitempty"` + }{ + + Crate: m.Crate, + + Description: m.Description, + + ExternalCrate: m.ExternalCrate, + + Item: m.Item, + + ReServiceCode: m.ReServiceCode, + + RequestApprovalsRequestedStatus: m.RequestApprovalsRequestedStatus, + + StandaloneCrate: m.StandaloneCrate, + + UpdateReason: m.UpdateReason, + }) + if err != nil { + return nil, err + } + b2, err = json.Marshal(struct { + ID strfmt.UUID `json:"id,omitempty"` + + ModelType UpdateMTOServiceItemModelType `json:"modelType"` + }{ + + ID: m.ID(), + + ModelType: m.ModelType(), + }) + if err != nil { + return nil, err + } + + return swag.ConcatJSON(b1, b2, b3), nil +} + +// Validate validates this update m t o service item crating +func (m *UpdateMTOServiceItemCrating) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateID(formats); err != nil { + res = append(res, err) + } + + if err := m.validateCrate(formats); err != nil { + res = append(res, err) + } + + if err := m.validateItem(formats); err != nil { + res = append(res, err) + } + + if err := m.validateReServiceCode(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *UpdateMTOServiceItemCrating) 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 *UpdateMTOServiceItemCrating) validateCrate(formats strfmt.Registry) error { + + if swag.IsZero(m.Crate) { // not required + return nil + } + + return nil +} + +func (m *UpdateMTOServiceItemCrating) validateItem(formats strfmt.Registry) error { + + if swag.IsZero(m.Item) { // not required + return nil + } + + return nil +} + +var updateMTOServiceItemCratingTypeReServiceCodePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["ICRT","IUCRT"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + updateMTOServiceItemCratingTypeReServiceCodePropEnum = append(updateMTOServiceItemCratingTypeReServiceCodePropEnum, v) + } +} + +// property enum +func (m *UpdateMTOServiceItemCrating) validateReServiceCodeEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, updateMTOServiceItemCratingTypeReServiceCodePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *UpdateMTOServiceItemCrating) validateReServiceCode(formats strfmt.Registry) error { + + if swag.IsZero(m.ReServiceCode) { // not required + return nil + } + + // value enum + if err := m.validateReServiceCodeEnum("reServiceCode", "body", m.ReServiceCode); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this update m t o service item crating based on the context it is used +func (m *UpdateMTOServiceItemCrating) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + var res []error + + if err := m.contextValidateCrate(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateItem(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *UpdateMTOServiceItemCrating) 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 *UpdateMTOServiceItemCrating) contextValidateCrate(ctx context.Context, formats strfmt.Registry) error { + + return nil +} + +func (m *UpdateMTOServiceItemCrating) contextValidateItem(ctx context.Context, formats strfmt.Registry) error { + + return nil +} + +// MarshalBinary interface implementation +func (m *UpdateMTOServiceItemCrating) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *UpdateMTOServiceItemCrating) UnmarshalBinary(b []byte) error { + var res UpdateMTOServiceItemCrating + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/gen/primemessages/update_m_t_o_service_item_model_type.go b/pkg/gen/primemessages/update_m_t_o_service_item_model_type.go index cc59a427e36..3a41327a34b 100644 --- a/pkg/gen/primemessages/update_m_t_o_service_item_model_type.go +++ b/pkg/gen/primemessages/update_m_t_o_service_item_model_type.go @@ -23,12 +23,22 @@ import ( // - DOFSIT - UpdateMTOServiceItemSIT // - DOSFSC - UpdateMTOServiceItemSIT // - DDSFSC - UpdateMTOServiceItemSIT +// - IDDSIT - UpdateMTOServiceItemSIT +// - IDFSIT - UpdateMTOServiceItemSIT +// - IDASIT - UpdateMTOServiceItemSIT +// - IOPSIT - UpdateMTOServiceItemSIT +// - IOASIT - UpdateMTOServiceItemSIT +// - IOFSIT - UpdateMTOServiceItemSIT +// - IOSFSC - UpdateMTOServiceItemSIT +// - IDSFSC - UpdateMTOServiceItemSIT // - DDSHUT - UpdateMTOServiceItemShuttle // - DOSHUT - UpdateMTOServiceItemShuttle // - PODFSC - UpdateMTOServiceItemInternationalPortFSC // - POEFSC - UpdateMTOServiceItemInternationalPortFSC // - IDSHUT - UpdateMTOServiceItemInternationalShuttle // - IOSHUT - UpdateMTOServiceItemInternationalShuttle +// - ICRT - UpdateMTOServiceItemCrating +// - IUCRT - UpdateMTOServiceItemCrating // // The documentation will then update with the supported fields. // @@ -57,6 +67,9 @@ const ( // UpdateMTOServiceItemModelTypeUpdateMTOServiceItemInternationalShuttle captures enum value "UpdateMTOServiceItemInternationalShuttle" UpdateMTOServiceItemModelTypeUpdateMTOServiceItemInternationalShuttle UpdateMTOServiceItemModelType = "UpdateMTOServiceItemInternationalShuttle" + + // UpdateMTOServiceItemModelTypeUpdateMTOServiceItemCrating captures enum value "UpdateMTOServiceItemCrating" + UpdateMTOServiceItemModelTypeUpdateMTOServiceItemCrating UpdateMTOServiceItemModelType = "UpdateMTOServiceItemCrating" ) // for schema @@ -64,7 +77,7 @@ var updateMTOServiceItemModelTypeEnum []interface{} func init() { var res []UpdateMTOServiceItemModelType - if err := json.Unmarshal([]byte(`["UpdateMTOServiceItemSIT","UpdateMTOServiceItemShuttle","UpdateMTOServiceItemInternationalPortFSC","UpdateMTOServiceItemInternationalShuttle"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["UpdateMTOServiceItemSIT","UpdateMTOServiceItemShuttle","UpdateMTOServiceItemInternationalPortFSC","UpdateMTOServiceItemInternationalShuttle","UpdateMTOServiceItemCrating"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/primemessages/update_m_t_o_service_item_s_i_t.go b/pkg/gen/primemessages/update_m_t_o_service_item_s_i_t.go index 34fed36c1aa..5c76260ea6a 100644 --- a/pkg/gen/primemessages/update_m_t_o_service_item_s_i_t.go +++ b/pkg/gen/primemessages/update_m_t_o_service_item_s_i_t.go @@ -39,7 +39,7 @@ type UpdateMTOServiceItemSIT struct { FirstAvailableDeliveryDate2 *strfmt.Date `json:"firstAvailableDeliveryDate2,omitempty"` // Service code allowed for this model type. - // Enum: [DDDSIT DDASIT DDFSIT DDSFSC DOPSIT DOASIT DOFSIT DOSFSC] + // Enum: [DDDSIT DDASIT DDFSIT DDSFSC DOPSIT DOASIT DOFSIT DOSFSC IDDSIT IDASIT IDFSIT IDSFSC IOPSIT IOASIT IOFSIT IOSFSC] ReServiceCode string `json:"reServiceCode,omitempty"` // Indicates if "Approvals Requested" status is being requested. @@ -123,7 +123,7 @@ func (m *UpdateMTOServiceItemSIT) UnmarshalJSON(raw []byte) error { FirstAvailableDeliveryDate2 *strfmt.Date `json:"firstAvailableDeliveryDate2,omitempty"` // Service code allowed for this model type. - // Enum: [DDDSIT DDASIT DDFSIT DDSFSC DOPSIT DOASIT DOFSIT DOSFSC] + // Enum: [DDDSIT DDASIT DDFSIT DDSFSC DOPSIT DOASIT DOFSIT DOSFSC IDDSIT IDASIT IDFSIT IDSFSC IOPSIT IOASIT IOFSIT IOSFSC] ReServiceCode string `json:"reServiceCode,omitempty"` // Indicates if "Approvals Requested" status is being requested. @@ -242,7 +242,7 @@ func (m UpdateMTOServiceItemSIT) MarshalJSON() ([]byte, error) { FirstAvailableDeliveryDate2 *strfmt.Date `json:"firstAvailableDeliveryDate2,omitempty"` // Service code allowed for this model type. - // Enum: [DDDSIT DDASIT DDFSIT DDSFSC DOPSIT DOASIT DOFSIT DOSFSC] + // Enum: [DDDSIT DDASIT DDFSIT DDSFSC DOPSIT DOASIT DOFSIT DOSFSC IDDSIT IDASIT IDFSIT IDSFSC IOPSIT IOASIT IOFSIT IOSFSC] ReServiceCode string `json:"reServiceCode,omitempty"` // Indicates if "Approvals Requested" status is being requested. @@ -471,7 +471,7 @@ var updateMTOServiceItemSITTypeReServiceCodePropEnum []interface{} func init() { var res []string - if err := json.Unmarshal([]byte(`["DDDSIT","DDASIT","DDFSIT","DDSFSC","DOPSIT","DOASIT","DOFSIT","DOSFSC"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["DDDSIT","DDASIT","DDFSIT","DDSFSC","DOPSIT","DOASIT","DOFSIT","DOSFSC","IDDSIT","IDASIT","IDFSIT","IDSFSC","IOPSIT","IOASIT","IOFSIT","IOSFSC"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/primemessages/v_location.go b/pkg/gen/primemessages/v_location.go new file mode 100644 index 00000000000..77cd75ee6e3 --- /dev/null +++ b/pkg/gen/primemessages/v_location.go @@ -0,0 +1,302 @@ +// 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 ( + "context" + "encoding/json" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// VLocation A postal code, city, and state lookup +// +// swagger:model VLocation +type VLocation struct { + + // City + // Example: Anytown + City string `json:"city,omitempty"` + + // County + // Example: LOS ANGELES + County *string `json:"county,omitempty"` + + // ZIP + // Example: 90210 + // Pattern: ^(\d{5}?)$ + PostalCode string `json:"postalCode,omitempty"` + + // State + // Enum: [AL AK AR AZ CA CO CT DC DE FL GA HI IA ID IL IN KS KY LA MA MD ME MI MN MO MS MT NC ND NE NH NJ NM NV NY OH OK OR PA RI SC SD TN TX UT VA VT WA WI WV WY] + State string `json:"state,omitempty"` + + // us post region cities ID + // Example: c56a4180-65aa-42ec-a945-5fd21dec0538 + // Format: uuid + UsPostRegionCitiesID strfmt.UUID `json:"usPostRegionCitiesID,omitempty"` +} + +// Validate validates this v location +func (m *VLocation) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validatePostalCode(formats); err != nil { + res = append(res, err) + } + + if err := m.validateState(formats); err != nil { + res = append(res, err) + } + + if err := m.validateUsPostRegionCitiesID(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *VLocation) validatePostalCode(formats strfmt.Registry) error { + if swag.IsZero(m.PostalCode) { // not required + return nil + } + + if err := validate.Pattern("postalCode", "body", m.PostalCode, `^(\d{5}?)$`); err != nil { + return err + } + + return nil +} + +var vLocationTypeStatePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["AL","AK","AR","AZ","CA","CO","CT","DC","DE","FL","GA","HI","IA","ID","IL","IN","KS","KY","LA","MA","MD","ME","MI","MN","MO","MS","MT","NC","ND","NE","NH","NJ","NM","NV","NY","OH","OK","OR","PA","RI","SC","SD","TN","TX","UT","VA","VT","WA","WI","WV","WY"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + vLocationTypeStatePropEnum = append(vLocationTypeStatePropEnum, v) + } +} + +const ( + + // VLocationStateAL captures enum value "AL" + VLocationStateAL string = "AL" + + // VLocationStateAK captures enum value "AK" + VLocationStateAK string = "AK" + + // VLocationStateAR captures enum value "AR" + VLocationStateAR string = "AR" + + // VLocationStateAZ captures enum value "AZ" + VLocationStateAZ string = "AZ" + + // VLocationStateCA captures enum value "CA" + VLocationStateCA string = "CA" + + // VLocationStateCO captures enum value "CO" + VLocationStateCO string = "CO" + + // VLocationStateCT captures enum value "CT" + VLocationStateCT string = "CT" + + // VLocationStateDC captures enum value "DC" + VLocationStateDC string = "DC" + + // VLocationStateDE captures enum value "DE" + VLocationStateDE string = "DE" + + // VLocationStateFL captures enum value "FL" + VLocationStateFL string = "FL" + + // VLocationStateGA captures enum value "GA" + VLocationStateGA string = "GA" + + // VLocationStateHI captures enum value "HI" + VLocationStateHI string = "HI" + + // VLocationStateIA captures enum value "IA" + VLocationStateIA string = "IA" + + // VLocationStateID captures enum value "ID" + VLocationStateID string = "ID" + + // VLocationStateIL captures enum value "IL" + VLocationStateIL string = "IL" + + // VLocationStateIN captures enum value "IN" + VLocationStateIN string = "IN" + + // VLocationStateKS captures enum value "KS" + VLocationStateKS string = "KS" + + // VLocationStateKY captures enum value "KY" + VLocationStateKY string = "KY" + + // VLocationStateLA captures enum value "LA" + VLocationStateLA string = "LA" + + // VLocationStateMA captures enum value "MA" + VLocationStateMA string = "MA" + + // VLocationStateMD captures enum value "MD" + VLocationStateMD string = "MD" + + // VLocationStateME captures enum value "ME" + VLocationStateME string = "ME" + + // VLocationStateMI captures enum value "MI" + VLocationStateMI string = "MI" + + // VLocationStateMN captures enum value "MN" + VLocationStateMN string = "MN" + + // VLocationStateMO captures enum value "MO" + VLocationStateMO string = "MO" + + // VLocationStateMS captures enum value "MS" + VLocationStateMS string = "MS" + + // VLocationStateMT captures enum value "MT" + VLocationStateMT string = "MT" + + // VLocationStateNC captures enum value "NC" + VLocationStateNC string = "NC" + + // VLocationStateND captures enum value "ND" + VLocationStateND string = "ND" + + // VLocationStateNE captures enum value "NE" + VLocationStateNE string = "NE" + + // VLocationStateNH captures enum value "NH" + VLocationStateNH string = "NH" + + // VLocationStateNJ captures enum value "NJ" + VLocationStateNJ string = "NJ" + + // VLocationStateNM captures enum value "NM" + VLocationStateNM string = "NM" + + // VLocationStateNV captures enum value "NV" + VLocationStateNV string = "NV" + + // VLocationStateNY captures enum value "NY" + VLocationStateNY string = "NY" + + // VLocationStateOH captures enum value "OH" + VLocationStateOH string = "OH" + + // VLocationStateOK captures enum value "OK" + VLocationStateOK string = "OK" + + // VLocationStateOR captures enum value "OR" + VLocationStateOR string = "OR" + + // VLocationStatePA captures enum value "PA" + VLocationStatePA string = "PA" + + // VLocationStateRI captures enum value "RI" + VLocationStateRI string = "RI" + + // VLocationStateSC captures enum value "SC" + VLocationStateSC string = "SC" + + // VLocationStateSD captures enum value "SD" + VLocationStateSD string = "SD" + + // VLocationStateTN captures enum value "TN" + VLocationStateTN string = "TN" + + // VLocationStateTX captures enum value "TX" + VLocationStateTX string = "TX" + + // VLocationStateUT captures enum value "UT" + VLocationStateUT string = "UT" + + // VLocationStateVA captures enum value "VA" + VLocationStateVA string = "VA" + + // VLocationStateVT captures enum value "VT" + VLocationStateVT string = "VT" + + // VLocationStateWA captures enum value "WA" + VLocationStateWA string = "WA" + + // VLocationStateWI captures enum value "WI" + VLocationStateWI string = "WI" + + // VLocationStateWV captures enum value "WV" + VLocationStateWV string = "WV" + + // VLocationStateWY captures enum value "WY" + VLocationStateWY string = "WY" +) + +// prop value enum +func (m *VLocation) validateStateEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, vLocationTypeStatePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *VLocation) validateState(formats strfmt.Registry) error { + if swag.IsZero(m.State) { // not required + return nil + } + + // value enum + if err := m.validateStateEnum("state", "body", m.State); err != nil { + return err + } + + return nil +} + +func (m *VLocation) validateUsPostRegionCitiesID(formats strfmt.Registry) error { + if swag.IsZero(m.UsPostRegionCitiesID) { // not required + return nil + } + + if err := validate.FormatOf("usPostRegionCitiesID", "body", "uuid", m.UsPostRegionCitiesID.String(), formats); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this v location based on context it is used +func (m *VLocation) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *VLocation) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *VLocation) UnmarshalBinary(b []byte) error { + var res VLocation + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/gen/primemessages/v_locations.go b/pkg/gen/primemessages/v_locations.go new file mode 100644 index 00000000000..caa019fc057 --- /dev/null +++ b/pkg/gen/primemessages/v_locations.go @@ -0,0 +1,78 @@ +// 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 ( + "context" + "strconv" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" +) + +// VLocations v locations +// +// swagger:model VLocations +type VLocations []*VLocation + +// Validate validates this v locations +func (m VLocations) 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 v locations based on the context it is used +func (m VLocations) 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/primev2api/embedded_spec.go b/pkg/gen/primev2api/embedded_spec.go index 9e0f66eb6c8..85afde7f3e3 100644 --- a/pkg/gen/primev2api/embedded_spec.go +++ b/pkg/gen/primev2api/embedded_spec.go @@ -1435,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": [ @@ -1507,6 +1551,172 @@ func init() { } ] }, + "MTOServiceItemInternationalDestSIT": { + "description": "Describes a international destination SIT service item. Subtype of a MTOServiceItem.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reServiceCode", + "sitEntryDate", + "reason" + ], + "properties": { + "dateOfContact1": { + "description": "Date of attempted contact by the prime corresponding to ` + "`" + `timeMilitary1` + "`" + `.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "dateOfContact2": { + "description": "Date of attempted contact by the prime corresponding to ` + "`" + `timeMilitary2` + "`" + `.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "firstAvailableDeliveryDate1": { + "description": "First available date that Prime can deliver SIT service item.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "firstAvailableDeliveryDate2": { + "description": "Second available date that Prime can deliver SIT service item.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "reServiceCode": { + "description": "Service code allowed for this model type.", + "type": "string", + "enum": [ + "IDFSIT", + "IDASIT" + ] + }, + "reason": { + "description": "The reason item has been placed in SIT.\n", + "type": "string", + "x-nullable": true, + "x-omitempty": false + }, + "sitCustomerContacted": { + "description": "Date when the customer contacted the prime for a delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "sitDepartureDate": { + "description": "Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "sitDestinationFinalAddress": { + "$ref": "#/definitions/Address" + }, + "sitEntryDate": { + "description": "Entry date for the SIT", + "type": "string", + "format": "date" + }, + "sitRequestedDelivery": { + "description": "Date when the customer has requested delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "timeMilitary1": { + "description": "Time of attempted contact corresponding to ` + "`" + `dateOfContact1` + "`" + `, in military format.", + "type": "string", + "pattern": "\\d{4}Z", + "x-nullable": true, + "example": "1400Z" + }, + "timeMilitary2": { + "description": "Time of attempted contact corresponding to ` + "`" + `dateOfContact2` + "`" + `, in military format.", + "type": "string", + "pattern": "\\d{4}Z", + "x-nullable": true, + "example": "1400Z" + } + } + } + ] + }, + "MTOServiceItemInternationalOriginSIT": { + "description": "Describes a international origin SIT service item. Subtype of a MTOServiceItem.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reServiceCode", + "reason", + "sitPostalCode", + "sitEntryDate" + ], + "properties": { + "reServiceCode": { + "description": "Service code allowed for this model type.", + "type": "string", + "enum": [ + "IOFSIT", + "IOASIT" + ] + }, + "reason": { + "description": "Explanation of why Prime is picking up SIT item.", + "type": "string", + "example": "Storage items need to be picked up" + }, + "requestApprovalsRequestedStatus": { + "type": "boolean" + }, + "sitCustomerContacted": { + "description": "Date when the customer contacted the prime for a delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "sitDepartureDate": { + "description": "Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "sitEntryDate": { + "description": "Entry date for the SIT", + "type": "string", + "format": "date" + }, + "sitHHGActualOrigin": { + "$ref": "#/definitions/Address" + }, + "sitHHGOriginalOrigin": { + "$ref": "#/definitions/Address" + }, + "sitPostalCode": { + "type": "string", + "format": "zip", + "pattern": "^(\\d{5}([\\-]\\d{4})?)$", + "example": "90210" + }, + "sitRequestedDelivery": { + "description": "Date when the customer has requested delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + } + } + } + ] + }, "MTOServiceItemInternationalShuttle": { "description": "Describes an international shuttle service item.", "allOf": [ @@ -1566,13 +1776,16 @@ 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 * IOFSIT, IOASIT - MTOServiceItemInternationalOriginSIT\n * IDFSIT, IDASIT - MTOServiceItemInternationalDestSIT\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", + "MTOServiceItemInternationalOriginSIT", + "MTOServiceItemInternationalDestSIT", "MTOServiceItemShuttle", + "MTOServiceItemDomesticShuttle", "MTOServiceItemInternationalShuttle", "MTOServiceItemDomesticCrating", "MTOServiceItemInternationalCrating", @@ -3179,7 +3392,7 @@ func init() { ] }, "UpdateMTOServiceItemModelType": { - "description": "Using this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DDDSIT - UpdateMTOServiceItemSIT\n * DOPSIT - UpdateMTOServiceItemSIT\n * DOASIT - UpdateMTOServiceItemSIT\n * DOFSIT - UpdateMTOServiceItemSIT\n * DDSHUT - UpdateMTOServiceItemShuttle\n * DOSHUT - UpdateMTOServiceItemShuttle\n * IDSHUT - UpdateMTOServiceItemInternationalShuttle\n * IOSHUT - UpdateMTOServiceItemInternationalShuttle\n\nThe documentation will then update with the supported fields.\n", + "description": "Using this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DDDSIT - UpdateMTOServiceItemSIT\n * DOPSIT - UpdateMTOServiceItemSIT\n * DOASIT - UpdateMTOServiceItemSIT\n * DOFSIT - UpdateMTOServiceItemSIT\n * IDDSIT - UpdateMTOServiceItemSIT\n * IOPSIT - UpdateMTOServiceItemSIT\n * IOASIT - UpdateMTOServiceItemSIT\n * IOFSIT - UpdateMTOServiceItemSIT\n * DDSHUT - UpdateMTOServiceItemShuttle\n * DOSHUT - UpdateMTOServiceItemShuttle\n * IDSHUT - UpdateMTOServiceItemInternationalShuttle\n * IOSHUT - UpdateMTOServiceItemInternationalShuttle\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "UpdateMTOServiceItemSIT", @@ -3227,7 +3440,11 @@ func init() { "DDDSIT", "DOPSIT", "DOASIT", - "DOFSIT" + "DOFSIT", + "IDDSIT", + "IOPSIT", + "IOASIT", + "IOFSIT" ] }, "requestApprovalsRequestedStatus": { @@ -5199,6 +5416,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": [ @@ -5271,6 +5532,172 @@ func init() { } ] }, + "MTOServiceItemInternationalDestSIT": { + "description": "Describes a international destination SIT service item. Subtype of a MTOServiceItem.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reServiceCode", + "sitEntryDate", + "reason" + ], + "properties": { + "dateOfContact1": { + "description": "Date of attempted contact by the prime corresponding to ` + "`" + `timeMilitary1` + "`" + `.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "dateOfContact2": { + "description": "Date of attempted contact by the prime corresponding to ` + "`" + `timeMilitary2` + "`" + `.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "firstAvailableDeliveryDate1": { + "description": "First available date that Prime can deliver SIT service item.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "firstAvailableDeliveryDate2": { + "description": "Second available date that Prime can deliver SIT service item.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "reServiceCode": { + "description": "Service code allowed for this model type.", + "type": "string", + "enum": [ + "IDFSIT", + "IDASIT" + ] + }, + "reason": { + "description": "The reason item has been placed in SIT.\n", + "type": "string", + "x-nullable": true, + "x-omitempty": false + }, + "sitCustomerContacted": { + "description": "Date when the customer contacted the prime for a delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "sitDepartureDate": { + "description": "Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "sitDestinationFinalAddress": { + "$ref": "#/definitions/Address" + }, + "sitEntryDate": { + "description": "Entry date for the SIT", + "type": "string", + "format": "date" + }, + "sitRequestedDelivery": { + "description": "Date when the customer has requested delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "timeMilitary1": { + "description": "Time of attempted contact corresponding to ` + "`" + `dateOfContact1` + "`" + `, in military format.", + "type": "string", + "pattern": "\\d{4}Z", + "x-nullable": true, + "example": "1400Z" + }, + "timeMilitary2": { + "description": "Time of attempted contact corresponding to ` + "`" + `dateOfContact2` + "`" + `, in military format.", + "type": "string", + "pattern": "\\d{4}Z", + "x-nullable": true, + "example": "1400Z" + } + } + } + ] + }, + "MTOServiceItemInternationalOriginSIT": { + "description": "Describes a international origin SIT service item. Subtype of a MTOServiceItem.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reServiceCode", + "reason", + "sitPostalCode", + "sitEntryDate" + ], + "properties": { + "reServiceCode": { + "description": "Service code allowed for this model type.", + "type": "string", + "enum": [ + "IOFSIT", + "IOASIT" + ] + }, + "reason": { + "description": "Explanation of why Prime is picking up SIT item.", + "type": "string", + "example": "Storage items need to be picked up" + }, + "requestApprovalsRequestedStatus": { + "type": "boolean" + }, + "sitCustomerContacted": { + "description": "Date when the customer contacted the prime for a delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "sitDepartureDate": { + "description": "Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "sitEntryDate": { + "description": "Entry date for the SIT", + "type": "string", + "format": "date" + }, + "sitHHGActualOrigin": { + "$ref": "#/definitions/Address" + }, + "sitHHGOriginalOrigin": { + "$ref": "#/definitions/Address" + }, + "sitPostalCode": { + "type": "string", + "format": "zip", + "pattern": "^(\\d{5}([\\-]\\d{4})?)$", + "example": "90210" + }, + "sitRequestedDelivery": { + "description": "Date when the customer has requested delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + } + } + } + ] + }, "MTOServiceItemInternationalShuttle": { "description": "Describes an international shuttle service item.", "allOf": [ @@ -5330,13 +5757,16 @@ 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 * IOFSIT, IOASIT - MTOServiceItemInternationalOriginSIT\n * IDFSIT, IDASIT - MTOServiceItemInternationalDestSIT\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", + "MTOServiceItemInternationalOriginSIT", + "MTOServiceItemInternationalDestSIT", "MTOServiceItemShuttle", + "MTOServiceItemDomesticShuttle", "MTOServiceItemInternationalShuttle", "MTOServiceItemDomesticCrating", "MTOServiceItemInternationalCrating", @@ -6945,7 +7375,7 @@ func init() { ] }, "UpdateMTOServiceItemModelType": { - "description": "Using this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DDDSIT - UpdateMTOServiceItemSIT\n * DOPSIT - UpdateMTOServiceItemSIT\n * DOASIT - UpdateMTOServiceItemSIT\n * DOFSIT - UpdateMTOServiceItemSIT\n * DDSHUT - UpdateMTOServiceItemShuttle\n * DOSHUT - UpdateMTOServiceItemShuttle\n * IDSHUT - UpdateMTOServiceItemInternationalShuttle\n * IOSHUT - UpdateMTOServiceItemInternationalShuttle\n\nThe documentation will then update with the supported fields.\n", + "description": "Using this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DDDSIT - UpdateMTOServiceItemSIT\n * DOPSIT - UpdateMTOServiceItemSIT\n * DOASIT - UpdateMTOServiceItemSIT\n * DOFSIT - UpdateMTOServiceItemSIT\n * IDDSIT - UpdateMTOServiceItemSIT\n * IOPSIT - UpdateMTOServiceItemSIT\n * IOASIT - UpdateMTOServiceItemSIT\n * IOFSIT - UpdateMTOServiceItemSIT\n * DDSHUT - UpdateMTOServiceItemShuttle\n * DOSHUT - UpdateMTOServiceItemShuttle\n * IDSHUT - UpdateMTOServiceItemInternationalShuttle\n * IOSHUT - UpdateMTOServiceItemInternationalShuttle\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "UpdateMTOServiceItemSIT", @@ -6993,7 +7423,11 @@ func init() { "DDDSIT", "DOPSIT", "DOASIT", - "DOFSIT" + "DOFSIT", + "IDDSIT", + "IOPSIT", + "IOASIT", + "IOFSIT" ] }, "requestApprovalsRequestedStatus": { diff --git a/pkg/gen/primev2messages/m_t_o_service_item.go b/pkg/gen/primev2messages/m_t_o_service_item.go index 7dfadf4c428..1cd148dfd39 100644 --- a/pkg/gen/primev2messages/m_t_o_service_item.go +++ b/pkg/gen/primev2messages/m_t_o_service_item.go @@ -273,12 +273,30 @@ 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 { return nil, err } return &result, nil + case "MTOServiceItemInternationalDestSIT": + var result MTOServiceItemInternationalDestSIT + if err := consumer.Consume(buf2, &result); err != nil { + return nil, err + } + return &result, nil + case "MTOServiceItemInternationalOriginSIT": + var result MTOServiceItemInternationalOriginSIT + if err := consumer.Consume(buf2, &result); err != nil { + return nil, err + } + return &result, nil case "MTOServiceItemInternationalShuttle": var result MTOServiceItemInternationalShuttle 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_international_dest_s_i_t.go b/pkg/gen/primev2messages/m_t_o_service_item_international_dest_s_i_t.go new file mode 100644 index 00000000000..fb503d39a43 --- /dev/null +++ b/pkg/gen/primev2messages/m_t_o_service_item_international_dest_s_i_t.go @@ -0,0 +1,987 @@ +// 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" +) + +// MTOServiceItemInternationalDestSIT Describes a international destination SIT service item. Subtype of a MTOServiceItem. +// +// swagger:model MTOServiceItemInternationalDestSIT +type MTOServiceItemInternationalDestSIT struct { + eTagField string + + idField strfmt.UUID + + lockedPriceCentsField *int64 + + moveTaskOrderIdField *strfmt.UUID + + mtoShipmentIdField strfmt.UUID + + reServiceNameField string + + rejectionReasonField *string + + serviceRequestDocumentsField ServiceRequestDocuments + + statusField MTOServiceItemStatus + + // Date of attempted contact by the prime corresponding to `timeMilitary1`. + // Format: date + DateOfContact1 *strfmt.Date `json:"dateOfContact1,omitempty"` + + // Date of attempted contact by the prime corresponding to `timeMilitary2`. + // Format: date + DateOfContact2 *strfmt.Date `json:"dateOfContact2,omitempty"` + + // First available date that Prime can deliver SIT service item. + // Format: date + FirstAvailableDeliveryDate1 *strfmt.Date `json:"firstAvailableDeliveryDate1,omitempty"` + + // Second available date that Prime can deliver SIT service item. + // Format: date + FirstAvailableDeliveryDate2 *strfmt.Date `json:"firstAvailableDeliveryDate2,omitempty"` + + // Service code allowed for this model type. + // Required: true + // Enum: [IDFSIT IDASIT] + ReServiceCode *string `json:"reServiceCode"` + + // The reason item has been placed in SIT. + // + // Required: true + Reason *string `json:"reason"` + + // Date when the customer contacted the prime for a delivery out of SIT. + // Format: date + SitCustomerContacted *strfmt.Date `json:"sitCustomerContacted,omitempty"` + + // Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date. + // Format: date + SitDepartureDate *strfmt.Date `json:"sitDepartureDate,omitempty"` + + // sit destination final address + SitDestinationFinalAddress *Address `json:"sitDestinationFinalAddress,omitempty"` + + // Entry date for the SIT + // Required: true + // Format: date + SitEntryDate *strfmt.Date `json:"sitEntryDate"` + + // Date when the customer has requested delivery out of SIT. + // Format: date + SitRequestedDelivery *strfmt.Date `json:"sitRequestedDelivery,omitempty"` + + // Time of attempted contact corresponding to `dateOfContact1`, in military format. + // Example: 1400Z + // Pattern: \d{4}Z + TimeMilitary1 *string `json:"timeMilitary1,omitempty"` + + // Time of attempted contact corresponding to `dateOfContact2`, in military format. + // Example: 1400Z + // Pattern: \d{4}Z + TimeMilitary2 *string `json:"timeMilitary2,omitempty"` +} + +// ETag gets the e tag of this subtype +func (m *MTOServiceItemInternationalDestSIT) ETag() string { + return m.eTagField +} + +// SetETag sets the e tag of this subtype +func (m *MTOServiceItemInternationalDestSIT) SetETag(val string) { + m.eTagField = val +} + +// ID gets the id of this subtype +func (m *MTOServiceItemInternationalDestSIT) ID() strfmt.UUID { + return m.idField +} + +// SetID sets the id of this subtype +func (m *MTOServiceItemInternationalDestSIT) SetID(val strfmt.UUID) { + m.idField = val +} + +// LockedPriceCents gets the locked price cents of this subtype +func (m *MTOServiceItemInternationalDestSIT) LockedPriceCents() *int64 { + return m.lockedPriceCentsField +} + +// SetLockedPriceCents sets the locked price cents of this subtype +func (m *MTOServiceItemInternationalDestSIT) SetLockedPriceCents(val *int64) { + m.lockedPriceCentsField = val +} + +// ModelType gets the model type of this subtype +func (m *MTOServiceItemInternationalDestSIT) ModelType() MTOServiceItemModelType { + return "MTOServiceItemInternationalDestSIT" +} + +// SetModelType sets the model type of this subtype +func (m *MTOServiceItemInternationalDestSIT) SetModelType(val MTOServiceItemModelType) { +} + +// MoveTaskOrderID gets the move task order ID of this subtype +func (m *MTOServiceItemInternationalDestSIT) MoveTaskOrderID() *strfmt.UUID { + return m.moveTaskOrderIdField +} + +// SetMoveTaskOrderID sets the move task order ID of this subtype +func (m *MTOServiceItemInternationalDestSIT) SetMoveTaskOrderID(val *strfmt.UUID) { + m.moveTaskOrderIdField = val +} + +// MtoShipmentID gets the mto shipment ID of this subtype +func (m *MTOServiceItemInternationalDestSIT) MtoShipmentID() strfmt.UUID { + return m.mtoShipmentIdField +} + +// SetMtoShipmentID sets the mto shipment ID of this subtype +func (m *MTOServiceItemInternationalDestSIT) SetMtoShipmentID(val strfmt.UUID) { + m.mtoShipmentIdField = val +} + +// ReServiceName gets the re service name of this subtype +func (m *MTOServiceItemInternationalDestSIT) ReServiceName() string { + return m.reServiceNameField +} + +// SetReServiceName sets the re service name of this subtype +func (m *MTOServiceItemInternationalDestSIT) SetReServiceName(val string) { + m.reServiceNameField = val +} + +// RejectionReason gets the rejection reason of this subtype +func (m *MTOServiceItemInternationalDestSIT) RejectionReason() *string { + return m.rejectionReasonField +} + +// SetRejectionReason sets the rejection reason of this subtype +func (m *MTOServiceItemInternationalDestSIT) SetRejectionReason(val *string) { + m.rejectionReasonField = val +} + +// ServiceRequestDocuments gets the service request documents of this subtype +func (m *MTOServiceItemInternationalDestSIT) ServiceRequestDocuments() ServiceRequestDocuments { + return m.serviceRequestDocumentsField +} + +// SetServiceRequestDocuments sets the service request documents of this subtype +func (m *MTOServiceItemInternationalDestSIT) SetServiceRequestDocuments(val ServiceRequestDocuments) { + m.serviceRequestDocumentsField = val +} + +// Status gets the status of this subtype +func (m *MTOServiceItemInternationalDestSIT) Status() MTOServiceItemStatus { + return m.statusField +} + +// SetStatus sets the status of this subtype +func (m *MTOServiceItemInternationalDestSIT) SetStatus(val MTOServiceItemStatus) { + m.statusField = val +} + +// UnmarshalJSON unmarshals this object with a polymorphic type from a JSON structure +func (m *MTOServiceItemInternationalDestSIT) UnmarshalJSON(raw []byte) error { + var data struct { + + // Date of attempted contact by the prime corresponding to `timeMilitary1`. + // Format: date + DateOfContact1 *strfmt.Date `json:"dateOfContact1,omitempty"` + + // Date of attempted contact by the prime corresponding to `timeMilitary2`. + // Format: date + DateOfContact2 *strfmt.Date `json:"dateOfContact2,omitempty"` + + // First available date that Prime can deliver SIT service item. + // Format: date + FirstAvailableDeliveryDate1 *strfmt.Date `json:"firstAvailableDeliveryDate1,omitempty"` + + // Second available date that Prime can deliver SIT service item. + // Format: date + FirstAvailableDeliveryDate2 *strfmt.Date `json:"firstAvailableDeliveryDate2,omitempty"` + + // Service code allowed for this model type. + // Required: true + // Enum: [IDFSIT IDASIT] + ReServiceCode *string `json:"reServiceCode"` + + // The reason item has been placed in SIT. + // + // Required: true + Reason *string `json:"reason"` + + // Date when the customer contacted the prime for a delivery out of SIT. + // Format: date + SitCustomerContacted *strfmt.Date `json:"sitCustomerContacted,omitempty"` + + // Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date. + // Format: date + SitDepartureDate *strfmt.Date `json:"sitDepartureDate,omitempty"` + + // sit destination final address + SitDestinationFinalAddress *Address `json:"sitDestinationFinalAddress,omitempty"` + + // Entry date for the SIT + // Required: true + // Format: date + SitEntryDate *strfmt.Date `json:"sitEntryDate"` + + // Date when the customer has requested delivery out of SIT. + // Format: date + SitRequestedDelivery *strfmt.Date `json:"sitRequestedDelivery,omitempty"` + + // Time of attempted contact corresponding to `dateOfContact1`, in military format. + // Example: 1400Z + // Pattern: \d{4}Z + TimeMilitary1 *string `json:"timeMilitary1,omitempty"` + + // Time of attempted contact corresponding to `dateOfContact2`, in military format. + // Example: 1400Z + // Pattern: \d{4}Z + TimeMilitary2 *string `json:"timeMilitary2,omitempty"` + } + 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 MTOServiceItemInternationalDestSIT + + 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.DateOfContact1 = data.DateOfContact1 + result.DateOfContact2 = data.DateOfContact2 + result.FirstAvailableDeliveryDate1 = data.FirstAvailableDeliveryDate1 + result.FirstAvailableDeliveryDate2 = data.FirstAvailableDeliveryDate2 + result.ReServiceCode = data.ReServiceCode + result.Reason = data.Reason + result.SitCustomerContacted = data.SitCustomerContacted + result.SitDepartureDate = data.SitDepartureDate + result.SitDestinationFinalAddress = data.SitDestinationFinalAddress + result.SitEntryDate = data.SitEntryDate + result.SitRequestedDelivery = data.SitRequestedDelivery + result.TimeMilitary1 = data.TimeMilitary1 + result.TimeMilitary2 = data.TimeMilitary2 + + *m = result + + return nil +} + +// MarshalJSON marshals this object with a polymorphic type to a JSON structure +func (m MTOServiceItemInternationalDestSIT) MarshalJSON() ([]byte, error) { + var b1, b2, b3 []byte + var err error + b1, err = json.Marshal(struct { + + // Date of attempted contact by the prime corresponding to `timeMilitary1`. + // Format: date + DateOfContact1 *strfmt.Date `json:"dateOfContact1,omitempty"` + + // Date of attempted contact by the prime corresponding to `timeMilitary2`. + // Format: date + DateOfContact2 *strfmt.Date `json:"dateOfContact2,omitempty"` + + // First available date that Prime can deliver SIT service item. + // Format: date + FirstAvailableDeliveryDate1 *strfmt.Date `json:"firstAvailableDeliveryDate1,omitempty"` + + // Second available date that Prime can deliver SIT service item. + // Format: date + FirstAvailableDeliveryDate2 *strfmt.Date `json:"firstAvailableDeliveryDate2,omitempty"` + + // Service code allowed for this model type. + // Required: true + // Enum: [IDFSIT IDASIT] + ReServiceCode *string `json:"reServiceCode"` + + // The reason item has been placed in SIT. + // + // Required: true + Reason *string `json:"reason"` + + // Date when the customer contacted the prime for a delivery out of SIT. + // Format: date + SitCustomerContacted *strfmt.Date `json:"sitCustomerContacted,omitempty"` + + // Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date. + // Format: date + SitDepartureDate *strfmt.Date `json:"sitDepartureDate,omitempty"` + + // sit destination final address + SitDestinationFinalAddress *Address `json:"sitDestinationFinalAddress,omitempty"` + + // Entry date for the SIT + // Required: true + // Format: date + SitEntryDate *strfmt.Date `json:"sitEntryDate"` + + // Date when the customer has requested delivery out of SIT. + // Format: date + SitRequestedDelivery *strfmt.Date `json:"sitRequestedDelivery,omitempty"` + + // Time of attempted contact corresponding to `dateOfContact1`, in military format. + // Example: 1400Z + // Pattern: \d{4}Z + TimeMilitary1 *string `json:"timeMilitary1,omitempty"` + + // Time of attempted contact corresponding to `dateOfContact2`, in military format. + // Example: 1400Z + // Pattern: \d{4}Z + TimeMilitary2 *string `json:"timeMilitary2,omitempty"` + }{ + + DateOfContact1: m.DateOfContact1, + + DateOfContact2: m.DateOfContact2, + + FirstAvailableDeliveryDate1: m.FirstAvailableDeliveryDate1, + + FirstAvailableDeliveryDate2: m.FirstAvailableDeliveryDate2, + + ReServiceCode: m.ReServiceCode, + + Reason: m.Reason, + + SitCustomerContacted: m.SitCustomerContacted, + + SitDepartureDate: m.SitDepartureDate, + + SitDestinationFinalAddress: m.SitDestinationFinalAddress, + + SitEntryDate: m.SitEntryDate, + + SitRequestedDelivery: m.SitRequestedDelivery, + + TimeMilitary1: m.TimeMilitary1, + + TimeMilitary2: m.TimeMilitary2, + }) + 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 international dest s i t +func (m *MTOServiceItemInternationalDestSIT) 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.validateDateOfContact1(formats); err != nil { + res = append(res, err) + } + + if err := m.validateDateOfContact2(formats); err != nil { + res = append(res, err) + } + + if err := m.validateFirstAvailableDeliveryDate1(formats); err != nil { + res = append(res, err) + } + + if err := m.validateFirstAvailableDeliveryDate2(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 err := m.validateSitCustomerContacted(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSitDepartureDate(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSitDestinationFinalAddress(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSitEntryDate(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSitRequestedDelivery(formats); err != nil { + res = append(res, err) + } + + if err := m.validateTimeMilitary1(formats); err != nil { + res = append(res, err) + } + + if err := m.validateTimeMilitary2(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) 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 +} + +func (m *MTOServiceItemInternationalDestSIT) validateDateOfContact1(formats strfmt.Registry) error { + + if swag.IsZero(m.DateOfContact1) { // not required + return nil + } + + if err := validate.FormatOf("dateOfContact1", "body", "date", m.DateOfContact1.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) validateDateOfContact2(formats strfmt.Registry) error { + + if swag.IsZero(m.DateOfContact2) { // not required + return nil + } + + if err := validate.FormatOf("dateOfContact2", "body", "date", m.DateOfContact2.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) validateFirstAvailableDeliveryDate1(formats strfmt.Registry) error { + + if swag.IsZero(m.FirstAvailableDeliveryDate1) { // not required + return nil + } + + if err := validate.FormatOf("firstAvailableDeliveryDate1", "body", "date", m.FirstAvailableDeliveryDate1.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) validateFirstAvailableDeliveryDate2(formats strfmt.Registry) error { + + if swag.IsZero(m.FirstAvailableDeliveryDate2) { // not required + return nil + } + + if err := validate.FormatOf("firstAvailableDeliveryDate2", "body", "date", m.FirstAvailableDeliveryDate2.String(), formats); err != nil { + return err + } + + return nil +} + +var mTOServiceItemInternationalDestSITTypeReServiceCodePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["IDFSIT","IDASIT"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + mTOServiceItemInternationalDestSITTypeReServiceCodePropEnum = append(mTOServiceItemInternationalDestSITTypeReServiceCodePropEnum, v) + } +} + +// property enum +func (m *MTOServiceItemInternationalDestSIT) validateReServiceCodeEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, mTOServiceItemInternationalDestSITTypeReServiceCodePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) validateReason(formats strfmt.Registry) error { + + if err := validate.Required("reason", "body", m.Reason); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) validateSitCustomerContacted(formats strfmt.Registry) error { + + if swag.IsZero(m.SitCustomerContacted) { // not required + return nil + } + + if err := validate.FormatOf("sitCustomerContacted", "body", "date", m.SitCustomerContacted.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) validateSitDepartureDate(formats strfmt.Registry) error { + + if swag.IsZero(m.SitDepartureDate) { // not required + return nil + } + + if err := validate.FormatOf("sitDepartureDate", "body", "date", m.SitDepartureDate.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) validateSitDestinationFinalAddress(formats strfmt.Registry) error { + + if swag.IsZero(m.SitDestinationFinalAddress) { // not required + return nil + } + + if m.SitDestinationFinalAddress != nil { + if err := m.SitDestinationFinalAddress.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("sitDestinationFinalAddress") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("sitDestinationFinalAddress") + } + return err + } + } + + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) validateSitEntryDate(formats strfmt.Registry) error { + + if err := validate.Required("sitEntryDate", "body", m.SitEntryDate); err != nil { + return err + } + + if err := validate.FormatOf("sitEntryDate", "body", "date", m.SitEntryDate.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) validateSitRequestedDelivery(formats strfmt.Registry) error { + + if swag.IsZero(m.SitRequestedDelivery) { // not required + return nil + } + + if err := validate.FormatOf("sitRequestedDelivery", "body", "date", m.SitRequestedDelivery.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) validateTimeMilitary1(formats strfmt.Registry) error { + + if swag.IsZero(m.TimeMilitary1) { // not required + return nil + } + + if err := validate.Pattern("timeMilitary1", "body", *m.TimeMilitary1, `\d{4}Z`); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) validateTimeMilitary2(formats strfmt.Registry) error { + + if swag.IsZero(m.TimeMilitary2) { // not required + return nil + } + + if err := validate.Pattern("timeMilitary2", "body", *m.TimeMilitary2, `\d{4}Z`); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this m t o service item international dest s i t based on the context it is used +func (m *MTOServiceItemInternationalDestSIT) 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 err := m.contextValidateSitDestinationFinalAddress(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) 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 +} + +func (m *MTOServiceItemInternationalDestSIT) contextValidateSitDestinationFinalAddress(ctx context.Context, formats strfmt.Registry) error { + + if m.SitDestinationFinalAddress != nil { + + if swag.IsZero(m.SitDestinationFinalAddress) { // not required + return nil + } + + if err := m.SitDestinationFinalAddress.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("sitDestinationFinalAddress") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("sitDestinationFinalAddress") + } + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (m *MTOServiceItemInternationalDestSIT) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *MTOServiceItemInternationalDestSIT) UnmarshalBinary(b []byte) error { + var res MTOServiceItemInternationalDestSIT + 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_international_origin_s_i_t.go b/pkg/gen/primev2messages/m_t_o_service_item_international_origin_s_i_t.go new file mode 100644 index 00000000000..37e22af66af --- /dev/null +++ b/pkg/gen/primev2messages/m_t_o_service_item_international_origin_s_i_t.go @@ -0,0 +1,900 @@ +// 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" +) + +// MTOServiceItemInternationalOriginSIT Describes a international origin SIT service item. Subtype of a MTOServiceItem. +// +// swagger:model MTOServiceItemInternationalOriginSIT +type MTOServiceItemInternationalOriginSIT struct { + eTagField string + + idField strfmt.UUID + + lockedPriceCentsField *int64 + + moveTaskOrderIdField *strfmt.UUID + + mtoShipmentIdField strfmt.UUID + + reServiceNameField string + + rejectionReasonField *string + + serviceRequestDocumentsField ServiceRequestDocuments + + statusField MTOServiceItemStatus + + // Service code allowed for this model type. + // Required: true + // Enum: [IOFSIT IOASIT] + ReServiceCode *string `json:"reServiceCode"` + + // Explanation of why Prime is picking up SIT item. + // Example: Storage items need to be picked up + // Required: true + Reason *string `json:"reason"` + + // request approvals requested status + RequestApprovalsRequestedStatus bool `json:"requestApprovalsRequestedStatus,omitempty"` + + // Date when the customer contacted the prime for a delivery out of SIT. + // Format: date + SitCustomerContacted *strfmt.Date `json:"sitCustomerContacted,omitempty"` + + // Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date. + // Format: date + SitDepartureDate *strfmt.Date `json:"sitDepartureDate,omitempty"` + + // Entry date for the SIT + // Required: true + // Format: date + SitEntryDate *strfmt.Date `json:"sitEntryDate"` + + // sit h h g actual origin + SitHHGActualOrigin *Address `json:"sitHHGActualOrigin,omitempty"` + + // sit h h g original origin + SitHHGOriginalOrigin *Address `json:"sitHHGOriginalOrigin,omitempty"` + + // sit postal code + // Example: 90210 + // Required: true + // Pattern: ^(\d{5}([\-]\d{4})?)$ + SitPostalCode *string `json:"sitPostalCode"` + + // Date when the customer has requested delivery out of SIT. + // Format: date + SitRequestedDelivery *strfmt.Date `json:"sitRequestedDelivery,omitempty"` +} + +// ETag gets the e tag of this subtype +func (m *MTOServiceItemInternationalOriginSIT) ETag() string { + return m.eTagField +} + +// SetETag sets the e tag of this subtype +func (m *MTOServiceItemInternationalOriginSIT) SetETag(val string) { + m.eTagField = val +} + +// ID gets the id of this subtype +func (m *MTOServiceItemInternationalOriginSIT) ID() strfmt.UUID { + return m.idField +} + +// SetID sets the id of this subtype +func (m *MTOServiceItemInternationalOriginSIT) SetID(val strfmt.UUID) { + m.idField = val +} + +// LockedPriceCents gets the locked price cents of this subtype +func (m *MTOServiceItemInternationalOriginSIT) LockedPriceCents() *int64 { + return m.lockedPriceCentsField +} + +// SetLockedPriceCents sets the locked price cents of this subtype +func (m *MTOServiceItemInternationalOriginSIT) SetLockedPriceCents(val *int64) { + m.lockedPriceCentsField = val +} + +// ModelType gets the model type of this subtype +func (m *MTOServiceItemInternationalOriginSIT) ModelType() MTOServiceItemModelType { + return "MTOServiceItemInternationalOriginSIT" +} + +// SetModelType sets the model type of this subtype +func (m *MTOServiceItemInternationalOriginSIT) SetModelType(val MTOServiceItemModelType) { +} + +// MoveTaskOrderID gets the move task order ID of this subtype +func (m *MTOServiceItemInternationalOriginSIT) MoveTaskOrderID() *strfmt.UUID { + return m.moveTaskOrderIdField +} + +// SetMoveTaskOrderID sets the move task order ID of this subtype +func (m *MTOServiceItemInternationalOriginSIT) SetMoveTaskOrderID(val *strfmt.UUID) { + m.moveTaskOrderIdField = val +} + +// MtoShipmentID gets the mto shipment ID of this subtype +func (m *MTOServiceItemInternationalOriginSIT) MtoShipmentID() strfmt.UUID { + return m.mtoShipmentIdField +} + +// SetMtoShipmentID sets the mto shipment ID of this subtype +func (m *MTOServiceItemInternationalOriginSIT) SetMtoShipmentID(val strfmt.UUID) { + m.mtoShipmentIdField = val +} + +// ReServiceName gets the re service name of this subtype +func (m *MTOServiceItemInternationalOriginSIT) ReServiceName() string { + return m.reServiceNameField +} + +// SetReServiceName sets the re service name of this subtype +func (m *MTOServiceItemInternationalOriginSIT) SetReServiceName(val string) { + m.reServiceNameField = val +} + +// RejectionReason gets the rejection reason of this subtype +func (m *MTOServiceItemInternationalOriginSIT) RejectionReason() *string { + return m.rejectionReasonField +} + +// SetRejectionReason sets the rejection reason of this subtype +func (m *MTOServiceItemInternationalOriginSIT) SetRejectionReason(val *string) { + m.rejectionReasonField = val +} + +// ServiceRequestDocuments gets the service request documents of this subtype +func (m *MTOServiceItemInternationalOriginSIT) ServiceRequestDocuments() ServiceRequestDocuments { + return m.serviceRequestDocumentsField +} + +// SetServiceRequestDocuments sets the service request documents of this subtype +func (m *MTOServiceItemInternationalOriginSIT) SetServiceRequestDocuments(val ServiceRequestDocuments) { + m.serviceRequestDocumentsField = val +} + +// Status gets the status of this subtype +func (m *MTOServiceItemInternationalOriginSIT) Status() MTOServiceItemStatus { + return m.statusField +} + +// SetStatus sets the status of this subtype +func (m *MTOServiceItemInternationalOriginSIT) SetStatus(val MTOServiceItemStatus) { + m.statusField = val +} + +// UnmarshalJSON unmarshals this object with a polymorphic type from a JSON structure +func (m *MTOServiceItemInternationalOriginSIT) UnmarshalJSON(raw []byte) error { + var data struct { + + // Service code allowed for this model type. + // Required: true + // Enum: [IOFSIT IOASIT] + ReServiceCode *string `json:"reServiceCode"` + + // Explanation of why Prime is picking up SIT item. + // Example: Storage items need to be picked up + // Required: true + Reason *string `json:"reason"` + + // request approvals requested status + RequestApprovalsRequestedStatus bool `json:"requestApprovalsRequestedStatus,omitempty"` + + // Date when the customer contacted the prime for a delivery out of SIT. + // Format: date + SitCustomerContacted *strfmt.Date `json:"sitCustomerContacted,omitempty"` + + // Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date. + // Format: date + SitDepartureDate *strfmt.Date `json:"sitDepartureDate,omitempty"` + + // Entry date for the SIT + // Required: true + // Format: date + SitEntryDate *strfmt.Date `json:"sitEntryDate"` + + // sit h h g actual origin + SitHHGActualOrigin *Address `json:"sitHHGActualOrigin,omitempty"` + + // sit h h g original origin + SitHHGOriginalOrigin *Address `json:"sitHHGOriginalOrigin,omitempty"` + + // sit postal code + // Example: 90210 + // Required: true + // Pattern: ^(\d{5}([\-]\d{4})?)$ + SitPostalCode *string `json:"sitPostalCode"` + + // Date when the customer has requested delivery out of SIT. + // Format: date + SitRequestedDelivery *strfmt.Date `json:"sitRequestedDelivery,omitempty"` + } + 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 MTOServiceItemInternationalOriginSIT + + 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.ReServiceCode = data.ReServiceCode + result.Reason = data.Reason + result.RequestApprovalsRequestedStatus = data.RequestApprovalsRequestedStatus + result.SitCustomerContacted = data.SitCustomerContacted + result.SitDepartureDate = data.SitDepartureDate + result.SitEntryDate = data.SitEntryDate + result.SitHHGActualOrigin = data.SitHHGActualOrigin + result.SitHHGOriginalOrigin = data.SitHHGOriginalOrigin + result.SitPostalCode = data.SitPostalCode + result.SitRequestedDelivery = data.SitRequestedDelivery + + *m = result + + return nil +} + +// MarshalJSON marshals this object with a polymorphic type to a JSON structure +func (m MTOServiceItemInternationalOriginSIT) MarshalJSON() ([]byte, error) { + var b1, b2, b3 []byte + var err error + b1, err = json.Marshal(struct { + + // Service code allowed for this model type. + // Required: true + // Enum: [IOFSIT IOASIT] + ReServiceCode *string `json:"reServiceCode"` + + // Explanation of why Prime is picking up SIT item. + // Example: Storage items need to be picked up + // Required: true + Reason *string `json:"reason"` + + // request approvals requested status + RequestApprovalsRequestedStatus bool `json:"requestApprovalsRequestedStatus,omitempty"` + + // Date when the customer contacted the prime for a delivery out of SIT. + // Format: date + SitCustomerContacted *strfmt.Date `json:"sitCustomerContacted,omitempty"` + + // Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date. + // Format: date + SitDepartureDate *strfmt.Date `json:"sitDepartureDate,omitempty"` + + // Entry date for the SIT + // Required: true + // Format: date + SitEntryDate *strfmt.Date `json:"sitEntryDate"` + + // sit h h g actual origin + SitHHGActualOrigin *Address `json:"sitHHGActualOrigin,omitempty"` + + // sit h h g original origin + SitHHGOriginalOrigin *Address `json:"sitHHGOriginalOrigin,omitempty"` + + // sit postal code + // Example: 90210 + // Required: true + // Pattern: ^(\d{5}([\-]\d{4})?)$ + SitPostalCode *string `json:"sitPostalCode"` + + // Date when the customer has requested delivery out of SIT. + // Format: date + SitRequestedDelivery *strfmt.Date `json:"sitRequestedDelivery,omitempty"` + }{ + + ReServiceCode: m.ReServiceCode, + + Reason: m.Reason, + + RequestApprovalsRequestedStatus: m.RequestApprovalsRequestedStatus, + + SitCustomerContacted: m.SitCustomerContacted, + + SitDepartureDate: m.SitDepartureDate, + + SitEntryDate: m.SitEntryDate, + + SitHHGActualOrigin: m.SitHHGActualOrigin, + + SitHHGOriginalOrigin: m.SitHHGOriginalOrigin, + + SitPostalCode: m.SitPostalCode, + + SitRequestedDelivery: m.SitRequestedDelivery, + }) + 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 international origin s i t +func (m *MTOServiceItemInternationalOriginSIT) 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 err := m.validateSitCustomerContacted(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSitDepartureDate(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSitEntryDate(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSitHHGActualOrigin(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSitHHGOriginalOrigin(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSitPostalCode(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSitRequestedDelivery(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) 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 mTOServiceItemInternationalOriginSITTypeReServiceCodePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["IOFSIT","IOASIT"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + mTOServiceItemInternationalOriginSITTypeReServiceCodePropEnum = append(mTOServiceItemInternationalOriginSITTypeReServiceCodePropEnum, v) + } +} + +// property enum +func (m *MTOServiceItemInternationalOriginSIT) validateReServiceCodeEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, mTOServiceItemInternationalOriginSITTypeReServiceCodePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) validateReason(formats strfmt.Registry) error { + + if err := validate.Required("reason", "body", m.Reason); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) validateSitCustomerContacted(formats strfmt.Registry) error { + + if swag.IsZero(m.SitCustomerContacted) { // not required + return nil + } + + if err := validate.FormatOf("sitCustomerContacted", "body", "date", m.SitCustomerContacted.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) validateSitDepartureDate(formats strfmt.Registry) error { + + if swag.IsZero(m.SitDepartureDate) { // not required + return nil + } + + if err := validate.FormatOf("sitDepartureDate", "body", "date", m.SitDepartureDate.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) validateSitEntryDate(formats strfmt.Registry) error { + + if err := validate.Required("sitEntryDate", "body", m.SitEntryDate); err != nil { + return err + } + + if err := validate.FormatOf("sitEntryDate", "body", "date", m.SitEntryDate.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) validateSitHHGActualOrigin(formats strfmt.Registry) error { + + if swag.IsZero(m.SitHHGActualOrigin) { // not required + return nil + } + + if m.SitHHGActualOrigin != nil { + if err := m.SitHHGActualOrigin.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("sitHHGActualOrigin") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("sitHHGActualOrigin") + } + return err + } + } + + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) validateSitHHGOriginalOrigin(formats strfmt.Registry) error { + + if swag.IsZero(m.SitHHGOriginalOrigin) { // not required + return nil + } + + if m.SitHHGOriginalOrigin != nil { + if err := m.SitHHGOriginalOrigin.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("sitHHGOriginalOrigin") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("sitHHGOriginalOrigin") + } + return err + } + } + + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) validateSitPostalCode(formats strfmt.Registry) error { + + if err := validate.Required("sitPostalCode", "body", m.SitPostalCode); err != nil { + return err + } + + if err := validate.Pattern("sitPostalCode", "body", *m.SitPostalCode, `^(\d{5}([\-]\d{4})?)$`); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) validateSitRequestedDelivery(formats strfmt.Registry) error { + + if swag.IsZero(m.SitRequestedDelivery) { // not required + return nil + } + + if err := validate.FormatOf("sitRequestedDelivery", "body", "date", m.SitRequestedDelivery.String(), formats); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this m t o service item international origin s i t based on the context it is used +func (m *MTOServiceItemInternationalOriginSIT) 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 err := m.contextValidateSitHHGActualOrigin(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateSitHHGOriginalOrigin(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) 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 +} + +func (m *MTOServiceItemInternationalOriginSIT) contextValidateSitHHGActualOrigin(ctx context.Context, formats strfmt.Registry) error { + + if m.SitHHGActualOrigin != nil { + + if swag.IsZero(m.SitHHGActualOrigin) { // not required + return nil + } + + if err := m.SitHHGActualOrigin.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("sitHHGActualOrigin") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("sitHHGActualOrigin") + } + return err + } + } + + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) contextValidateSitHHGOriginalOrigin(ctx context.Context, formats strfmt.Registry) error { + + if m.SitHHGOriginalOrigin != nil { + + if swag.IsZero(m.SitHHGOriginalOrigin) { // not required + return nil + } + + if err := m.SitHHGOriginalOrigin.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("sitHHGOriginalOrigin") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("sitHHGOriginalOrigin") + } + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (m *MTOServiceItemInternationalOriginSIT) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *MTOServiceItemInternationalOriginSIT) UnmarshalBinary(b []byte) error { + var res MTOServiceItemInternationalOriginSIT + 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..b443afec65b 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 @@ -19,7 +19,10 @@ import ( // Using this list, choose the correct modelType in the dropdown, corresponding to the service item type. // - DOFSIT, DOASIT - MTOServiceItemOriginSIT // - DDFSIT, DDASIT - MTOServiceItemDestSIT +// - IOFSIT, IOASIT - MTOServiceItemInternationalOriginSIT +// - IDFSIT, IDASIT - MTOServiceItemInternationalDestSIT // - DOSHUT, DDSHUT - MTOServiceItemShuttle +// - DOSHUT, DDSHUT - MTOServiceItemDomesticShuttle // - IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle // - DCRT, DUCRT - MTOServiceItemDomesticCrating // - ICRT, IUCRT - MTOServiceItemInternationalCrating @@ -50,9 +53,18 @@ const ( // MTOServiceItemModelTypeMTOServiceItemDestSIT captures enum value "MTOServiceItemDestSIT" MTOServiceItemModelTypeMTOServiceItemDestSIT MTOServiceItemModelType = "MTOServiceItemDestSIT" + // MTOServiceItemModelTypeMTOServiceItemInternationalOriginSIT captures enum value "MTOServiceItemInternationalOriginSIT" + MTOServiceItemModelTypeMTOServiceItemInternationalOriginSIT MTOServiceItemModelType = "MTOServiceItemInternationalOriginSIT" + + // MTOServiceItemModelTypeMTOServiceItemInternationalDestSIT captures enum value "MTOServiceItemInternationalDestSIT" + MTOServiceItemModelTypeMTOServiceItemInternationalDestSIT MTOServiceItemModelType = "MTOServiceItemInternationalDestSIT" + // 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 +83,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","MTOServiceItemInternationalOriginSIT","MTOServiceItemInternationalDestSIT","MTOServiceItemShuttle","MTOServiceItemDomesticShuttle","MTOServiceItemInternationalShuttle","MTOServiceItemDomesticCrating","MTOServiceItemInternationalCrating","MTOSerivceItemInternationalFuelSurcharge"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/primev2messages/update_m_t_o_service_item_model_type.go b/pkg/gen/primev2messages/update_m_t_o_service_item_model_type.go index 8b865cfec1f..ce2195310c9 100644 --- a/pkg/gen/primev2messages/update_m_t_o_service_item_model_type.go +++ b/pkg/gen/primev2messages/update_m_t_o_service_item_model_type.go @@ -19,6 +19,10 @@ import ( // - DOPSIT - UpdateMTOServiceItemSIT // - DOASIT - UpdateMTOServiceItemSIT // - DOFSIT - UpdateMTOServiceItemSIT +// - IDDSIT - UpdateMTOServiceItemSIT +// - IOPSIT - UpdateMTOServiceItemSIT +// - IOASIT - UpdateMTOServiceItemSIT +// - IOFSIT - UpdateMTOServiceItemSIT // - DDSHUT - UpdateMTOServiceItemShuttle // - DOSHUT - UpdateMTOServiceItemShuttle // - IDSHUT - UpdateMTOServiceItemInternationalShuttle diff --git a/pkg/gen/primev2messages/update_m_t_o_service_item_s_i_t.go b/pkg/gen/primev2messages/update_m_t_o_service_item_s_i_t.go index 558e84ba19c..9accd54333f 100644 --- a/pkg/gen/primev2messages/update_m_t_o_service_item_s_i_t.go +++ b/pkg/gen/primev2messages/update_m_t_o_service_item_s_i_t.go @@ -39,7 +39,7 @@ type UpdateMTOServiceItemSIT struct { FirstAvailableDeliveryDate2 *strfmt.Date `json:"firstAvailableDeliveryDate2,omitempty"` // Service code allowed for this model type. - // Enum: [DDDSIT DOPSIT DOASIT DOFSIT] + // Enum: [DDDSIT DOPSIT DOASIT DOFSIT IDDSIT IOPSIT IOASIT IOFSIT] ReServiceCode string `json:"reServiceCode,omitempty"` // Indicates if "Approvals Requested" status is being requested. @@ -123,7 +123,7 @@ func (m *UpdateMTOServiceItemSIT) UnmarshalJSON(raw []byte) error { FirstAvailableDeliveryDate2 *strfmt.Date `json:"firstAvailableDeliveryDate2,omitempty"` // Service code allowed for this model type. - // Enum: [DDDSIT DOPSIT DOASIT DOFSIT] + // Enum: [DDDSIT DOPSIT DOASIT DOFSIT IDDSIT IOPSIT IOASIT IOFSIT] ReServiceCode string `json:"reServiceCode,omitempty"` // Indicates if "Approvals Requested" status is being requested. @@ -242,7 +242,7 @@ func (m UpdateMTOServiceItemSIT) MarshalJSON() ([]byte, error) { FirstAvailableDeliveryDate2 *strfmt.Date `json:"firstAvailableDeliveryDate2,omitempty"` // Service code allowed for this model type. - // Enum: [DDDSIT DOPSIT DOASIT DOFSIT] + // Enum: [DDDSIT DOPSIT DOASIT DOFSIT IDDSIT IOPSIT IOASIT IOFSIT] ReServiceCode string `json:"reServiceCode,omitempty"` // Indicates if "Approvals Requested" status is being requested. @@ -471,7 +471,7 @@ var updateMTOServiceItemSITTypeReServiceCodePropEnum []interface{} func init() { var res []string - if err := json.Unmarshal([]byte(`["DDDSIT","DOPSIT","DOASIT","DOFSIT"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["DDDSIT","DOPSIT","DOASIT","DOFSIT","IDDSIT","IOPSIT","IOASIT","IOFSIT"]`), &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 e5bde117279..582a95400fd 100644 --- a/pkg/gen/primev3api/embedded_spec.go +++ b/pkg/gen/primev3api/embedded_spec.go @@ -1597,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": [ @@ -1669,6 +1713,102 @@ func init() { } ] }, + "MTOServiceItemInternationalDestSIT": { + "description": "Describes a international destination SIT service item. Subtype of a MTOServiceItem.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reServiceCode", + "sitEntryDate", + "reason" + ], + "properties": { + "dateOfContact1": { + "description": "Date of attempted contact by the prime corresponding to ` + "`" + `timeMilitary1` + "`" + `.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "dateOfContact2": { + "description": "Date of attempted contact by the prime corresponding to ` + "`" + `timeMilitary2` + "`" + `.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "firstAvailableDeliveryDate1": { + "description": "First available date that Prime can deliver SIT service item.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "firstAvailableDeliveryDate2": { + "description": "Second available date that Prime can deliver SIT service item.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "reServiceCode": { + "description": "Service code allowed for this model type.", + "type": "string", + "enum": [ + "IDFSIT", + "IDASIT" + ] + }, + "reason": { + "description": "The reason item has been placed in SIT.\n", + "type": "string", + "x-nullable": true, + "x-omitempty": false + }, + "sitCustomerContacted": { + "description": "Date when the customer contacted the prime for a delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "sitDepartureDate": { + "description": "Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "sitDestinationFinalAddress": { + "$ref": "#/definitions/Address" + }, + "sitEntryDate": { + "description": "Entry date for the SIT", + "type": "string", + "format": "date" + }, + "sitRequestedDelivery": { + "description": "Date when the customer has requested delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "timeMilitary1": { + "description": "Time of attempted contact corresponding to ` + "`" + `dateOfContact1` + "`" + `, in military format.", + "type": "string", + "pattern": "\\d{4}Z", + "x-nullable": true, + "example": "1400Z" + }, + "timeMilitary2": { + "description": "Time of attempted contact corresponding to ` + "`" + `dateOfContact2` + "`" + `, in military format.", + "type": "string", + "pattern": "\\d{4}Z", + "x-nullable": true, + "example": "1400Z" + } + } + } + ] + }, "MTOServiceItemInternationalFuelSurcharge": { "description": "Describes a international Port of Embarkation/Debarkation fuel surcharge service item subtype of a MTOServiceItem.", "allOf": [ @@ -1694,6 +1834,76 @@ func init() { } ] }, + "MTOServiceItemInternationalOriginSIT": { + "description": "Describes a international origin SIT service item. Subtype of a MTOServiceItem.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reServiceCode", + "reason", + "sitPostalCode", + "sitEntryDate" + ], + "properties": { + "reServiceCode": { + "description": "Service code allowed for this model type.", + "type": "string", + "enum": [ + "IOFSIT", + "IOASIT" + ] + }, + "reason": { + "description": "Explanation of why Prime is picking up SIT item.", + "type": "string", + "example": "Storage items need to be picked up" + }, + "requestApprovalsRequestedStatus": { + "type": "boolean" + }, + "sitCustomerContacted": { + "description": "Date when the customer contacted the prime for a delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "sitDepartureDate": { + "description": "Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "sitEntryDate": { + "description": "Entry date for the SIT", + "type": "string", + "format": "date" + }, + "sitHHGActualOrigin": { + "$ref": "#/definitions/Address" + }, + "sitHHGOriginalOrigin": { + "$ref": "#/definitions/Address" + }, + "sitPostalCode": { + "type": "string", + "format": "zip", + "pattern": "^(\\d{5}([\\-]\\d{4})?)$", + "example": "90210" + }, + "sitRequestedDelivery": { + "description": "Date when the customer has requested delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + } + } + } + ] + }, "MTOServiceItemInternationalShuttle": { "description": "Describes an international shuttle service item.", "allOf": [ @@ -1753,13 +1963,16 @@ 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 * IOFSIT, IOASIT - MTOServiceItemInternationalOriginSIT\n * IDFSIT, IDASIT - MTOServiceItemInternationalDestSIT\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", + "MTOServiceItemInternationalOriginSIT", + "MTOServiceItemInternationalDestSIT", "MTOServiceItemShuttle", + "MTOServiceItemDomesticShuttle", "MTOServiceItemInternationalShuttle", "MTOServiceItemDomesticCrating", "MTOServiceItemInternationalCrating", @@ -3797,7 +4010,7 @@ func init() { ] }, "UpdateMTOServiceItemModelType": { - "description": "Using this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DDDSIT - UpdateMTOServiceItemSIT\n * DOPSIT - UpdateMTOServiceItemSIT\n * DOASIT - UpdateMTOServiceItemSIT\n * DOFSIT - UpdateMTOServiceItemSIT\n * DDSHUT - UpdateMTOServiceItemShuttle\n * DOSHUT - UpdateMTOServiceItemShuttle\n * IDSHUT - UpdateMTOServiceItemInternationalShuttle\n * IOSHUT - UpdateMTOServiceItemInternationalShuttle\n\nThe documentation will then update with the supported fields.\n", + "description": "Using this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DDDSIT - UpdateMTOServiceItemSIT\n * DOPSIT - UpdateMTOServiceItemSIT\n * DOASIT - UpdateMTOServiceItemSIT\n * DOFSIT - UpdateMTOServiceItemSIT\n * IDDSIT - UpdateMTOServiceItemSIT\n * IOPSIT - UpdateMTOServiceItemSIT\n * IOASIT - UpdateMTOServiceItemSIT\n * IOFSIT - UpdateMTOServiceItemSIT\n * DDSHUT - UpdateMTOServiceItemShuttle\n * DOSHUT - UpdateMTOServiceItemShuttle\n * IDSHUT - UpdateMTOServiceItemInternationalShuttle\n * IOSHUT - UpdateMTOServiceItemInternationalShuttle\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "UpdateMTOServiceItemSIT", @@ -3845,7 +4058,11 @@ func init() { "DDDSIT", "DOPSIT", "DOASIT", - "DOFSIT" + "DOFSIT", + "IDDSIT", + "IOPSIT", + "IOASIT", + "IOFSIT" ] }, "requestApprovalsRequestedStatus": { @@ -6071,6 +6288,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": [ @@ -6143,6 +6404,102 @@ func init() { } ] }, + "MTOServiceItemInternationalDestSIT": { + "description": "Describes a international destination SIT service item. Subtype of a MTOServiceItem.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reServiceCode", + "sitEntryDate", + "reason" + ], + "properties": { + "dateOfContact1": { + "description": "Date of attempted contact by the prime corresponding to ` + "`" + `timeMilitary1` + "`" + `.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "dateOfContact2": { + "description": "Date of attempted contact by the prime corresponding to ` + "`" + `timeMilitary2` + "`" + `.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "firstAvailableDeliveryDate1": { + "description": "First available date that Prime can deliver SIT service item.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "firstAvailableDeliveryDate2": { + "description": "Second available date that Prime can deliver SIT service item.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "reServiceCode": { + "description": "Service code allowed for this model type.", + "type": "string", + "enum": [ + "IDFSIT", + "IDASIT" + ] + }, + "reason": { + "description": "The reason item has been placed in SIT.\n", + "type": "string", + "x-nullable": true, + "x-omitempty": false + }, + "sitCustomerContacted": { + "description": "Date when the customer contacted the prime for a delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "sitDepartureDate": { + "description": "Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "sitDestinationFinalAddress": { + "$ref": "#/definitions/Address" + }, + "sitEntryDate": { + "description": "Entry date for the SIT", + "type": "string", + "format": "date" + }, + "sitRequestedDelivery": { + "description": "Date when the customer has requested delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "timeMilitary1": { + "description": "Time of attempted contact corresponding to ` + "`" + `dateOfContact1` + "`" + `, in military format.", + "type": "string", + "pattern": "\\d{4}Z", + "x-nullable": true, + "example": "1400Z" + }, + "timeMilitary2": { + "description": "Time of attempted contact corresponding to ` + "`" + `dateOfContact2` + "`" + `, in military format.", + "type": "string", + "pattern": "\\d{4}Z", + "x-nullable": true, + "example": "1400Z" + } + } + } + ] + }, "MTOServiceItemInternationalFuelSurcharge": { "description": "Describes a international Port of Embarkation/Debarkation fuel surcharge service item subtype of a MTOServiceItem.", "allOf": [ @@ -6168,6 +6525,76 @@ func init() { } ] }, + "MTOServiceItemInternationalOriginSIT": { + "description": "Describes a international origin SIT service item. Subtype of a MTOServiceItem.", + "allOf": [ + { + "$ref": "#/definitions/MTOServiceItem" + }, + { + "type": "object", + "required": [ + "reServiceCode", + "reason", + "sitPostalCode", + "sitEntryDate" + ], + "properties": { + "reServiceCode": { + "description": "Service code allowed for this model type.", + "type": "string", + "enum": [ + "IOFSIT", + "IOASIT" + ] + }, + "reason": { + "description": "Explanation of why Prime is picking up SIT item.", + "type": "string", + "example": "Storage items need to be picked up" + }, + "requestApprovalsRequestedStatus": { + "type": "boolean" + }, + "sitCustomerContacted": { + "description": "Date when the customer contacted the prime for a delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "sitDepartureDate": { + "description": "Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date.", + "type": "string", + "format": "date", + "x-nullable": true + }, + "sitEntryDate": { + "description": "Entry date for the SIT", + "type": "string", + "format": "date" + }, + "sitHHGActualOrigin": { + "$ref": "#/definitions/Address" + }, + "sitHHGOriginalOrigin": { + "$ref": "#/definitions/Address" + }, + "sitPostalCode": { + "type": "string", + "format": "zip", + "pattern": "^(\\d{5}([\\-]\\d{4})?)$", + "example": "90210" + }, + "sitRequestedDelivery": { + "description": "Date when the customer has requested delivery out of SIT.", + "type": "string", + "format": "date", + "x-nullable": true + } + } + } + ] + }, "MTOServiceItemInternationalShuttle": { "description": "Describes an international shuttle service item.", "allOf": [ @@ -6227,13 +6654,16 @@ 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 * IOFSIT, IOASIT - MTOServiceItemInternationalOriginSIT\n * IDFSIT, IDASIT - MTOServiceItemInternationalDestSIT\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", + "MTOServiceItemInternationalOriginSIT", + "MTOServiceItemInternationalDestSIT", "MTOServiceItemShuttle", + "MTOServiceItemDomesticShuttle", "MTOServiceItemInternationalShuttle", "MTOServiceItemDomesticCrating", "MTOServiceItemInternationalCrating", @@ -8273,7 +8703,7 @@ func init() { ] }, "UpdateMTOServiceItemModelType": { - "description": "Using this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DDDSIT - UpdateMTOServiceItemSIT\n * DOPSIT - UpdateMTOServiceItemSIT\n * DOASIT - UpdateMTOServiceItemSIT\n * DOFSIT - UpdateMTOServiceItemSIT\n * DDSHUT - UpdateMTOServiceItemShuttle\n * DOSHUT - UpdateMTOServiceItemShuttle\n * IDSHUT - UpdateMTOServiceItemInternationalShuttle\n * IOSHUT - UpdateMTOServiceItemInternationalShuttle\n\nThe documentation will then update with the supported fields.\n", + "description": "Using this list, choose the correct modelType in the dropdown, corresponding to the service item type.\n * DDDSIT - UpdateMTOServiceItemSIT\n * DOPSIT - UpdateMTOServiceItemSIT\n * DOASIT - UpdateMTOServiceItemSIT\n * DOFSIT - UpdateMTOServiceItemSIT\n * IDDSIT - UpdateMTOServiceItemSIT\n * IOPSIT - UpdateMTOServiceItemSIT\n * IOASIT - UpdateMTOServiceItemSIT\n * IOFSIT - UpdateMTOServiceItemSIT\n * DDSHUT - UpdateMTOServiceItemShuttle\n * DOSHUT - UpdateMTOServiceItemShuttle\n * IDSHUT - UpdateMTOServiceItemInternationalShuttle\n * IOSHUT - UpdateMTOServiceItemInternationalShuttle\n\nThe documentation will then update with the supported fields.\n", "type": "string", "enum": [ "UpdateMTOServiceItemSIT", @@ -8321,7 +8751,11 @@ func init() { "DDDSIT", "DOPSIT", "DOASIT", - "DOFSIT" + "DOFSIT", + "IDDSIT", + "IOPSIT", + "IOASIT", + "IOFSIT" ] }, "requestApprovalsRequestedStatus": { diff --git a/pkg/gen/primev3messages/m_t_o_service_item.go b/pkg/gen/primev3messages/m_t_o_service_item.go index 75d33c217f1..93880d33231 100644 --- a/pkg/gen/primev3messages/m_t_o_service_item.go +++ b/pkg/gen/primev3messages/m_t_o_service_item.go @@ -273,18 +273,36 @@ 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 { return nil, err } return &result, nil + case "MTOServiceItemInternationalDestSIT": + var result MTOServiceItemInternationalDestSIT + if err := consumer.Consume(buf2, &result); err != nil { + return nil, err + } + return &result, nil case "MTOServiceItemInternationalFuelSurcharge": var result MTOServiceItemInternationalFuelSurcharge if err := consumer.Consume(buf2, &result); err != nil { return nil, err } return &result, nil + case "MTOServiceItemInternationalOriginSIT": + var result MTOServiceItemInternationalOriginSIT + if err := consumer.Consume(buf2, &result); err != nil { + return nil, err + } + return &result, nil case "MTOServiceItemInternationalShuttle": var result MTOServiceItemInternationalShuttle 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_international_dest_s_i_t.go b/pkg/gen/primev3messages/m_t_o_service_item_international_dest_s_i_t.go new file mode 100644 index 00000000000..cd60e27e3d1 --- /dev/null +++ b/pkg/gen/primev3messages/m_t_o_service_item_international_dest_s_i_t.go @@ -0,0 +1,987 @@ +// 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" +) + +// MTOServiceItemInternationalDestSIT Describes a international destination SIT service item. Subtype of a MTOServiceItem. +// +// swagger:model MTOServiceItemInternationalDestSIT +type MTOServiceItemInternationalDestSIT struct { + eTagField string + + idField strfmt.UUID + + lockedPriceCentsField *int64 + + moveTaskOrderIdField *strfmt.UUID + + mtoShipmentIdField strfmt.UUID + + reServiceNameField string + + rejectionReasonField *string + + serviceRequestDocumentsField ServiceRequestDocuments + + statusField MTOServiceItemStatus + + // Date of attempted contact by the prime corresponding to `timeMilitary1`. + // Format: date + DateOfContact1 *strfmt.Date `json:"dateOfContact1,omitempty"` + + // Date of attempted contact by the prime corresponding to `timeMilitary2`. + // Format: date + DateOfContact2 *strfmt.Date `json:"dateOfContact2,omitempty"` + + // First available date that Prime can deliver SIT service item. + // Format: date + FirstAvailableDeliveryDate1 *strfmt.Date `json:"firstAvailableDeliveryDate1,omitempty"` + + // Second available date that Prime can deliver SIT service item. + // Format: date + FirstAvailableDeliveryDate2 *strfmt.Date `json:"firstAvailableDeliveryDate2,omitempty"` + + // Service code allowed for this model type. + // Required: true + // Enum: [IDFSIT IDASIT] + ReServiceCode *string `json:"reServiceCode"` + + // The reason item has been placed in SIT. + // + // Required: true + Reason *string `json:"reason"` + + // Date when the customer contacted the prime for a delivery out of SIT. + // Format: date + SitCustomerContacted *strfmt.Date `json:"sitCustomerContacted,omitempty"` + + // Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date. + // Format: date + SitDepartureDate *strfmt.Date `json:"sitDepartureDate,omitempty"` + + // sit destination final address + SitDestinationFinalAddress *Address `json:"sitDestinationFinalAddress,omitempty"` + + // Entry date for the SIT + // Required: true + // Format: date + SitEntryDate *strfmt.Date `json:"sitEntryDate"` + + // Date when the customer has requested delivery out of SIT. + // Format: date + SitRequestedDelivery *strfmt.Date `json:"sitRequestedDelivery,omitempty"` + + // Time of attempted contact corresponding to `dateOfContact1`, in military format. + // Example: 1400Z + // Pattern: \d{4}Z + TimeMilitary1 *string `json:"timeMilitary1,omitempty"` + + // Time of attempted contact corresponding to `dateOfContact2`, in military format. + // Example: 1400Z + // Pattern: \d{4}Z + TimeMilitary2 *string `json:"timeMilitary2,omitempty"` +} + +// ETag gets the e tag of this subtype +func (m *MTOServiceItemInternationalDestSIT) ETag() string { + return m.eTagField +} + +// SetETag sets the e tag of this subtype +func (m *MTOServiceItemInternationalDestSIT) SetETag(val string) { + m.eTagField = val +} + +// ID gets the id of this subtype +func (m *MTOServiceItemInternationalDestSIT) ID() strfmt.UUID { + return m.idField +} + +// SetID sets the id of this subtype +func (m *MTOServiceItemInternationalDestSIT) SetID(val strfmt.UUID) { + m.idField = val +} + +// LockedPriceCents gets the locked price cents of this subtype +func (m *MTOServiceItemInternationalDestSIT) LockedPriceCents() *int64 { + return m.lockedPriceCentsField +} + +// SetLockedPriceCents sets the locked price cents of this subtype +func (m *MTOServiceItemInternationalDestSIT) SetLockedPriceCents(val *int64) { + m.lockedPriceCentsField = val +} + +// ModelType gets the model type of this subtype +func (m *MTOServiceItemInternationalDestSIT) ModelType() MTOServiceItemModelType { + return "MTOServiceItemInternationalDestSIT" +} + +// SetModelType sets the model type of this subtype +func (m *MTOServiceItemInternationalDestSIT) SetModelType(val MTOServiceItemModelType) { +} + +// MoveTaskOrderID gets the move task order ID of this subtype +func (m *MTOServiceItemInternationalDestSIT) MoveTaskOrderID() *strfmt.UUID { + return m.moveTaskOrderIdField +} + +// SetMoveTaskOrderID sets the move task order ID of this subtype +func (m *MTOServiceItemInternationalDestSIT) SetMoveTaskOrderID(val *strfmt.UUID) { + m.moveTaskOrderIdField = val +} + +// MtoShipmentID gets the mto shipment ID of this subtype +func (m *MTOServiceItemInternationalDestSIT) MtoShipmentID() strfmt.UUID { + return m.mtoShipmentIdField +} + +// SetMtoShipmentID sets the mto shipment ID of this subtype +func (m *MTOServiceItemInternationalDestSIT) SetMtoShipmentID(val strfmt.UUID) { + m.mtoShipmentIdField = val +} + +// ReServiceName gets the re service name of this subtype +func (m *MTOServiceItemInternationalDestSIT) ReServiceName() string { + return m.reServiceNameField +} + +// SetReServiceName sets the re service name of this subtype +func (m *MTOServiceItemInternationalDestSIT) SetReServiceName(val string) { + m.reServiceNameField = val +} + +// RejectionReason gets the rejection reason of this subtype +func (m *MTOServiceItemInternationalDestSIT) RejectionReason() *string { + return m.rejectionReasonField +} + +// SetRejectionReason sets the rejection reason of this subtype +func (m *MTOServiceItemInternationalDestSIT) SetRejectionReason(val *string) { + m.rejectionReasonField = val +} + +// ServiceRequestDocuments gets the service request documents of this subtype +func (m *MTOServiceItemInternationalDestSIT) ServiceRequestDocuments() ServiceRequestDocuments { + return m.serviceRequestDocumentsField +} + +// SetServiceRequestDocuments sets the service request documents of this subtype +func (m *MTOServiceItemInternationalDestSIT) SetServiceRequestDocuments(val ServiceRequestDocuments) { + m.serviceRequestDocumentsField = val +} + +// Status gets the status of this subtype +func (m *MTOServiceItemInternationalDestSIT) Status() MTOServiceItemStatus { + return m.statusField +} + +// SetStatus sets the status of this subtype +func (m *MTOServiceItemInternationalDestSIT) SetStatus(val MTOServiceItemStatus) { + m.statusField = val +} + +// UnmarshalJSON unmarshals this object with a polymorphic type from a JSON structure +func (m *MTOServiceItemInternationalDestSIT) UnmarshalJSON(raw []byte) error { + var data struct { + + // Date of attempted contact by the prime corresponding to `timeMilitary1`. + // Format: date + DateOfContact1 *strfmt.Date `json:"dateOfContact1,omitempty"` + + // Date of attempted contact by the prime corresponding to `timeMilitary2`. + // Format: date + DateOfContact2 *strfmt.Date `json:"dateOfContact2,omitempty"` + + // First available date that Prime can deliver SIT service item. + // Format: date + FirstAvailableDeliveryDate1 *strfmt.Date `json:"firstAvailableDeliveryDate1,omitempty"` + + // Second available date that Prime can deliver SIT service item. + // Format: date + FirstAvailableDeliveryDate2 *strfmt.Date `json:"firstAvailableDeliveryDate2,omitempty"` + + // Service code allowed for this model type. + // Required: true + // Enum: [IDFSIT IDASIT] + ReServiceCode *string `json:"reServiceCode"` + + // The reason item has been placed in SIT. + // + // Required: true + Reason *string `json:"reason"` + + // Date when the customer contacted the prime for a delivery out of SIT. + // Format: date + SitCustomerContacted *strfmt.Date `json:"sitCustomerContacted,omitempty"` + + // Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date. + // Format: date + SitDepartureDate *strfmt.Date `json:"sitDepartureDate,omitempty"` + + // sit destination final address + SitDestinationFinalAddress *Address `json:"sitDestinationFinalAddress,omitempty"` + + // Entry date for the SIT + // Required: true + // Format: date + SitEntryDate *strfmt.Date `json:"sitEntryDate"` + + // Date when the customer has requested delivery out of SIT. + // Format: date + SitRequestedDelivery *strfmt.Date `json:"sitRequestedDelivery,omitempty"` + + // Time of attempted contact corresponding to `dateOfContact1`, in military format. + // Example: 1400Z + // Pattern: \d{4}Z + TimeMilitary1 *string `json:"timeMilitary1,omitempty"` + + // Time of attempted contact corresponding to `dateOfContact2`, in military format. + // Example: 1400Z + // Pattern: \d{4}Z + TimeMilitary2 *string `json:"timeMilitary2,omitempty"` + } + 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 MTOServiceItemInternationalDestSIT + + 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.DateOfContact1 = data.DateOfContact1 + result.DateOfContact2 = data.DateOfContact2 + result.FirstAvailableDeliveryDate1 = data.FirstAvailableDeliveryDate1 + result.FirstAvailableDeliveryDate2 = data.FirstAvailableDeliveryDate2 + result.ReServiceCode = data.ReServiceCode + result.Reason = data.Reason + result.SitCustomerContacted = data.SitCustomerContacted + result.SitDepartureDate = data.SitDepartureDate + result.SitDestinationFinalAddress = data.SitDestinationFinalAddress + result.SitEntryDate = data.SitEntryDate + result.SitRequestedDelivery = data.SitRequestedDelivery + result.TimeMilitary1 = data.TimeMilitary1 + result.TimeMilitary2 = data.TimeMilitary2 + + *m = result + + return nil +} + +// MarshalJSON marshals this object with a polymorphic type to a JSON structure +func (m MTOServiceItemInternationalDestSIT) MarshalJSON() ([]byte, error) { + var b1, b2, b3 []byte + var err error + b1, err = json.Marshal(struct { + + // Date of attempted contact by the prime corresponding to `timeMilitary1`. + // Format: date + DateOfContact1 *strfmt.Date `json:"dateOfContact1,omitempty"` + + // Date of attempted contact by the prime corresponding to `timeMilitary2`. + // Format: date + DateOfContact2 *strfmt.Date `json:"dateOfContact2,omitempty"` + + // First available date that Prime can deliver SIT service item. + // Format: date + FirstAvailableDeliveryDate1 *strfmt.Date `json:"firstAvailableDeliveryDate1,omitempty"` + + // Second available date that Prime can deliver SIT service item. + // Format: date + FirstAvailableDeliveryDate2 *strfmt.Date `json:"firstAvailableDeliveryDate2,omitempty"` + + // Service code allowed for this model type. + // Required: true + // Enum: [IDFSIT IDASIT] + ReServiceCode *string `json:"reServiceCode"` + + // The reason item has been placed in SIT. + // + // Required: true + Reason *string `json:"reason"` + + // Date when the customer contacted the prime for a delivery out of SIT. + // Format: date + SitCustomerContacted *strfmt.Date `json:"sitCustomerContacted,omitempty"` + + // Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date. + // Format: date + SitDepartureDate *strfmt.Date `json:"sitDepartureDate,omitempty"` + + // sit destination final address + SitDestinationFinalAddress *Address `json:"sitDestinationFinalAddress,omitempty"` + + // Entry date for the SIT + // Required: true + // Format: date + SitEntryDate *strfmt.Date `json:"sitEntryDate"` + + // Date when the customer has requested delivery out of SIT. + // Format: date + SitRequestedDelivery *strfmt.Date `json:"sitRequestedDelivery,omitempty"` + + // Time of attempted contact corresponding to `dateOfContact1`, in military format. + // Example: 1400Z + // Pattern: \d{4}Z + TimeMilitary1 *string `json:"timeMilitary1,omitempty"` + + // Time of attempted contact corresponding to `dateOfContact2`, in military format. + // Example: 1400Z + // Pattern: \d{4}Z + TimeMilitary2 *string `json:"timeMilitary2,omitempty"` + }{ + + DateOfContact1: m.DateOfContact1, + + DateOfContact2: m.DateOfContact2, + + FirstAvailableDeliveryDate1: m.FirstAvailableDeliveryDate1, + + FirstAvailableDeliveryDate2: m.FirstAvailableDeliveryDate2, + + ReServiceCode: m.ReServiceCode, + + Reason: m.Reason, + + SitCustomerContacted: m.SitCustomerContacted, + + SitDepartureDate: m.SitDepartureDate, + + SitDestinationFinalAddress: m.SitDestinationFinalAddress, + + SitEntryDate: m.SitEntryDate, + + SitRequestedDelivery: m.SitRequestedDelivery, + + TimeMilitary1: m.TimeMilitary1, + + TimeMilitary2: m.TimeMilitary2, + }) + 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 international dest s i t +func (m *MTOServiceItemInternationalDestSIT) 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.validateDateOfContact1(formats); err != nil { + res = append(res, err) + } + + if err := m.validateDateOfContact2(formats); err != nil { + res = append(res, err) + } + + if err := m.validateFirstAvailableDeliveryDate1(formats); err != nil { + res = append(res, err) + } + + if err := m.validateFirstAvailableDeliveryDate2(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 err := m.validateSitCustomerContacted(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSitDepartureDate(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSitDestinationFinalAddress(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSitEntryDate(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSitRequestedDelivery(formats); err != nil { + res = append(res, err) + } + + if err := m.validateTimeMilitary1(formats); err != nil { + res = append(res, err) + } + + if err := m.validateTimeMilitary2(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) 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 +} + +func (m *MTOServiceItemInternationalDestSIT) validateDateOfContact1(formats strfmt.Registry) error { + + if swag.IsZero(m.DateOfContact1) { // not required + return nil + } + + if err := validate.FormatOf("dateOfContact1", "body", "date", m.DateOfContact1.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) validateDateOfContact2(formats strfmt.Registry) error { + + if swag.IsZero(m.DateOfContact2) { // not required + return nil + } + + if err := validate.FormatOf("dateOfContact2", "body", "date", m.DateOfContact2.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) validateFirstAvailableDeliveryDate1(formats strfmt.Registry) error { + + if swag.IsZero(m.FirstAvailableDeliveryDate1) { // not required + return nil + } + + if err := validate.FormatOf("firstAvailableDeliveryDate1", "body", "date", m.FirstAvailableDeliveryDate1.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) validateFirstAvailableDeliveryDate2(formats strfmt.Registry) error { + + if swag.IsZero(m.FirstAvailableDeliveryDate2) { // not required + return nil + } + + if err := validate.FormatOf("firstAvailableDeliveryDate2", "body", "date", m.FirstAvailableDeliveryDate2.String(), formats); err != nil { + return err + } + + return nil +} + +var mTOServiceItemInternationalDestSITTypeReServiceCodePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["IDFSIT","IDASIT"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + mTOServiceItemInternationalDestSITTypeReServiceCodePropEnum = append(mTOServiceItemInternationalDestSITTypeReServiceCodePropEnum, v) + } +} + +// property enum +func (m *MTOServiceItemInternationalDestSIT) validateReServiceCodeEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, mTOServiceItemInternationalDestSITTypeReServiceCodePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) validateReason(formats strfmt.Registry) error { + + if err := validate.Required("reason", "body", m.Reason); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) validateSitCustomerContacted(formats strfmt.Registry) error { + + if swag.IsZero(m.SitCustomerContacted) { // not required + return nil + } + + if err := validate.FormatOf("sitCustomerContacted", "body", "date", m.SitCustomerContacted.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) validateSitDepartureDate(formats strfmt.Registry) error { + + if swag.IsZero(m.SitDepartureDate) { // not required + return nil + } + + if err := validate.FormatOf("sitDepartureDate", "body", "date", m.SitDepartureDate.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) validateSitDestinationFinalAddress(formats strfmt.Registry) error { + + if swag.IsZero(m.SitDestinationFinalAddress) { // not required + return nil + } + + if m.SitDestinationFinalAddress != nil { + if err := m.SitDestinationFinalAddress.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("sitDestinationFinalAddress") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("sitDestinationFinalAddress") + } + return err + } + } + + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) validateSitEntryDate(formats strfmt.Registry) error { + + if err := validate.Required("sitEntryDate", "body", m.SitEntryDate); err != nil { + return err + } + + if err := validate.FormatOf("sitEntryDate", "body", "date", m.SitEntryDate.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) validateSitRequestedDelivery(formats strfmt.Registry) error { + + if swag.IsZero(m.SitRequestedDelivery) { // not required + return nil + } + + if err := validate.FormatOf("sitRequestedDelivery", "body", "date", m.SitRequestedDelivery.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) validateTimeMilitary1(formats strfmt.Registry) error { + + if swag.IsZero(m.TimeMilitary1) { // not required + return nil + } + + if err := validate.Pattern("timeMilitary1", "body", *m.TimeMilitary1, `\d{4}Z`); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) validateTimeMilitary2(formats strfmt.Registry) error { + + if swag.IsZero(m.TimeMilitary2) { // not required + return nil + } + + if err := validate.Pattern("timeMilitary2", "body", *m.TimeMilitary2, `\d{4}Z`); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this m t o service item international dest s i t based on the context it is used +func (m *MTOServiceItemInternationalDestSIT) 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 err := m.contextValidateSitDestinationFinalAddress(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) 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 *MTOServiceItemInternationalDestSIT) 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 +} + +func (m *MTOServiceItemInternationalDestSIT) contextValidateSitDestinationFinalAddress(ctx context.Context, formats strfmt.Registry) error { + + if m.SitDestinationFinalAddress != nil { + + if swag.IsZero(m.SitDestinationFinalAddress) { // not required + return nil + } + + if err := m.SitDestinationFinalAddress.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("sitDestinationFinalAddress") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("sitDestinationFinalAddress") + } + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (m *MTOServiceItemInternationalDestSIT) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *MTOServiceItemInternationalDestSIT) UnmarshalBinary(b []byte) error { + var res MTOServiceItemInternationalDestSIT + 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_international_origin_s_i_t.go b/pkg/gen/primev3messages/m_t_o_service_item_international_origin_s_i_t.go new file mode 100644 index 00000000000..de349763f20 --- /dev/null +++ b/pkg/gen/primev3messages/m_t_o_service_item_international_origin_s_i_t.go @@ -0,0 +1,900 @@ +// 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" +) + +// MTOServiceItemInternationalOriginSIT Describes a international origin SIT service item. Subtype of a MTOServiceItem. +// +// swagger:model MTOServiceItemInternationalOriginSIT +type MTOServiceItemInternationalOriginSIT struct { + eTagField string + + idField strfmt.UUID + + lockedPriceCentsField *int64 + + moveTaskOrderIdField *strfmt.UUID + + mtoShipmentIdField strfmt.UUID + + reServiceNameField string + + rejectionReasonField *string + + serviceRequestDocumentsField ServiceRequestDocuments + + statusField MTOServiceItemStatus + + // Service code allowed for this model type. + // Required: true + // Enum: [IOFSIT IOASIT] + ReServiceCode *string `json:"reServiceCode"` + + // Explanation of why Prime is picking up SIT item. + // Example: Storage items need to be picked up + // Required: true + Reason *string `json:"reason"` + + // request approvals requested status + RequestApprovalsRequestedStatus bool `json:"requestApprovalsRequestedStatus,omitempty"` + + // Date when the customer contacted the prime for a delivery out of SIT. + // Format: date + SitCustomerContacted *strfmt.Date `json:"sitCustomerContacted,omitempty"` + + // Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date. + // Format: date + SitDepartureDate *strfmt.Date `json:"sitDepartureDate,omitempty"` + + // Entry date for the SIT + // Required: true + // Format: date + SitEntryDate *strfmt.Date `json:"sitEntryDate"` + + // sit h h g actual origin + SitHHGActualOrigin *Address `json:"sitHHGActualOrigin,omitempty"` + + // sit h h g original origin + SitHHGOriginalOrigin *Address `json:"sitHHGOriginalOrigin,omitempty"` + + // sit postal code + // Example: 90210 + // Required: true + // Pattern: ^(\d{5}([\-]\d{4})?)$ + SitPostalCode *string `json:"sitPostalCode"` + + // Date when the customer has requested delivery out of SIT. + // Format: date + SitRequestedDelivery *strfmt.Date `json:"sitRequestedDelivery,omitempty"` +} + +// ETag gets the e tag of this subtype +func (m *MTOServiceItemInternationalOriginSIT) ETag() string { + return m.eTagField +} + +// SetETag sets the e tag of this subtype +func (m *MTOServiceItemInternationalOriginSIT) SetETag(val string) { + m.eTagField = val +} + +// ID gets the id of this subtype +func (m *MTOServiceItemInternationalOriginSIT) ID() strfmt.UUID { + return m.idField +} + +// SetID sets the id of this subtype +func (m *MTOServiceItemInternationalOriginSIT) SetID(val strfmt.UUID) { + m.idField = val +} + +// LockedPriceCents gets the locked price cents of this subtype +func (m *MTOServiceItemInternationalOriginSIT) LockedPriceCents() *int64 { + return m.lockedPriceCentsField +} + +// SetLockedPriceCents sets the locked price cents of this subtype +func (m *MTOServiceItemInternationalOriginSIT) SetLockedPriceCents(val *int64) { + m.lockedPriceCentsField = val +} + +// ModelType gets the model type of this subtype +func (m *MTOServiceItemInternationalOriginSIT) ModelType() MTOServiceItemModelType { + return "MTOServiceItemInternationalOriginSIT" +} + +// SetModelType sets the model type of this subtype +func (m *MTOServiceItemInternationalOriginSIT) SetModelType(val MTOServiceItemModelType) { +} + +// MoveTaskOrderID gets the move task order ID of this subtype +func (m *MTOServiceItemInternationalOriginSIT) MoveTaskOrderID() *strfmt.UUID { + return m.moveTaskOrderIdField +} + +// SetMoveTaskOrderID sets the move task order ID of this subtype +func (m *MTOServiceItemInternationalOriginSIT) SetMoveTaskOrderID(val *strfmt.UUID) { + m.moveTaskOrderIdField = val +} + +// MtoShipmentID gets the mto shipment ID of this subtype +func (m *MTOServiceItemInternationalOriginSIT) MtoShipmentID() strfmt.UUID { + return m.mtoShipmentIdField +} + +// SetMtoShipmentID sets the mto shipment ID of this subtype +func (m *MTOServiceItemInternationalOriginSIT) SetMtoShipmentID(val strfmt.UUID) { + m.mtoShipmentIdField = val +} + +// ReServiceName gets the re service name of this subtype +func (m *MTOServiceItemInternationalOriginSIT) ReServiceName() string { + return m.reServiceNameField +} + +// SetReServiceName sets the re service name of this subtype +func (m *MTOServiceItemInternationalOriginSIT) SetReServiceName(val string) { + m.reServiceNameField = val +} + +// RejectionReason gets the rejection reason of this subtype +func (m *MTOServiceItemInternationalOriginSIT) RejectionReason() *string { + return m.rejectionReasonField +} + +// SetRejectionReason sets the rejection reason of this subtype +func (m *MTOServiceItemInternationalOriginSIT) SetRejectionReason(val *string) { + m.rejectionReasonField = val +} + +// ServiceRequestDocuments gets the service request documents of this subtype +func (m *MTOServiceItemInternationalOriginSIT) ServiceRequestDocuments() ServiceRequestDocuments { + return m.serviceRequestDocumentsField +} + +// SetServiceRequestDocuments sets the service request documents of this subtype +func (m *MTOServiceItemInternationalOriginSIT) SetServiceRequestDocuments(val ServiceRequestDocuments) { + m.serviceRequestDocumentsField = val +} + +// Status gets the status of this subtype +func (m *MTOServiceItemInternationalOriginSIT) Status() MTOServiceItemStatus { + return m.statusField +} + +// SetStatus sets the status of this subtype +func (m *MTOServiceItemInternationalOriginSIT) SetStatus(val MTOServiceItemStatus) { + m.statusField = val +} + +// UnmarshalJSON unmarshals this object with a polymorphic type from a JSON structure +func (m *MTOServiceItemInternationalOriginSIT) UnmarshalJSON(raw []byte) error { + var data struct { + + // Service code allowed for this model type. + // Required: true + // Enum: [IOFSIT IOASIT] + ReServiceCode *string `json:"reServiceCode"` + + // Explanation of why Prime is picking up SIT item. + // Example: Storage items need to be picked up + // Required: true + Reason *string `json:"reason"` + + // request approvals requested status + RequestApprovalsRequestedStatus bool `json:"requestApprovalsRequestedStatus,omitempty"` + + // Date when the customer contacted the prime for a delivery out of SIT. + // Format: date + SitCustomerContacted *strfmt.Date `json:"sitCustomerContacted,omitempty"` + + // Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date. + // Format: date + SitDepartureDate *strfmt.Date `json:"sitDepartureDate,omitempty"` + + // Entry date for the SIT + // Required: true + // Format: date + SitEntryDate *strfmt.Date `json:"sitEntryDate"` + + // sit h h g actual origin + SitHHGActualOrigin *Address `json:"sitHHGActualOrigin,omitempty"` + + // sit h h g original origin + SitHHGOriginalOrigin *Address `json:"sitHHGOriginalOrigin,omitempty"` + + // sit postal code + // Example: 90210 + // Required: true + // Pattern: ^(\d{5}([\-]\d{4})?)$ + SitPostalCode *string `json:"sitPostalCode"` + + // Date when the customer has requested delivery out of SIT. + // Format: date + SitRequestedDelivery *strfmt.Date `json:"sitRequestedDelivery,omitempty"` + } + 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 MTOServiceItemInternationalOriginSIT + + 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.ReServiceCode = data.ReServiceCode + result.Reason = data.Reason + result.RequestApprovalsRequestedStatus = data.RequestApprovalsRequestedStatus + result.SitCustomerContacted = data.SitCustomerContacted + result.SitDepartureDate = data.SitDepartureDate + result.SitEntryDate = data.SitEntryDate + result.SitHHGActualOrigin = data.SitHHGActualOrigin + result.SitHHGOriginalOrigin = data.SitHHGOriginalOrigin + result.SitPostalCode = data.SitPostalCode + result.SitRequestedDelivery = data.SitRequestedDelivery + + *m = result + + return nil +} + +// MarshalJSON marshals this object with a polymorphic type to a JSON structure +func (m MTOServiceItemInternationalOriginSIT) MarshalJSON() ([]byte, error) { + var b1, b2, b3 []byte + var err error + b1, err = json.Marshal(struct { + + // Service code allowed for this model type. + // Required: true + // Enum: [IOFSIT IOASIT] + ReServiceCode *string `json:"reServiceCode"` + + // Explanation of why Prime is picking up SIT item. + // Example: Storage items need to be picked up + // Required: true + Reason *string `json:"reason"` + + // request approvals requested status + RequestApprovalsRequestedStatus bool `json:"requestApprovalsRequestedStatus,omitempty"` + + // Date when the customer contacted the prime for a delivery out of SIT. + // Format: date + SitCustomerContacted *strfmt.Date `json:"sitCustomerContacted,omitempty"` + + // Departure date for SIT. This is the end date of the SIT at either origin or destination. This is optional as it can be updated using the UpdateMTOServiceItemSIT modelType at a later date. + // Format: date + SitDepartureDate *strfmt.Date `json:"sitDepartureDate,omitempty"` + + // Entry date for the SIT + // Required: true + // Format: date + SitEntryDate *strfmt.Date `json:"sitEntryDate"` + + // sit h h g actual origin + SitHHGActualOrigin *Address `json:"sitHHGActualOrigin,omitempty"` + + // sit h h g original origin + SitHHGOriginalOrigin *Address `json:"sitHHGOriginalOrigin,omitempty"` + + // sit postal code + // Example: 90210 + // Required: true + // Pattern: ^(\d{5}([\-]\d{4})?)$ + SitPostalCode *string `json:"sitPostalCode"` + + // Date when the customer has requested delivery out of SIT. + // Format: date + SitRequestedDelivery *strfmt.Date `json:"sitRequestedDelivery,omitempty"` + }{ + + ReServiceCode: m.ReServiceCode, + + Reason: m.Reason, + + RequestApprovalsRequestedStatus: m.RequestApprovalsRequestedStatus, + + SitCustomerContacted: m.SitCustomerContacted, + + SitDepartureDate: m.SitDepartureDate, + + SitEntryDate: m.SitEntryDate, + + SitHHGActualOrigin: m.SitHHGActualOrigin, + + SitHHGOriginalOrigin: m.SitHHGOriginalOrigin, + + SitPostalCode: m.SitPostalCode, + + SitRequestedDelivery: m.SitRequestedDelivery, + }) + 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 international origin s i t +func (m *MTOServiceItemInternationalOriginSIT) 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 err := m.validateSitCustomerContacted(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSitDepartureDate(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSitEntryDate(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSitHHGActualOrigin(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSitHHGOriginalOrigin(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSitPostalCode(formats); err != nil { + res = append(res, err) + } + + if err := m.validateSitRequestedDelivery(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) 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 mTOServiceItemInternationalOriginSITTypeReServiceCodePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["IOFSIT","IOASIT"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + mTOServiceItemInternationalOriginSITTypeReServiceCodePropEnum = append(mTOServiceItemInternationalOriginSITTypeReServiceCodePropEnum, v) + } +} + +// property enum +func (m *MTOServiceItemInternationalOriginSIT) validateReServiceCodeEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, mTOServiceItemInternationalOriginSITTypeReServiceCodePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) validateReason(formats strfmt.Registry) error { + + if err := validate.Required("reason", "body", m.Reason); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) validateSitCustomerContacted(formats strfmt.Registry) error { + + if swag.IsZero(m.SitCustomerContacted) { // not required + return nil + } + + if err := validate.FormatOf("sitCustomerContacted", "body", "date", m.SitCustomerContacted.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) validateSitDepartureDate(formats strfmt.Registry) error { + + if swag.IsZero(m.SitDepartureDate) { // not required + return nil + } + + if err := validate.FormatOf("sitDepartureDate", "body", "date", m.SitDepartureDate.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) validateSitEntryDate(formats strfmt.Registry) error { + + if err := validate.Required("sitEntryDate", "body", m.SitEntryDate); err != nil { + return err + } + + if err := validate.FormatOf("sitEntryDate", "body", "date", m.SitEntryDate.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) validateSitHHGActualOrigin(formats strfmt.Registry) error { + + if swag.IsZero(m.SitHHGActualOrigin) { // not required + return nil + } + + if m.SitHHGActualOrigin != nil { + if err := m.SitHHGActualOrigin.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("sitHHGActualOrigin") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("sitHHGActualOrigin") + } + return err + } + } + + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) validateSitHHGOriginalOrigin(formats strfmt.Registry) error { + + if swag.IsZero(m.SitHHGOriginalOrigin) { // not required + return nil + } + + if m.SitHHGOriginalOrigin != nil { + if err := m.SitHHGOriginalOrigin.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("sitHHGOriginalOrigin") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("sitHHGOriginalOrigin") + } + return err + } + } + + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) validateSitPostalCode(formats strfmt.Registry) error { + + if err := validate.Required("sitPostalCode", "body", m.SitPostalCode); err != nil { + return err + } + + if err := validate.Pattern("sitPostalCode", "body", *m.SitPostalCode, `^(\d{5}([\-]\d{4})?)$`); err != nil { + return err + } + + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) validateSitRequestedDelivery(formats strfmt.Registry) error { + + if swag.IsZero(m.SitRequestedDelivery) { // not required + return nil + } + + if err := validate.FormatOf("sitRequestedDelivery", "body", "date", m.SitRequestedDelivery.String(), formats); err != nil { + return err + } + + return nil +} + +// ContextValidate validate this m t o service item international origin s i t based on the context it is used +func (m *MTOServiceItemInternationalOriginSIT) 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 err := m.contextValidateSitHHGActualOrigin(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateSitHHGOriginalOrigin(ctx, formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) 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 *MTOServiceItemInternationalOriginSIT) 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 +} + +func (m *MTOServiceItemInternationalOriginSIT) contextValidateSitHHGActualOrigin(ctx context.Context, formats strfmt.Registry) error { + + if m.SitHHGActualOrigin != nil { + + if swag.IsZero(m.SitHHGActualOrigin) { // not required + return nil + } + + if err := m.SitHHGActualOrigin.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("sitHHGActualOrigin") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("sitHHGActualOrigin") + } + return err + } + } + + return nil +} + +func (m *MTOServiceItemInternationalOriginSIT) contextValidateSitHHGOriginalOrigin(ctx context.Context, formats strfmt.Registry) error { + + if m.SitHHGOriginalOrigin != nil { + + if swag.IsZero(m.SitHHGOriginalOrigin) { // not required + return nil + } + + if err := m.SitHHGOriginalOrigin.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("sitHHGOriginalOrigin") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("sitHHGOriginalOrigin") + } + return err + } + } + + return nil +} + +// MarshalBinary interface implementation +func (m *MTOServiceItemInternationalOriginSIT) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *MTOServiceItemInternationalOriginSIT) UnmarshalBinary(b []byte) error { + var res MTOServiceItemInternationalOriginSIT + 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..7301906a417 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 @@ -19,7 +19,10 @@ import ( // Using this list, choose the correct modelType in the dropdown, corresponding to the service item type. // - DOFSIT, DOASIT - MTOServiceItemOriginSIT // - DDFSIT, DDASIT - MTOServiceItemDestSIT +// - IOFSIT, IOASIT - MTOServiceItemInternationalOriginSIT +// - IDFSIT, IDASIT - MTOServiceItemInternationalDestSIT // - DOSHUT, DDSHUT - MTOServiceItemShuttle +// - DOSHUT, DDSHUT - MTOServiceItemDomesticShuttle // - IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle // - DCRT, DUCRT - MTOServiceItemDomesticCrating // - ICRT, IUCRT - MTOServiceItemInternationalCrating @@ -50,9 +53,18 @@ const ( // MTOServiceItemModelTypeMTOServiceItemDestSIT captures enum value "MTOServiceItemDestSIT" MTOServiceItemModelTypeMTOServiceItemDestSIT MTOServiceItemModelType = "MTOServiceItemDestSIT" + // MTOServiceItemModelTypeMTOServiceItemInternationalOriginSIT captures enum value "MTOServiceItemInternationalOriginSIT" + MTOServiceItemModelTypeMTOServiceItemInternationalOriginSIT MTOServiceItemModelType = "MTOServiceItemInternationalOriginSIT" + + // MTOServiceItemModelTypeMTOServiceItemInternationalDestSIT captures enum value "MTOServiceItemInternationalDestSIT" + MTOServiceItemModelTypeMTOServiceItemInternationalDestSIT MTOServiceItemModelType = "MTOServiceItemInternationalDestSIT" + // 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 +83,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","MTOServiceItemInternationalOriginSIT","MTOServiceItemInternationalDestSIT","MTOServiceItemShuttle","MTOServiceItemDomesticShuttle","MTOServiceItemInternationalShuttle","MTOServiceItemDomesticCrating","MTOServiceItemInternationalCrating","MTOSerivceItemInternationalFuelSurcharge"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/primev3messages/update_m_t_o_service_item_model_type.go b/pkg/gen/primev3messages/update_m_t_o_service_item_model_type.go index a1bc8152ec6..e1ad72b1e4e 100644 --- a/pkg/gen/primev3messages/update_m_t_o_service_item_model_type.go +++ b/pkg/gen/primev3messages/update_m_t_o_service_item_model_type.go @@ -19,6 +19,10 @@ import ( // - DOPSIT - UpdateMTOServiceItemSIT // - DOASIT - UpdateMTOServiceItemSIT // - DOFSIT - UpdateMTOServiceItemSIT +// - IDDSIT - UpdateMTOServiceItemSIT +// - IOPSIT - UpdateMTOServiceItemSIT +// - IOASIT - UpdateMTOServiceItemSIT +// - IOFSIT - UpdateMTOServiceItemSIT // - DDSHUT - UpdateMTOServiceItemShuttle // - DOSHUT - UpdateMTOServiceItemShuttle // - IDSHUT - UpdateMTOServiceItemInternationalShuttle diff --git a/pkg/gen/primev3messages/update_m_t_o_service_item_s_i_t.go b/pkg/gen/primev3messages/update_m_t_o_service_item_s_i_t.go index 7012106e91e..ea432a5d501 100644 --- a/pkg/gen/primev3messages/update_m_t_o_service_item_s_i_t.go +++ b/pkg/gen/primev3messages/update_m_t_o_service_item_s_i_t.go @@ -39,7 +39,7 @@ type UpdateMTOServiceItemSIT struct { FirstAvailableDeliveryDate2 *strfmt.Date `json:"firstAvailableDeliveryDate2,omitempty"` // Service code allowed for this model type. - // Enum: [DDDSIT DOPSIT DOASIT DOFSIT] + // Enum: [DDDSIT DOPSIT DOASIT DOFSIT IDDSIT IOPSIT IOASIT IOFSIT] ReServiceCode string `json:"reServiceCode,omitempty"` // Indicates if "Approvals Requested" status is being requested. @@ -123,7 +123,7 @@ func (m *UpdateMTOServiceItemSIT) UnmarshalJSON(raw []byte) error { FirstAvailableDeliveryDate2 *strfmt.Date `json:"firstAvailableDeliveryDate2,omitempty"` // Service code allowed for this model type. - // Enum: [DDDSIT DOPSIT DOASIT DOFSIT] + // Enum: [DDDSIT DOPSIT DOASIT DOFSIT IDDSIT IOPSIT IOASIT IOFSIT] ReServiceCode string `json:"reServiceCode,omitempty"` // Indicates if "Approvals Requested" status is being requested. @@ -242,7 +242,7 @@ func (m UpdateMTOServiceItemSIT) MarshalJSON() ([]byte, error) { FirstAvailableDeliveryDate2 *strfmt.Date `json:"firstAvailableDeliveryDate2,omitempty"` // Service code allowed for this model type. - // Enum: [DDDSIT DOPSIT DOASIT DOFSIT] + // Enum: [DDDSIT DOPSIT DOASIT DOFSIT IDDSIT IOPSIT IOASIT IOFSIT] ReServiceCode string `json:"reServiceCode,omitempty"` // Indicates if "Approvals Requested" status is being requested. @@ -471,7 +471,7 @@ var updateMTOServiceItemSITTypeReServiceCodePropEnum []interface{} func init() { var res []string - if err := json.Unmarshal([]byte(`["DDDSIT","DOPSIT","DOASIT","DOFSIT"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["DDDSIT","DOPSIT","DOASIT","DOFSIT","IDDSIT","IOPSIT","IOASIT","IOFSIT"]`), &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 b794a05a20e..e906379f345 100644 --- a/pkg/gen/supportapi/embedded_spec.go +++ b/pkg/gen/supportapi/embedded_spec.go @@ -1536,14 +1536,114 @@ 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." + } + } + } + ] + }, + "MTOServiceItemInternationalShuttle": { + "description": "Describes an interntional 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 (IDSHUT \u0026 IOSHUT) 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 (IDSHUT \u0026 IOSHUT) service item.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4200 + }, + "market": { + "description": "To identify whether the service was provided within (CONUS) or (OCONUS)", + "type": "string", + "enum": [ + "CONUS", + "OCONUS" + ], + "example": "CONUS" + }, + "reServiceCode": { + "description": "Service codes allowed for this model type.", + "type": "string", + "enum": [ + "IOSHUT", + "IDSHUT" + ] + }, + "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 * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\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" @@ -4409,14 +4509,114 @@ 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." + } + } + } + ] + }, + "MTOServiceItemInternationalShuttle": { + "description": "Describes an interntional 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 (IDSHUT \u0026 IOSHUT) 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 (IDSHUT \u0026 IOSHUT) service item.", + "type": "integer", + "x-nullable": true, + "x-omitempty": false, + "example": 4200 + }, + "market": { + "description": "To identify whether the service was provided within (CONUS) or (OCONUS)", + "type": "string", + "enum": [ + "CONUS", + "OCONUS" + ], + "example": "CONUS" + }, + "reServiceCode": { + "description": "Service codes allowed for this model type.", + "type": "string", + "enum": [ + "IOSHUT", + "IDSHUT" + ] + }, + "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 * IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle\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/m_t_o_service_item.go b/pkg/gen/supportmessages/m_t_o_service_item.go index 23f35835eb2..4b3db62900d 100644 --- a/pkg/gen/supportmessages/m_t_o_service_item.go +++ b/pkg/gen/supportmessages/m_t_o_service_item.go @@ -239,6 +239,18 @@ 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 "MTOServiceItemInternationalShuttle": + var result MTOServiceItemInternationalShuttle + 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_international_shuttle.go b/pkg/gen/supportmessages/m_t_o_service_item_international_shuttle.go new file mode 100644 index 00000000000..06c7744abb2 --- /dev/null +++ b/pkg/gen/supportmessages/m_t_o_service_item_international_shuttle.go @@ -0,0 +1,577 @@ +// 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" +) + +// MTOServiceItemInternationalShuttle Describes an interntional shuttle service item. +// +// swagger:model MTOServiceItemInternationalShuttle +type MTOServiceItemInternationalShuttle 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 (IDSHUT & IOSHUT) service items. + // Example: 4000 + ActualWeight *int64 `json:"actualWeight"` + + // An estimate of how much weight from a shipment will be included in a shuttling (IDSHUT & IOSHUT) service item. + // Example: 4200 + EstimatedWeight *int64 `json:"estimatedWeight"` + + // To identify whether the service was provided within (CONUS) or (OCONUS) + // Example: CONUS + // Enum: [CONUS OCONUS] + Market string `json:"market,omitempty"` + + // Service codes allowed for this model type. + // Required: true + // Enum: [IOSHUT IDSHUT] + 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 *MTOServiceItemInternationalShuttle) ETag() string { + return m.eTagField +} + +// SetETag sets the e tag of this subtype +func (m *MTOServiceItemInternationalShuttle) SetETag(val string) { + m.eTagField = val +} + +// ID gets the id of this subtype +func (m *MTOServiceItemInternationalShuttle) ID() strfmt.UUID { + return m.idField +} + +// SetID sets the id of this subtype +func (m *MTOServiceItemInternationalShuttle) SetID(val strfmt.UUID) { + m.idField = val +} + +// ModelType gets the model type of this subtype +func (m *MTOServiceItemInternationalShuttle) ModelType() MTOServiceItemModelType { + return "MTOServiceItemInternationalShuttle" +} + +// SetModelType sets the model type of this subtype +func (m *MTOServiceItemInternationalShuttle) SetModelType(val MTOServiceItemModelType) { +} + +// MoveTaskOrderID gets the move task order ID of this subtype +func (m *MTOServiceItemInternationalShuttle) MoveTaskOrderID() *strfmt.UUID { + return m.moveTaskOrderIdField +} + +// SetMoveTaskOrderID sets the move task order ID of this subtype +func (m *MTOServiceItemInternationalShuttle) SetMoveTaskOrderID(val *strfmt.UUID) { + m.moveTaskOrderIdField = val +} + +// MtoShipmentID gets the mto shipment ID of this subtype +func (m *MTOServiceItemInternationalShuttle) MtoShipmentID() strfmt.UUID { + return m.mtoShipmentIdField +} + +// SetMtoShipmentID sets the mto shipment ID of this subtype +func (m *MTOServiceItemInternationalShuttle) SetMtoShipmentID(val strfmt.UUID) { + m.mtoShipmentIdField = val +} + +// ReServiceName gets the re service name of this subtype +func (m *MTOServiceItemInternationalShuttle) ReServiceName() string { + return m.reServiceNameField +} + +// SetReServiceName sets the re service name of this subtype +func (m *MTOServiceItemInternationalShuttle) SetReServiceName(val string) { + m.reServiceNameField = val +} + +// RejectionReason gets the rejection reason of this subtype +func (m *MTOServiceItemInternationalShuttle) RejectionReason() *string { + return m.rejectionReasonField +} + +// SetRejectionReason sets the rejection reason of this subtype +func (m *MTOServiceItemInternationalShuttle) SetRejectionReason(val *string) { + m.rejectionReasonField = val +} + +// Status gets the status of this subtype +func (m *MTOServiceItemInternationalShuttle) Status() MTOServiceItemStatus { + return m.statusField +} + +// SetStatus sets the status of this subtype +func (m *MTOServiceItemInternationalShuttle) SetStatus(val MTOServiceItemStatus) { + m.statusField = val +} + +// UnmarshalJSON unmarshals this object with a polymorphic type from a JSON structure +func (m *MTOServiceItemInternationalShuttle) UnmarshalJSON(raw []byte) error { + var data struct { + + // Provided by the movers, based on weight tickets. Relevant for shuttling (IDSHUT & IOSHUT) service items. + // Example: 4000 + ActualWeight *int64 `json:"actualWeight"` + + // An estimate of how much weight from a shipment will be included in a shuttling (IDSHUT & IOSHUT) service item. + // Example: 4200 + EstimatedWeight *int64 `json:"estimatedWeight"` + + // To identify whether the service was provided within (CONUS) or (OCONUS) + // Example: CONUS + // Enum: [CONUS OCONUS] + Market string `json:"market,omitempty"` + + // Service codes allowed for this model type. + // Required: true + // Enum: [IOSHUT IDSHUT] + 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 MTOServiceItemInternationalShuttle + + 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.Market = data.Market + 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 MTOServiceItemInternationalShuttle) 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 (IDSHUT & IOSHUT) service items. + // Example: 4000 + ActualWeight *int64 `json:"actualWeight"` + + // An estimate of how much weight from a shipment will be included in a shuttling (IDSHUT & IOSHUT) service item. + // Example: 4200 + EstimatedWeight *int64 `json:"estimatedWeight"` + + // To identify whether the service was provided within (CONUS) or (OCONUS) + // Example: CONUS + // Enum: [CONUS OCONUS] + Market string `json:"market,omitempty"` + + // Service codes allowed for this model type. + // Required: true + // Enum: [IOSHUT IDSHUT] + 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, + + Market: m.Market, + + 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 international shuttle +func (m *MTOServiceItemInternationalShuttle) 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.validateMarket(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 *MTOServiceItemInternationalShuttle) 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 *MTOServiceItemInternationalShuttle) 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 *MTOServiceItemInternationalShuttle) 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 *MTOServiceItemInternationalShuttle) 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 mTOServiceItemInternationalShuttleTypeMarketPropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["CONUS","OCONUS"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + mTOServiceItemInternationalShuttleTypeMarketPropEnum = append(mTOServiceItemInternationalShuttleTypeMarketPropEnum, v) + } +} + +// property enum +func (m *MTOServiceItemInternationalShuttle) validateMarketEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, mTOServiceItemInternationalShuttleTypeMarketPropEnum, true); err != nil { + return err + } + return nil +} + +func (m *MTOServiceItemInternationalShuttle) validateMarket(formats strfmt.Registry) error { + + if swag.IsZero(m.Market) { // not required + return nil + } + + // value enum + if err := m.validateMarketEnum("market", "body", m.Market); err != nil { + return err + } + + return nil +} + +var mTOServiceItemInternationalShuttleTypeReServiceCodePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["IOSHUT","IDSHUT"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + mTOServiceItemInternationalShuttleTypeReServiceCodePropEnum = append(mTOServiceItemInternationalShuttleTypeReServiceCodePropEnum, v) + } +} + +// property enum +func (m *MTOServiceItemInternationalShuttle) validateReServiceCodeEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, mTOServiceItemInternationalShuttleTypeReServiceCodePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *MTOServiceItemInternationalShuttle) 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 *MTOServiceItemInternationalShuttle) 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 international shuttle based on the context it is used +func (m *MTOServiceItemInternationalShuttle) 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 *MTOServiceItemInternationalShuttle) 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 *MTOServiceItemInternationalShuttle) 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 *MTOServiceItemInternationalShuttle) 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 *MTOServiceItemInternationalShuttle) 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 *MTOServiceItemInternationalShuttle) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *MTOServiceItemInternationalShuttle) UnmarshalBinary(b []byte) error { + var res MTOServiceItemInternationalShuttle + 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..52f3729cdab 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,8 @@ import ( // - DOFSIT, DOASIT - MTOServiceItemOriginSIT // - DDFSIT, DDASIT - MTOServiceItemDestSIT // - DOSHUT, DDSHUT - MTOServiceItemShuttle +// - DOSHUT, DDSHUT - MTOServiceItemDomesticShuttle +// - IOSHUT, IDSHUT - MTOServiceItemInternationalShuttle // - DCRT, DUCRT - MTOServiceItemDomesticCrating // - ICRT, IUCRT - MTOServiceItemInternationalCrating // @@ -51,6 +53,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 +71,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/apitests.go b/pkg/handlers/apitests.go index a84a6627f2c..a540d37e1f3 100644 --- a/pkg/handlers/apitests.go +++ b/pkg/handlers/apitests.go @@ -9,6 +9,7 @@ import ( "path" "path/filepath" "runtime/debug" + "strings" "time" "github.com/go-openapi/runtime" @@ -148,6 +149,11 @@ func (suite *BaseHandlerTestSuite) TestNotificationSender() notifications.Notifi return suite.notificationSender } +// TestNotificationReceiver returns the notification sender to use in the suite +func (suite *BaseHandlerTestSuite) TestNotificationReceiver() notifications.NotificationReceiver { + return notifications.NewStubNotificationReceiver() +} + // HasWebhookNotification checks that there's a record on the WebhookNotifications table for the object and trace IDs func (suite *BaseHandlerTestSuite) HasWebhookNotification(objectID uuid.UUID, traceID uuid.UUID) { notification := &models.WebhookNotification{} @@ -277,8 +283,12 @@ func (suite *BaseHandlerTestSuite) Fixture(name string) *runtime.File { if err != nil { suite.T().Error(err) } + cdRouting := "" + if strings.Contains(cwd, "routing") { + cdRouting = ".." + } - fixturePath := path.Join(cwd, "..", "..", fixtureDir, name) + fixturePath := path.Join(cwd, "..", "..", cdRouting, fixtureDir, name) file, err := os.Open(filepath.Clean(fixturePath)) if err != nil { diff --git a/pkg/handlers/authentication/auth.go b/pkg/handlers/authentication/auth.go index a01f499de5e..8e59132c750 100644 --- a/pkg/handlers/authentication/auth.go +++ b/pkg/handlers/authentication/auth.go @@ -221,6 +221,7 @@ var allowedRoutes = map[string]bool{ "uploads.deleteUpload": true, "users.showLoggedInUser": true, "okta_profile.showOktaInfo": true, + "uploads.getUploadStatus": true, } // checkIfRouteIsAllowed checks to see if the route is one of the ones that should be allowed through without stricter diff --git a/pkg/handlers/config.go b/pkg/handlers/config.go index b4bb2026915..50d45ee1978 100644 --- a/pkg/handlers/config.go +++ b/pkg/handlers/config.go @@ -39,6 +39,7 @@ type HandlerConfig interface { ) http.Handler FileStorer() storage.FileStorer NotificationSender() notifications.NotificationSender + NotificationReceiver() notifications.NotificationReceiver HHGPlanner() route.Planner DTODPlanner() route.Planner CookieSecret() string @@ -66,6 +67,7 @@ type Config struct { dtodPlanner route.Planner storage storage.FileStorer notificationSender notifications.NotificationSender + notificationReceiver notifications.NotificationReceiver iwsPersonLookup iws.PersonLookup sendProductionInvoice bool senderToGex services.GexSender @@ -86,6 +88,7 @@ func NewHandlerConfig( dtodPlanner route.Planner, storage storage.FileStorer, notificationSender notifications.NotificationSender, + notificationReceiver notifications.NotificationReceiver, iwsPersonLookup iws.PersonLookup, sendProductionInvoice bool, senderToGex services.GexSender, @@ -103,6 +106,7 @@ func NewHandlerConfig( dtodPlanner: dtodPlanner, storage: storage, notificationSender: notificationSender, + notificationReceiver: notificationReceiver, iwsPersonLookup: iwsPersonLookup, sendProductionInvoice: sendProductionInvoice, senderToGex: senderToGex, @@ -247,6 +251,16 @@ func (c *Config) SetNotificationSender(sender notifications.NotificationSender) c.notificationSender = sender } +// NotificationReceiver returns the sender to use in the current context +func (c *Config) NotificationReceiver() notifications.NotificationReceiver { + return c.notificationReceiver +} + +// SetNotificationSender is a simple setter for AWS SQS private field +func (c *Config) SetNotificationReceiver(receiver notifications.NotificationReceiver) { + c.notificationReceiver = receiver +} + // SetPlanner is a simple setter for the route.Planner private field func (c *Config) SetPlanner(planner route.Planner) { c.planner = planner diff --git a/pkg/handlers/config_test.go b/pkg/handlers/config_test.go index 26595daea29..85c9ccbff7c 100644 --- a/pkg/handlers/config_test.go +++ b/pkg/handlers/config_test.go @@ -30,7 +30,7 @@ func (suite *ConfigSuite) TestConfigHandler() { appCtx := suite.AppContextForTest() sessionManagers := auth.SetupSessionManagers(nil, false, time.Duration(180*time.Second), time.Duration(180*time.Second)) - handler := NewHandlerConfig(appCtx.DB(), nil, "", nil, nil, nil, nil, nil, false, nil, nil, false, ApplicationTestServername(), sessionManagers, nil) + handler := NewHandlerConfig(appCtx.DB(), nil, "", nil, nil, nil, nil, nil, nil, false, nil, nil, false, ApplicationTestServername(), sessionManagers, nil) req, err := http.NewRequest("GET", "/", nil) suite.NoError(err) myMethodCalled := false diff --git a/pkg/handlers/ghcapi/api.go b/pkg/handlers/ghcapi/api.go index 696148cba36..4402e196f67 100644 --- a/pkg/handlers/ghcapi/api.go +++ b/pkg/handlers/ghcapi/api.go @@ -4,6 +4,7 @@ import ( "log" "github.com/go-openapi/loads" + "github.com/go-openapi/runtime" "github.com/transcom/mymove/pkg/gen/ghcapi" ghcops "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations" @@ -83,9 +84,22 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { signedCertificationCreator := signedcertification.NewSignedCertificationCreator() signedCertificationUpdater := signedcertification.NewSignedCertificationUpdater() ppmEstimator := ppmshipment.NewEstimatePPM(handlerConfig.DTODPlanner(), &paymentrequesthelper.RequestPaymentHelper{}) + + mtoServiceItemCreator := mtoserviceitem.NewMTOServiceItemCreator( + handlerConfig.HHGPlanner(), + queryBuilder, + moveRouter, + ghcrateengine.NewDomesticUnpackPricer(), + ghcrateengine.NewDomesticPackPricer(), + ghcrateengine.NewDomesticLinehaulPricer(), + ghcrateengine.NewDomesticShorthaulPricer(), + ghcrateengine.NewDomesticOriginPricer(), + ghcrateengine.NewDomesticDestinationPricer(), + ghcrateengine.NewFuelSurchargePricer()) + moveTaskOrderUpdater := movetaskorder.NewMoveTaskOrderUpdater( queryBuilder, - mtoserviceitem.NewMTOServiceItemCreator(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()), + mtoServiceItemCreator, moveRouter, signedCertificationCreator, signedCertificationUpdater, ppmEstimator, ) @@ -234,7 +248,7 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { paymentRequestShipmentRecalculator, addressUpdater, addressCreator) - sitExtensionShipmentUpdater := shipment.NewShipmentUpdater(noCheckUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + sitExtensionShipmentUpdater := shipment.NewShipmentUpdater(noCheckUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, mtoServiceItemCreator) ghcAPI.MtoServiceItemUpdateServiceItemSitEntryDateHandler = UpdateServiceItemSitEntryDateHandler{ HandlerConfig: handlerConfig, @@ -246,7 +260,7 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { ghcAPI.MtoServiceItemUpdateMTOServiceItemStatusHandler = UpdateMTOServiceItemStatusHandler{ HandlerConfig: handlerConfig, - MTOServiceItemUpdater: mtoserviceitem.NewMTOServiceItemUpdater(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher), + MTOServiceItemUpdater: mtoserviceitem.NewMTOServiceItemUpdater(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()), Fetcher: fetch.NewFetcher(queryBuilder), ShipmentSITStatus: sitstatus.NewShipmentSITStatus(), MTOShipmentFetcher: mtoshipment.NewMTOShipmentFetcher(), @@ -428,7 +442,7 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { 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()), + mtoServiceItemCreator, handlerConfig.HHGPlanner(), move.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf), moveTaskOrderUpdater, @@ -499,7 +513,7 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { addressCreator, ) - shipmentUpdater := shipment.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipment.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, mtoServiceItemCreator) ghcAPI.MoveSearchMovesHandler = SearchMovesHandler{ HandlerConfig: handlerConfig, @@ -540,7 +554,7 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { ghcAPI.ShipmentUpdateSITServiceItemCustomerExpenseHandler = UpdateSITServiceItemCustomerExpenseHandler{ handlerConfig, - mtoserviceitem.NewMTOServiceItemUpdater(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher), + mtoserviceitem.NewMTOServiceItemUpdater(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()), mtoshipment.NewMTOShipmentFetcher(), shipmentSITStatus, } @@ -568,6 +582,13 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { officeusercreator.NewOfficeUserFetcherPop(), } + ghcAPI.QueuesGetDestinationRequestsQueueHandler = GetDestinationRequestsQueueHandler{ + handlerConfig, + order.NewOrderFetcher(waf), + movelocker.NewMoveUnlocker(), + officeusercreator.NewOfficeUserFetcherPop(), + } + ghcAPI.QueuesListPrimeMovesHandler = ListPrimeMovesHandler{ handlerConfig, movetaskorder.NewMoveTaskOrderFetcher(waf), @@ -705,6 +726,8 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { ghcAPI.UploadsCreateUploadHandler = CreateUploadHandler{handlerConfig} ghcAPI.UploadsUpdateUploadHandler = UpdateUploadHandler{handlerConfig, upload.NewUploadInformationFetcher()} ghcAPI.UploadsDeleteUploadHandler = DeleteUploadHandler{handlerConfig, upload.NewUploadInformationFetcher()} + ghcAPI.UploadsGetUploadStatusHandler = GetUploadStatusHandler{handlerConfig, upload.NewUploadInformationFetcher()} + ghcAPI.TextEventStreamProducer = runtime.ByteStreamProducer() // GetUploadStatus produces Event Stream ghcAPI.CustomerSearchCustomersHandler = SearchCustomersHandler{ HandlerConfig: handlerConfig, diff --git a/pkg/handlers/ghcapi/documents.go b/pkg/handlers/ghcapi/documents.go index b150eb2a5d3..bdbd0ad05cf 100644 --- a/pkg/handlers/ghcapi/documents.go +++ b/pkg/handlers/ghcapi/documents.go @@ -53,7 +53,7 @@ func (h GetDocumentHandler) Handle(params documentop.GetDocumentParams) middlewa return handlers.ResponseForError(appCtx.Logger(), err), err } - document, err := models.FetchDocument(appCtx.DB(), appCtx.Session(), documentID, true) + document, err := models.FetchDocument(appCtx.DB(), appCtx.Session(), documentID) if err != nil { return handlers.ResponseForError(appCtx.Logger(), err), err } diff --git a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go index 8a00f7814d5..0f20e7d767e 100644 --- a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go +++ b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go @@ -54,7 +54,7 @@ func OfficeUser(officeUser *models.OfficeUser) *ghcmessages.LockedOfficeUser { } func AssignedOfficeUser(officeUser *models.OfficeUser) *ghcmessages.AssignedOfficeUser { - if officeUser != nil { + if officeUser != nil && officeUser.FirstName != "" && officeUser.LastName != "" { payload := ghcmessages.AssignedOfficeUser{ OfficeUserID: strfmt.UUID(officeUser.ID.String()), FirstName: officeUser.FirstName, @@ -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 @@ -2073,10 +2075,10 @@ func Upload(storer storage.FileStorer, upload models.Upload, url string) *ghcmes } tags, err := storer.Tags(upload.StorageKey) - if err != nil || len(tags) == 0 { - uploadPayload.Status = "PROCESSING" + if err != nil { + uploadPayload.Status = string(models.AVStatusPROCESSING) } else { - uploadPayload.Status = tags["av-status"] + uploadPayload.Status = string(models.GetAVStatusFromTags(tags)) } return uploadPayload } @@ -2095,10 +2097,10 @@ func WeightTicketUpload(storer storage.FileStorer, upload models.Upload, url str IsWeightTicket: isWeightTicket, } tags, err := storer.Tags(upload.StorageKey) - if err != nil || len(tags) == 0 { - uploadPayload.Status = "PROCESSING" + if err != nil { + uploadPayload.Status = string(models.AVStatusPROCESSING) } else { - uploadPayload.Status = tags["av-status"] + uploadPayload.Status = string(models.GetAVStatusFromTags(tags)) } return uploadPayload } @@ -2151,10 +2153,10 @@ func PayloadForUploadModel( } tags, err := storer.Tags(upload.StorageKey) - if err != nil || len(tags) == 0 { - uploadPayload.Status = "PROCESSING" + if err != nil { + uploadPayload.Status = string(models.AVStatusPROCESSING) } else { - uploadPayload.Status = tags["av-status"] + uploadPayload.Status = string(models.GetAVStatusFromTags(tags)) } return uploadPayload } @@ -2761,6 +2763,30 @@ func SearchCustomers(customers models.ServiceMemberSearchResults) *ghcmessages.S return &searchCustomers } +// ReServiceItem payload +func ReServiceItem(reServiceItem *models.ReServiceItem) *ghcmessages.ReServiceItem { + if reServiceItem == nil || *reServiceItem == (models.ReServiceItem{}) { + return nil + } + return &ghcmessages.ReServiceItem{ + IsAutoApproved: reServiceItem.IsAutoApproved, + MarketCode: string(reServiceItem.MarketCode), + ServiceCode: string(reServiceItem.ReService.Code), + ShipmentType: string(reServiceItem.ShipmentType), + ServiceName: reServiceItem.ReService.Name, + } +} + +// ReServiceItems payload +func ReServiceItems(reServiceItems models.ReServiceItems) ghcmessages.ReServiceItems { + payload := make(ghcmessages.ReServiceItems, len(reServiceItems)) + for i, reServiceItem := range reServiceItems { + copyOfReServiceItem := reServiceItem + payload[i] = ReServiceItem(©OfReServiceItem) + } + return payload +} + // VLocation payload func VLocation(vLocation *models.VLocation) *ghcmessages.VLocation { if vLocation == nil { @@ -2789,30 +2815,6 @@ func VLocations(vLocations models.VLocations) ghcmessages.VLocations { return payload } -// ReServiceItem payload -func ReServiceItem(reServiceItem *models.ReServiceItem) *ghcmessages.ReServiceItem { - if reServiceItem == nil || *reServiceItem == (models.ReServiceItem{}) { - return nil - } - return &ghcmessages.ReServiceItem{ - IsAutoApproved: reServiceItem.IsAutoApproved, - MarketCode: string(reServiceItem.MarketCode), - ServiceCode: string(reServiceItem.ReService.Code), - ShipmentType: string(reServiceItem.ShipmentType), - ServiceName: reServiceItem.ReService.Name, - } -} - -// ReServiceItems payload -func ReServiceItems(reServiceItems models.ReServiceItems) ghcmessages.ReServiceItems { - payload := make(ghcmessages.ReServiceItems, len(reServiceItems)) - for i, reServiceItem := range reServiceItems { - copyOfReServiceItem := reServiceItem - payload[i] = ReServiceItem(©OfReServiceItem) - } - return payload -} - func Port(mtoServiceItems models.MTOServiceItems, portType string) *ghcmessages.Port { if mtoServiceItems == nil { return nil 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 e1f22b64ee8..5a9f2660d07 100644 --- a/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go +++ b/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go @@ -862,7 +862,6 @@ func (suite *PayloadsSuite) TestSearchMoves() { }, }, }, nil) - moves := models.Moves{moveUSMC} suite.Run("Success - Returns a ghcmessages Upload payload from Upload Struct Marine move with no shipments", func() { payload := SearchMoves(appCtx, moves) @@ -905,7 +904,7 @@ func (suite *PayloadsSuite) TestReServiceItem() { isAutoApproved := true marketCodeInternational := models.MarketCodeInternational reServiceCode := models.ReServiceCodePOEFSC - poefscServiceName := "International POE Fuel Surcharge" + poefscServiceName := "International POE fuel surcharge" reService := models.ReService{ Code: reServiceCode, Name: poefscServiceName, @@ -937,8 +936,8 @@ func (suite *PayloadsSuite) TestReServiceItems() { marketCodeDomestic := models.MarketCodeDomestic poefscReServiceCode := models.ReServiceCodePOEFSC podfscReServiceCode := models.ReServiceCodePODFSC - poefscServiceName := "International POE Fuel Surcharge" - podfscServiceName := "International POD Fuel Surcharge" + poefscServiceName := "International POE fuel surcharge" + podfscServiceName := "International POD fuel surcharge" poefscService := models.ReService{ Code: poefscReServiceCode, Name: poefscServiceName, @@ -1295,6 +1294,217 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { }) } +func (suite *PayloadsSuite) TestPort() { + + suite.Run("returns nil when PortLocation is nil", func() { + var mtoServiceItems models.MTOServiceItems = nil + result := Port(mtoServiceItems, "POE") + suite.Nil(result, "Expected result to be nil when Port Location is nil") + }) + + suite.Run("Success - Maps PortLocation to Port payload", func() { + // Use the factory to create a port location + portLocation := factory.FetchPortLocation(suite.DB(), []factory.Customization{ + { + Model: models.Port{ + PortCode: "PDX", + }, + }, + }, nil) + + mtoServiceItem := factory.BuildMTOServiceItem(nil, []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodePOEFSC, + }, + }, + { + Model: portLocation, + LinkOnly: true, + Type: &factory.PortLocations.PortOfEmbarkation, + }, + }, nil) + + // Actual + mtoServiceItems := models.MTOServiceItems{mtoServiceItem} + result := Port(mtoServiceItems, "POE") + + // Assert + suite.IsType(&ghcmessages.Port{}, result) + suite.Equal(strfmt.UUID(portLocation.ID.String()), result.ID) + suite.Equal(portLocation.Port.PortType.String(), result.PortType) + suite.Equal(portLocation.Port.PortCode, result.PortCode) + suite.Equal(portLocation.Port.PortName, result.PortName) + suite.Equal(portLocation.City.CityName, result.City) + suite.Equal(portLocation.UsPostRegionCity.UsprcCountyNm, result.County) + suite.Equal(portLocation.UsPostRegionCity.UsPostRegion.State.StateName, result.State) + suite.Equal(portLocation.UsPostRegionCity.UsprZipID, result.Zip) + suite.Equal(portLocation.Country.CountryName, result.Country) + }) +} + +func (suite *PayloadsSuite) TestMTOShipment_POE_POD_Locations() { + suite.Run("Only POE Location is set", func() { + // Create mock data for MTOServiceItems with POE and POD + poePortLocation := factory.FetchPortLocation(suite.DB(), []factory.Customization{ + { + Model: models.Port{ + PortCode: "PDX", + }, + }, + }, nil) + + poefscServiceItem := factory.BuildMTOServiceItem(nil, []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodePOEFSC, + Priority: 1, + }, + }, + { + Model: poePortLocation, + LinkOnly: true, + Type: &factory.PortLocations.PortOfEmbarkation, + }, + }, nil) + + mtoShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + MTOServiceItems: models.MTOServiceItems{poefscServiceItem}, + }, + }, + }, nil) + + payload := MTOShipment(nil, &mtoShipment, nil) + + // Assertions + suite.NotNil(payload, "Expected payload to not be nil") + suite.NotNil(payload.PoeLocation, "Expected POELocation to not be nil") + suite.Equal("PDX", payload.PoeLocation.PortCode, "Expected POE Port Code to match") + suite.Equal("PORTLAND INTL", payload.PoeLocation.PortName, "Expected POE Port Name to match") + suite.Nil(payload.PodLocation, "Expected PODLocation to be nil when POELocation is set") + }) + + suite.Run("Only POD Location is set", func() { + // Create mock data for MTOServiceItems with POE and POD + podPortLocation := factory.FetchPortLocation(suite.DB(), []factory.Customization{ + { + Model: models.Port{ + PortCode: "PDX", + }, + }, + }, nil) + + podfscServiceItem := factory.BuildMTOServiceItem(nil, []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodePODFSC, + Priority: 1, + }, + }, + { + Model: podPortLocation, + LinkOnly: true, + Type: &factory.PortLocations.PortOfDebarkation, + }, + }, nil) + + mtoShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + MTOServiceItems: models.MTOServiceItems{podfscServiceItem}, + }, + }, + }, nil) + + payload := MTOShipment(nil, &mtoShipment, nil) + + // Assertions + suite.NotNil(payload, "Expected payload to not be nil") + suite.NotNil(payload.PodLocation, "Expected PODLocation to not be nil") + suite.Equal("PDX", payload.PodLocation.PortCode, "Expected POD Port Code to match") + suite.Equal("PORTLAND INTL", payload.PodLocation.PortName, "Expected POD Port Name to match") + suite.Nil(payload.PoeLocation, "Expected PODLocation to be nil when PODLocation is set") + }) +} + +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) TestMTOShipment() { suite.Run("transforms standard MTOShipment without SIT overrides", func() { mtoShipment := factory.BuildMTOShipment(suite.DB(), nil, nil) @@ -1358,52 +1568,34 @@ func (suite *PayloadsSuite) TestMTOShipment() { }) } -func (suite *PayloadsSuite) TestPort() { - - suite.Run("returns nil when PortLocation is nil", func() { - var mtoServiceItems models.MTOServiceItems = nil - result := Port(mtoServiceItems, "POE") - suite.Nil(result, "Expected result to be nil when Port Location is nil") - }) - - suite.Run("Success - Maps PortLocation to Port payload", func() { - // Use the factory to create a port location - portLocation := factory.FetchPortLocation(suite.DB(), []factory.Customization{ +func (suite *PayloadsSuite) TestCounselingOffices() { + suite.Run("correctly maps transportaion offices to counseling offices payload", func() { + office1 := factory.BuildTransportationOffice(nil, []factory.Customization{ { - Model: models.Port{ - PortCode: "PDX", + Model: models.TransportationOffice{ + ID: uuid.Must(uuid.NewV4()), + Name: "PPPO Fort Liberty", }, }, }, nil) - mtoServiceItem := factory.BuildMTOServiceItem(nil, []factory.Customization{ + office2 := factory.BuildTransportationOffice(nil, []factory.Customization{ { - Model: models.ReService{ - Code: models.ReServiceCodePOEFSC, + Model: models.TransportationOffice{ + ID: uuid.Must(uuid.NewV4()), + Name: "PPPO Fort Walker", }, }, - { - Model: portLocation, - LinkOnly: true, - Type: &factory.PortLocations.PortOfEmbarkation, - }, }, nil) - // Actual - mtoServiceItems := models.MTOServiceItems{mtoServiceItem} - result := Port(mtoServiceItems, "POE") + offices := models.TransportationOffices{office1, office2} - // Assert - suite.IsType(&ghcmessages.Port{}, result) - suite.Equal(strfmt.UUID(portLocation.ID.String()), result.ID) - suite.Equal(portLocation.Port.PortType.String(), result.PortType) - suite.Equal(portLocation.Port.PortCode, result.PortCode) - suite.Equal(portLocation.Port.PortName, result.PortName) - suite.Equal(portLocation.City.CityName, result.City) - suite.Equal(portLocation.UsPostRegionCity.UsprcCountyNm, result.County) - suite.Equal(portLocation.UsPostRegionCity.UsPostRegion.State.StateName, result.State) - suite.Equal(portLocation.UsPostRegionCity.UsprZipID, result.Zip) - suite.Equal(portLocation.Country.CountryName, result.Country) + 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()) }) } @@ -1599,169 +1791,6 @@ func (suite *PayloadsSuite) TestMTOServiceItemSingleModel() { }) } -func (suite *PayloadsSuite) TestMTOShipment_POE_POD_Locations() { - suite.Run("Only POE Location is set", func() { - // Create mock data for MTOServiceItems with POE and POD - poePortLocation := factory.FetchPortLocation(suite.DB(), []factory.Customization{ - { - Model: models.Port{ - PortCode: "PDX", - }, - }, - }, nil) - - poefscServiceItem := factory.BuildMTOServiceItem(nil, []factory.Customization{ - { - Model: models.ReService{ - Code: models.ReServiceCodePOEFSC, - Priority: 1, - }, - }, - { - Model: poePortLocation, - LinkOnly: true, - Type: &factory.PortLocations.PortOfEmbarkation, - }, - }, nil) - - mtoShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ - { - Model: models.MTOShipment{ - MTOServiceItems: models.MTOServiceItems{poefscServiceItem}, - }, - }, - }, nil) - - payload := MTOShipment(nil, &mtoShipment, nil) - - // Assertions - suite.NotNil(payload, "Expected payload to not be nil") - suite.NotNil(payload.PoeLocation, "Expected POELocation to not be nil") - suite.Equal("PDX", payload.PoeLocation.PortCode, "Expected POE Port Code to match") - suite.Equal("PORTLAND INTL", payload.PoeLocation.PortName, "Expected POE Port Name to match") - suite.Nil(payload.PodLocation, "Expected PODLocation to be nil when POELocation is set") - }) - - suite.Run("Only POD Location is set", func() { - // Create mock data for MTOServiceItems with POE and POD - podPortLocation := factory.FetchPortLocation(suite.DB(), []factory.Customization{ - { - Model: models.Port{ - PortCode: "PDX", - }, - }, - }, nil) - - podfscServiceItem := factory.BuildMTOServiceItem(nil, []factory.Customization{ - { - Model: models.ReService{ - Code: models.ReServiceCodePODFSC, - Priority: 1, - }, - }, - { - Model: podPortLocation, - LinkOnly: true, - Type: &factory.PortLocations.PortOfDebarkation, - }, - }, nil) - - mtoShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ - { - Model: models.MTOShipment{ - MTOServiceItems: models.MTOServiceItems{podfscServiceItem}, - }, - }, - }, nil) - - payload := MTOShipment(nil, &mtoShipment, nil) - - // Assertions - suite.NotNil(payload, "Expected payload to not be nil") - suite.NotNil(payload.PodLocation, "Expected PODLocation to not be nil") - suite.Equal("PDX", payload.PodLocation.PortCode, "Expected POD Port Code to match") - suite.Equal("PORTLAND INTL", payload.PodLocation.PortName, "Expected POD Port Name to match") - suite.Nil(payload.PoeLocation, "Expected PODLocation to be nil when PODLocation is set") - }) -} - -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()) @@ -1917,34 +1946,3 @@ 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.go b/pkg/handlers/ghcapi/move.go index aaf96dde91e..f4abb0b549a 100644 --- a/pkg/handlers/ghcapi/move.go +++ b/pkg/handlers/ghcapi/move.go @@ -429,10 +429,10 @@ func payloadForUploadModelFromAdditionalDocumentsUpload(storer storage.FileStore UpdatedAt: strfmt.DateTime(upload.UpdatedAt), } tags, err := storer.Tags(upload.StorageKey) - if err != nil || len(tags) == 0 { - uploadPayload.Status = "PROCESSING" + if err != nil { + uploadPayload.Status = string(models.AVStatusPROCESSING) } else { - uploadPayload.Status = tags["av-status"] + uploadPayload.Status = string(models.GetAVStatusFromTags(tags)) } return uploadPayload, nil } diff --git a/pkg/handlers/ghcapi/mto_service_items.go b/pkg/handlers/ghcapi/mto_service_items.go index e5f5572580d..99939aaf063 100644 --- a/pkg/handlers/ghcapi/mto_service_items.go +++ b/pkg/handlers/ghcapi/mto_service_items.go @@ -352,6 +352,8 @@ func (h ListMTOServiceItemsHandler) Handle(params mtoserviceitemop.ListMTOServic query.NewQueryAssociation("SITOriginHHGActualAddress"), query.NewQueryAssociation("ReService.ReServiceItems"), query.NewQueryAssociation("MTOShipment"), + query.NewQueryAssociation("MTOShipment.PickupAddress"), + query.NewQueryAssociation("MTOShipment.DestinationAddress"), }) var serviceItems models.MTOServiceItems diff --git a/pkg/handlers/ghcapi/mto_service_items_test.go b/pkg/handlers/ghcapi/mto_service_items_test.go index a380a72356e..8044580f0b0 100644 --- a/pkg/handlers/ghcapi/mto_service_items_test.go +++ b/pkg/handlers/ghcapi/mto_service_items_test.go @@ -425,7 +425,7 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemStatusHandler() { ppmShipmentUpdater := ppmshipment.NewPPMShipmentUpdater(&ppmEstimator, addressCreator, addressUpdater) boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() - shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(noCheckUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(noCheckUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, nil) shipmentFetcher := mtoshipment.NewMTOShipmentFetcher() moveTaskOrderID, _ := uuid.NewV4() @@ -661,7 +661,7 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemStatusHandler() { mock.Anything, false, ).Return(400, nil) - mtoServiceItemStatusUpdater := mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher) + mtoServiceItemStatusUpdater := mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) handler := UpdateMTOServiceItemStatusHandler{ HandlerConfig: suite.HandlerConfig(), @@ -723,7 +723,7 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemStatusHandler() { mock.Anything, false, ).Return(400, nil) - mtoServiceItemStatusUpdater := mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher) + mtoServiceItemStatusUpdater := mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) handler := UpdateMTOServiceItemStatusHandler{ HandlerConfig: suite.HandlerConfig(), @@ -877,7 +877,7 @@ func (suite *HandlerSuite) TestUpdateServiceItemSitEntryDateHandler() { ppmShipmentUpdater := ppmshipment.NewPPMShipmentUpdater(&ppmEstimator, addressCreator, addressUpdater) boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() - shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(noCheckUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(noCheckUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, nil) shipmentFetcher := mtoshipment.NewMTOShipmentFetcher() suite.Run("200 - success response", func() { diff --git a/pkg/handlers/ghcapi/mto_shipment_test.go b/pkg/handlers/ghcapi/mto_shipment_test.go index 70ac78cbaac..247ef1ae1fb 100644 --- a/pkg/handlers/ghcapi/mto_shipment_test.go +++ b/pkg/handlers/ghcapi/mto_shipment_test.go @@ -3293,7 +3293,7 @@ func (suite *HandlerSuite) TestApproveSITExtensionHandler() { boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() - sitExtensionShipmentUpdater := shipmentorchestrator.NewShipmentUpdater(noCheckUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + sitExtensionShipmentUpdater := shipmentorchestrator.NewShipmentUpdater(noCheckUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, nil) handler := ApproveSITExtensionHandler{ handlerConfig, @@ -3437,7 +3437,7 @@ func (suite *HandlerSuite) CreateApprovedSITDurationUpdate() { boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() - sitExtensionShipmentUpdater := shipmentorchestrator.NewShipmentUpdater(noCheckUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + sitExtensionShipmentUpdater := shipmentorchestrator.NewShipmentUpdater(noCheckUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, nil) handler := CreateApprovedSITDurationUpdateHandler{ handlerConfig, @@ -3523,7 +3523,7 @@ func (suite *HandlerSuite) CreateApprovedSITDurationUpdate() { mobilehomeshipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() - sitExtensionShipmentUpdater := shipmentorchestrator.NewShipmentUpdater(noCheckUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobilehomeshipmentUpdater) + sitExtensionShipmentUpdater := shipmentorchestrator.NewShipmentUpdater(noCheckUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobilehomeshipmentUpdater, nil) handler := CreateApprovedSITDurationUpdateHandler{ handlerConfig, @@ -4593,7 +4593,7 @@ func (suite *HandlerSuite) TestUpdateShipmentHandler() { ppmShipmentUpdater := ppmshipment.NewPPMShipmentUpdater(&ppmEstimator, addressCreator, addressUpdater) boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() - shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, nil) handler := UpdateShipmentHandler{ suite.HandlerConfig(), shipmentUpdater, @@ -4661,7 +4661,7 @@ func (suite *HandlerSuite) TestUpdateShipmentHandler() { boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() - shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, nil) handler := UpdateShipmentHandler{ suite.HandlerConfig(), shipmentUpdater, @@ -4847,7 +4847,7 @@ func (suite *HandlerSuite) TestUpdateShipmentHandler() { boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() - shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, nil) handler := UpdateShipmentHandler{ suite.HandlerConfig(), shipmentUpdater, @@ -4927,7 +4927,7 @@ func (suite *HandlerSuite) TestUpdateShipmentHandler() { boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() - shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, nil) handler := UpdateShipmentHandler{ suite.HandlerConfig(), shipmentUpdater, @@ -5007,7 +5007,7 @@ func (suite *HandlerSuite) TestUpdateShipmentHandler() { boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() - shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, nil) handler := UpdateShipmentHandler{ suite.HandlerConfig(), shipmentUpdater, @@ -5045,7 +5045,7 @@ func (suite *HandlerSuite) TestUpdateShipmentHandler() { boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() - shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, nil) handler := UpdateShipmentHandler{ suite.HandlerConfig(), shipmentUpdater, @@ -5085,7 +5085,7 @@ func (suite *HandlerSuite) TestUpdateShipmentHandler() { boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() - shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, nil) handler := UpdateShipmentHandler{ suite.HandlerConfig(), shipmentUpdater, @@ -5126,7 +5126,7 @@ func (suite *HandlerSuite) TestUpdateShipmentHandler() { boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() - shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, nil) handler := UpdateShipmentHandler{ suite.HandlerConfig(), shipmentUpdater, @@ -5243,7 +5243,7 @@ func (suite *HandlerSuite) TestUpdateSITServiceItemCustomerExpenseHandler() { mock.Anything, false, ).Return(400, nil) - updater := mtoserviceitem.NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher) + updater := mtoserviceitem.NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) req := httptest.NewRequest("PATCH", fmt.Sprintf("/shipments/%s/sit-service-item/convert-to-customer-expense", approvedShipment.ID.String()), nil) req = suite.AuthenticateOfficeRequest(req, officeUser) handlerConfig := suite.HandlerConfig() @@ -5320,7 +5320,7 @@ func (suite *HandlerSuite) TestUpdateSITServiceItemCustomerExpenseHandler() { mock.Anything, false, ).Return(400, nil) - updater := mtoserviceitem.NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher) + updater := mtoserviceitem.NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) req := httptest.NewRequest("PATCH", fmt.Sprintf("/shipments/%s/sit-service-item/convert-to-customer-expense", approvedShipment.ID.String()), nil) req = suite.AuthenticateOfficeRequest(req, officeUser) handlerConfig := suite.HandlerConfig() diff --git a/pkg/handlers/ghcapi/orders.go b/pkg/handlers/ghcapi/orders.go index 8a8ca3cafcf..dae66ae961a 100644 --- a/pkg/handlers/ghcapi/orders.go +++ b/pkg/handlers/ghcapi/orders.go @@ -291,7 +291,6 @@ func (h CreateOrderHandler) Handle(params orderop.CreateOrderParams) middleware. } var weightRestriction *int - entitlement := models.Entitlement{ DependentsAuthorized: payload.HasDependents, DBAuthorizedWeight: models.IntPointer(weight), @@ -959,10 +958,10 @@ func payloadForUploadModelFromAmendedOrdersUpload(storer storage.FileStorer, upl UpdatedAt: strfmt.DateTime(upload.UpdatedAt), } tags, err := storer.Tags(upload.StorageKey) - if err != nil || len(tags) == 0 { - uploadPayload.Status = "PROCESSING" + if err != nil { + uploadPayload.Status = string(models.AVStatusPROCESSING) } else { - uploadPayload.Status = tags["av-status"] + uploadPayload.Status = string(models.GetAVStatusFromTags(tags)) } return uploadPayload, nil } diff --git a/pkg/handlers/ghcapi/orders_test.go b/pkg/handlers/ghcapi/orders_test.go index 4496c7c1146..66b44041c13 100644 --- a/pkg/handlers/ghcapi/orders_test.go +++ b/pkg/handlers/ghcapi/orders_test.go @@ -758,6 +758,7 @@ func (suite *HandlerSuite) makeUpdateOrderHandlerSubtestData() (subtestData *upd Sac: nullable.NewString("987654321"), NtsTac: nullable.NewString("E19A"), NtsSac: nullable.NewString("987654321"), + DependentsAuthorized: models.BoolPointer(true), } return subtestData @@ -816,6 +817,7 @@ func (suite *HandlerSuite) TestUpdateOrderHandler() { suite.Equal(body.Sac.Value, ordersPayload.Sac) suite.Equal(body.NtsTac.Value, ordersPayload.NtsTac) suite.Equal(body.NtsSac.Value, ordersPayload.NtsSac) + suite.Equal(body.DependentsAuthorized, ordersPayload.Entitlement.DependentsAuthorized) }) // We need to confirm whether a user who only has the TIO role should indeed @@ -1051,6 +1053,7 @@ func (suite *HandlerSuite) makeCounselingUpdateOrderHandlerSubtestData() (subtes Sac: nullable.NewString("987654321"), NtsTac: nullable.NewString("E19A"), NtsSac: nullable.NewString("987654321"), + DependentsAuthorized: models.BoolPointer(true), } return subtestData @@ -1104,6 +1107,7 @@ func (suite *HandlerSuite) TestCounselingUpdateOrderHandler() { suite.Equal(body.Sac.Value, ordersPayload.Sac) suite.Equal(body.NtsTac.Value, ordersPayload.NtsTac) suite.Equal(body.NtsSac.Value, ordersPayload.NtsSac) + suite.Equal(body.DependentsAuthorized, ordersPayload.Entitlement.DependentsAuthorized) }) suite.Run("Returns 404 when updater returns NotFoundError", func() { @@ -1250,9 +1254,8 @@ func (suite *HandlerSuite) makeUpdateAllowanceHandlerSubtestData() (subtestData rmeWeight := models.Int64Pointer(10000) subtestData.body = &ghcmessages.UpdateAllowancePayload{ - Agency: &affiliation, - DependentsAuthorized: models.BoolPointer(true), - Grade: &grade, + Agency: &affiliation, + Grade: &grade, OrganizationalClothingAndIndividualEquipment: &ocie, ProGearWeight: proGearWeight, ProGearWeightSpouse: proGearWeightSpouse, @@ -1345,7 +1348,6 @@ func (suite *HandlerSuite) TestUpdateAllowanceHandler() { suite.Equal(order.ID.String(), ordersPayload.ID.String()) suite.Equal(body.Grade, ordersPayload.Grade) suite.Equal(body.Agency, ordersPayload.Agency) - suite.Equal(body.DependentsAuthorized, ordersPayload.Entitlement.DependentsAuthorized) suite.Equal(*body.OrganizationalClothingAndIndividualEquipment, ordersPayload.Entitlement.OrganizationalClothingAndIndividualEquipment) suite.Equal(*body.ProGearWeight, ordersPayload.Entitlement.ProGearWeight) suite.Equal(*body.ProGearWeightSpouse, ordersPayload.Entitlement.ProGearWeightSpouse) @@ -1524,9 +1526,8 @@ func (suite *HandlerSuite) TestCounselingUpdateAllowanceHandler() { rmeWeight := models.Int64Pointer(10000) body := &ghcmessages.CounselingUpdateAllowancePayload{ - Agency: &affiliation, - DependentsAuthorized: models.BoolPointer(true), - Grade: &grade, + Agency: &affiliation, + Grade: &grade, OrganizationalClothingAndIndividualEquipment: &ocie, ProGearWeight: proGearWeight, ProGearWeightSpouse: proGearWeightSpouse, @@ -1574,7 +1575,6 @@ func (suite *HandlerSuite) TestCounselingUpdateAllowanceHandler() { suite.Equal(order.ID.String(), ordersPayload.ID.String()) suite.Equal(body.Grade, ordersPayload.Grade) suite.Equal(body.Agency, ordersPayload.Agency) - suite.Equal(body.DependentsAuthorized, ordersPayload.Entitlement.DependentsAuthorized) suite.Equal(*body.OrganizationalClothingAndIndividualEquipment, ordersPayload.Entitlement.OrganizationalClothingAndIndividualEquipment) suite.Equal(*body.ProGearWeight, ordersPayload.Entitlement.ProGearWeight) suite.Equal(*body.ProGearWeightSpouse, ordersPayload.Entitlement.ProGearWeightSpouse) diff --git a/pkg/handlers/ghcapi/queues.go b/pkg/handlers/ghcapi/queues.go index 202e5d3d6a0..4d708d415b4 100644 --- a/pkg/handlers/ghcapi/queues.go +++ b/pkg/handlers/ghcapi/queues.go @@ -183,6 +183,127 @@ func (h GetMovesQueueHandler) Handle(params queues.GetMovesQueueParams) middlewa }) } +// GetDestinationRequestsQueueHandler returns the moves for the TOO queue user via GET /queues/destination-requests +type GetDestinationRequestsQueueHandler struct { + handlers.HandlerConfig + services.OrderFetcher + services.MoveUnlocker + services.OfficeUserFetcherPop +} + +// Handle returns the paginated list of moves with destination requests for a TOO user +func (h GetDestinationRequestsQueueHandler) Handle(params queues.GetDestinationRequestsQueueParams) middleware.Responder { + return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, + func(appCtx appcontext.AppContext) (middleware.Responder, error) { + if !appCtx.Session().IsOfficeUser() || + (!appCtx.Session().Roles.HasRole(roles.RoleTypeTOO)) { + forbiddenErr := apperror.NewForbiddenError( + "user is not authenticated with TOO role", + ) + appCtx.Logger().Error(forbiddenErr.Error()) + return queues.NewGetDestinationRequestsQueueForbidden(), forbiddenErr + } + + ListOrderParams := services.ListOrderParams{ + Branch: params.Branch, + Locator: params.Locator, + Edipi: params.Edipi, + Emplid: params.Emplid, + CustomerName: params.CustomerName, + DestinationDutyLocation: params.DestinationDutyLocation, + OriginDutyLocation: params.OriginDutyLocation, + AppearedInTOOAt: handlers.FmtDateTimePtrToPopPtr(params.AppearedInTooAt), + RequestedMoveDate: params.RequestedMoveDate, + Status: params.Status, + Page: params.Page, + PerPage: params.PerPage, + Sort: params.Sort, + Order: params.Order, + TOOAssignedUser: params.AssignedTo, + CounselingOffice: params.CounselingOffice, + } + + // we only care about moves in APPROVALS REQUESTED status + if params.Status == nil { + ListOrderParams.Status = []string{string(models.MoveStatusAPPROVALSREQUESTED)} + } + + // default pagination values + if params.Page == nil { + ListOrderParams.Page = models.Int64Pointer(1) + } + if params.PerPage == nil { + ListOrderParams.PerPage = models.Int64Pointer(20) + } + + moves, count, err := h.OrderFetcher.ListDestinationRequestsOrders( + appCtx, + appCtx.Session().OfficeUserID, + roles.RoleTypeTOO, + &ListOrderParams, + ) + if err != nil { + appCtx.Logger(). + Error("error fetching destinaton queue for office user", zap.Error(err)) + return queues.NewGetDestinationRequestsQueueInternalServerError(), err + } + + var officeUser models.OfficeUser + if appCtx.Session().OfficeUserID != uuid.Nil { + officeUser, err = h.OfficeUserFetcherPop.FetchOfficeUserByID(appCtx, appCtx.Session().OfficeUserID) + if err != nil { + appCtx.Logger().Error("Error retrieving office user", zap.Error(err)) + return queues.NewGetDestinationRequestsQueueInternalServerError(), err + } + } + privileges, err := models.FetchPrivilegesForUser(appCtx.DB(), appCtx.Session().UserID) + if err != nil { + appCtx.Logger().Error("Error retreiving user privileges", zap.Error(err)) + } + officeUser.User.Privileges = privileges + officeUser.User.Roles = appCtx.Session().Roles + + if err != nil { + appCtx.Logger(). + Error("error fetching office users", zap.Error(err)) + return queues.NewGetDestinationRequestsQueueInternalServerError(), err + } + + // if the TOO is accessing the queue, we need to unlock move/moves they have locked + if appCtx.Session().IsOfficeUser() { + officeUserID := appCtx.Session().OfficeUserID + for i, move := range moves { + lockedOfficeUserID := move.LockedByOfficeUserID + if lockedOfficeUserID != nil && *lockedOfficeUserID == officeUserID { + copyOfMove := move + unlockedMove, err := h.UnlockMove(appCtx, ©OfMove, officeUserID) + if err != nil { + return queues.NewGetDestinationRequestsQueueInternalServerError(), err + } + moves[i] = *unlockedMove + } + } + err := h.CheckForLockedMovesAndUnlock(appCtx, officeUserID) + if err != nil { + appCtx.Logger().Error(fmt.Sprintf("failed to unlock moves for office user ID: %s", officeUserID), zap.Error(err)) + } + } + + var activeRole string + officeUsers := models.OfficeUsers{officeUser} + queueMoves := payloads.QueueMoves(moves, officeUsers, nil, officeUser, nil, activeRole) + + result := &ghcmessages.QueueMovesResult{ + Page: *ListOrderParams.Page, + PerPage: *ListOrderParams.PerPage, + TotalCount: int64(count), + QueueMoves: *queueMoves, + } + + return queues.NewGetDestinationRequestsQueueOK().WithPayload(result), nil + }) +} + // ListMovesHandler lists moves with the option to filter since a particular date. Optimized ver. type ListPrimeMovesHandler struct { handlers.HandlerConfig diff --git a/pkg/handlers/ghcapi/queues_test.go b/pkg/handlers/ghcapi/queues_test.go index 6a658d6c2aa..229d60efaa5 100644 --- a/pkg/handlers/ghcapi/queues_test.go +++ b/pkg/handlers/ghcapi/queues_test.go @@ -478,6 +478,11 @@ func (suite *HandlerSuite) TestGetMoveQueuesHandlerFilters() { Status: models.MTOServiceItemStatusSubmitted, }, }, + { + Model: models.ReService{ + Code: models.ReServiceCodeDOFSIT, + }, + }, }, nil) // Service Counseling Completed Move @@ -2247,5 +2252,145 @@ func (suite *HandlerSuite) TestAvailableOfficeUsers() { 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() { + waf := entitlements.NewWeightAllotmentFetcher() + // default GBLOC is KKFA + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), factory.GetTraitActiveOfficeUser(), []roles.RoleType{roles.RoleTypeTOO}) + officeUser.User.Roles = append(officeUser.User.Roles, roles.Role{ + RoleType: roles.RoleTypeTOO, + }) + + postalCode := "90210" + postalCode2 := "73064" + factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), "90210", "KKFA") + factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), "73064", "JEAT") + + // setting up two moves, one we will see and the other we won't + move := factory.BuildAvailableToPrimeMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), + }, + }}, nil) + + destinationAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{PostalCode: postalCode}, + }, + }, nil) + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusApproved, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: destinationAddress, + LinkOnly: true, + }, + }, nil) + + // destination service item in SUBMITTED status + factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeDDFSIT, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusSubmitted, + }, + }, + }, nil) + + move2 := factory.BuildAvailableToPrimeMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), + }, + }}, nil) + destinationAddress2 := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{PostalCode: postalCode2}, + }, + }, nil) + shipment2 := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusApproved, + }, + }, + { + Model: move2, + LinkOnly: true, + }, + { + Model: destinationAddress2, + LinkOnly: true, + }, + }, nil) + + // destination shuttle + factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeDDSHUT, + }, + }, + { + Model: move2, + LinkOnly: true, + }, + { + Model: shipment2, + LinkOnly: true, + }, + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusSubmitted, + }, + }, + }, nil) + + request := httptest.NewRequest("GET", "/queues/destination-requests", nil) + request = suite.AuthenticateOfficeRequest(request, officeUser) + params := queues.GetDestinationRequestsQueueParams{ + HTTPRequest: request, + } + handlerConfig := suite.HandlerConfig() + mockUnlocker := movelocker.NewMoveUnlocker() + handler := GetDestinationRequestsQueueHandler{ + handlerConfig, + order.NewOrderFetcher(waf), + mockUnlocker, + officeusercreator.NewOfficeUserFetcherPop(), + } + + response := handler.Handle(params) + suite.IsNotErrResponse(response) + suite.IsType(&queues.GetDestinationRequestsQueueOK{}, response) + payload := response.(*queues.GetDestinationRequestsQueueOK).Payload + + // should only have one move + result := payload.QueueMoves[0] + suite.Len(payload.QueueMoves, 1) + suite.Equal(move.ID.String(), result.ID.String()) } diff --git a/pkg/handlers/ghcapi/tranportation_offices.go b/pkg/handlers/ghcapi/tranportation_offices.go index 5df15bb6aaa..b08f45cfb04 100644 --- a/pkg/handlers/ghcapi/tranportation_offices.go +++ b/pkg/handlers/ghcapi/tranportation_offices.go @@ -23,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)) @@ -44,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 diff --git a/pkg/handlers/ghcapi/transportation_offices_test.go b/pkg/handlers/ghcapi/transportation_offices_test.go index deef04cac31..b9305f4d4b7 100644 --- a/pkg/handlers/ghcapi/transportation_offices_test.go +++ b/pkg/handlers/ghcapi/transportation_offices_test.go @@ -181,7 +181,6 @@ func (suite *HandlerSuite) TestShowCounselingOfficesHandler() { }, }, }, nil) - suite.MustSave(&origDutyLocation) path := fmt.Sprintf("/transportation_offices/%v/counseling_offices/%v", origDutyLocation.ID.String(), serviceMember.ID.String()) req := httptest.NewRequest("GET", path, nil) diff --git a/pkg/handlers/ghcapi/uploads.go b/pkg/handlers/ghcapi/uploads.go index a74e5d48498..24708064e19 100644 --- a/pkg/handlers/ghcapi/uploads.go +++ b/pkg/handlers/ghcapi/uploads.go @@ -1,9 +1,16 @@ package ghcapi import ( + "context" + "fmt" + "net/http" + "strconv" + "time" + "github.com/go-openapi/runtime" "github.com/go-openapi/runtime/middleware" "github.com/gofrs/uuid" + "github.com/pkg/errors" "go.uber.org/zap" "github.com/transcom/mymove/pkg/appcontext" @@ -12,8 +19,10 @@ import ( "github.com/transcom/mymove/pkg/handlers" "github.com/transcom/mymove/pkg/handlers/ghcapi/internal/payloads" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/notifications" "github.com/transcom/mymove/pkg/services" "github.com/transcom/mymove/pkg/services/upload" + "github.com/transcom/mymove/pkg/storage" uploaderpkg "github.com/transcom/mymove/pkg/uploader" ) @@ -50,7 +59,7 @@ func (h CreateUploadHandler) Handle(params uploadop.CreateUploadParams) middlewa } // Fetch document to ensure user has access to it - document, docErr := models.FetchDocument(appCtx.DB(), appCtx.Session(), documentID, true) + document, docErr := models.FetchDocument(appCtx.DB(), appCtx.Session(), documentID) if docErr != nil { return handlers.ResponseForError(appCtx.Logger(), docErr), rollbackErr } @@ -157,3 +166,189 @@ func (h DeleteUploadHandler) Handle(params uploadop.DeleteUploadParams) middlewa }) } + +// UploadStatusHandler returns status of an upload +type GetUploadStatusHandler struct { + handlers.HandlerConfig + services.UploadInformationFetcher +} + +type CustomGetUploadStatusResponse struct { + params uploadop.GetUploadStatusParams + storageKey string + appCtx appcontext.AppContext + receiver notifications.NotificationReceiver + storer storage.FileStorer +} + +func (o *CustomGetUploadStatusResponse) writeEventStreamMessage(rw http.ResponseWriter, producer runtime.Producer, id int, event string, data string) { + resProcess := []byte(fmt.Sprintf("id: %s\nevent: %s\ndata: %s\n\n", strconv.Itoa(id), event, data)) + if produceErr := producer.Produce(rw, resProcess); produceErr != nil { + o.appCtx.Logger().Error(produceErr.Error()) + } + if f, ok := rw.(http.Flusher); ok { + f.Flush() + } +} + +func (o *CustomGetUploadStatusResponse) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + // Check current tag before event-driven wait for anti-virus + tags, err := o.storer.Tags(o.storageKey) + var uploadStatus models.AVStatusType + if err != nil { + uploadStatus = models.AVStatusPROCESSING + } else { + uploadStatus = models.GetAVStatusFromTags(tags) + } + + // Limitation: once the status code header has been written (first response), we are not able to update the status for subsequent responses. + // Standard 200 OK used with common SSE paradigm + rw.WriteHeader(http.StatusOK) + if uploadStatus == models.AVStatusCLEAN || uploadStatus == models.AVStatusINFECTED { + o.writeEventStreamMessage(rw, producer, 0, "message", string(uploadStatus)) + o.writeEventStreamMessage(rw, producer, 1, "close", "Connection closed") + return // skip notification loop since object already tagged from anti-virus + } else { + o.writeEventStreamMessage(rw, producer, 0, "message", string(uploadStatus)) + } + + // Start waiting for tag updates + topicName, err := o.receiver.GetDefaultTopic() + if err != nil { + o.appCtx.Logger().Error(err.Error()) + } + + filterPolicy := fmt.Sprintf(`{ + "detail": { + "object": { + "key": [ + {"suffix": "%s"} + ] + } + } + }`, o.params.UploadID) + + notificationParams := notifications.NotificationQueueParams{ + SubscriptionTopicName: topicName, + NamePrefix: notifications.QueuePrefixObjectTagsAdded, + FilterPolicy: filterPolicy, + } + + queueUrl, err := o.receiver.CreateQueueWithSubscription(o.appCtx, notificationParams) + if err != nil { + o.appCtx.Logger().Error(err.Error()) + } + + id_counter := 1 + + // For loop over 120 seconds, cancel context when done and it breaks the loop + totalReceiverContext, totalReceiverContextCancelFunc := context.WithTimeout(context.Background(), 120*time.Second) + defer func() { + id_counter++ + o.writeEventStreamMessage(rw, producer, id_counter, "close", "Connection closed") + totalReceiverContextCancelFunc() + }() + + // Cleanup if client closes connection + go func() { + <-o.params.HTTPRequest.Context().Done() + totalReceiverContextCancelFunc() + }() + + // Cleanup at end of work + go func() { + <-totalReceiverContext.Done() + _ = o.receiver.CloseoutQueue(o.appCtx, queueUrl) + }() + + for { + o.appCtx.Logger().Info("Receiving Messages...") + messages, errs := o.receiver.ReceiveMessages(o.appCtx, queueUrl, totalReceiverContext) + + if errors.Is(errs, context.Canceled) || errors.Is(errs, context.DeadlineExceeded) { + return + } + if errs != nil { + o.appCtx.Logger().Error(err.Error()) + return + } + + if len(messages) != 0 { + errTransaction := o.appCtx.NewTransaction(func(txnAppCtx appcontext.AppContext) error { + + tags, err := o.storer.Tags(o.storageKey) + + if err != nil { + uploadStatus = models.AVStatusPROCESSING + } else { + uploadStatus = models.GetAVStatusFromTags(tags) + } + + o.writeEventStreamMessage(rw, producer, id_counter, "message", string(uploadStatus)) + + if uploadStatus == models.AVStatusCLEAN || uploadStatus == models.AVStatusINFECTED { + return errors.New("connection_closed") + } + + return err + }) + + if errTransaction != nil && errTransaction.Error() == "connection_closed" { + return + } + + if errTransaction != nil { + o.appCtx.Logger().Error(err.Error()) + return + } + } + id_counter++ + + select { + case <-totalReceiverContext.Done(): + return + default: + time.Sleep(1 * time.Second) // Throttle as a precaution against hounding of the SDK + continue + } + } +} + +// Handle returns status of an upload +func (h GetUploadStatusHandler) Handle(params uploadop.GetUploadStatusParams) middleware.Responder { + return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, + func(appCtx appcontext.AppContext) (middleware.Responder, error) { + + handleError := func(err error) (middleware.Responder, error) { + appCtx.Logger().Error("GetUploadStatusHandler error", zap.Error(err)) + switch errors.Cause(err) { + case models.ErrFetchForbidden: + return uploadop.NewGetUploadStatusForbidden(), err + case models.ErrFetchNotFound: + return uploadop.NewGetUploadStatusNotFound(), err + default: + return uploadop.NewGetUploadStatusInternalServerError(), err + } + } + + uploadId := params.UploadID.String() + uploadUUID, err := uuid.FromString(uploadId) + if err != nil { + return handleError(err) + } + + uploaded, err := models.FetchUserUploadFromUploadID(appCtx.DB(), appCtx.Session(), uploadUUID) + if err != nil { + return handleError(err) + } + + return &CustomGetUploadStatusResponse{ + params: params, + storageKey: uploaded.Upload.StorageKey, + appCtx: h.AppContextFromRequest(params.HTTPRequest), + receiver: h.NotificationReceiver(), + storer: h.FileStorer(), + }, nil + }) +} diff --git a/pkg/handlers/ghcapi/uploads_test.go b/pkg/handlers/ghcapi/uploads_test.go index 94830bdb5bf..0a22ea6b87a 100644 --- a/pkg/handlers/ghcapi/uploads_test.go +++ b/pkg/handlers/ghcapi/uploads_test.go @@ -4,13 +4,17 @@ import ( "net/http" "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" "github.com/gofrs/uuid" "github.com/transcom/mymove/pkg/factory" uploadop "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/uploads" "github.com/transcom/mymove/pkg/handlers" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/notifications" + "github.com/transcom/mymove/pkg/services/upload" storageTest "github.com/transcom/mymove/pkg/storage/test" + "github.com/transcom/mymove/pkg/uploader" ) const FixturePDF = "test.pdf" @@ -156,3 +160,127 @@ func (suite *HandlerSuite) TestCreateUploadsHandlerFailure() { t.Fatalf("Wrong number of uploads in database: expected %d, got %d", currentCount, count) } } + +func (suite *HandlerSuite) TestGetUploadStatusHandlerSuccess() { + fakeS3 := storageTest.NewFakeS3Storage(true) + localReceiver := notifications.StubNotificationReceiver{} + + orders := factory.BuildOrder(suite.DB(), nil, nil) + uploadUser1 := factory.BuildUserUpload(suite.DB(), []factory.Customization{ + { + Model: orders.UploadedOrders, + LinkOnly: true, + }, + { + Model: models.Upload{ + Filename: "FileName", + Bytes: int64(15), + ContentType: uploader.FileTypePDF, + }, + }, + }, nil) + + file := suite.Fixture(FixturePDF) + _, err := fakeS3.Store(uploadUser1.Upload.StorageKey, file.Data, "somehash", nil) + suite.NoError(err) + + params := uploadop.NewGetUploadStatusParams() + params.UploadID = strfmt.UUID(uploadUser1.Upload.ID.String()) + + req := &http.Request{} + req = suite.AuthenticateRequest(req, uploadUser1.Document.ServiceMember) + params.HTTPRequest = req + + handlerConfig := suite.HandlerConfig() + handlerConfig.SetFileStorer(fakeS3) + handlerConfig.SetNotificationReceiver(localReceiver) + uploadInformationFetcher := upload.NewUploadInformationFetcher() + handler := GetUploadStatusHandler{handlerConfig, uploadInformationFetcher} + + response := handler.Handle(params) + _, ok := response.(*CustomGetUploadStatusResponse) + suite.True(ok) + + queriedUpload := models.Upload{} + err = suite.DB().Find(&queriedUpload, uploadUser1.Upload.ID) + suite.NoError(err) +} + +func (suite *HandlerSuite) TestGetUploadStatusHandlerFailure() { + suite.Run("Error on no match for uploadId", func() { + orders := factory.BuildOrder(suite.DB(), factory.GetTraitActiveServiceMemberUser(), nil) + + uploadUUID := uuid.Must(uuid.NewV4()) + + params := uploadop.NewGetUploadStatusParams() + params.UploadID = strfmt.UUID(uploadUUID.String()) + + req := &http.Request{} + req = suite.AuthenticateRequest(req, orders.ServiceMember) + params.HTTPRequest = req + + fakeS3 := storageTest.NewFakeS3Storage(true) + localReceiver := notifications.StubNotificationReceiver{} + + handlerConfig := suite.HandlerConfig() + handlerConfig.SetFileStorer(fakeS3) + handlerConfig.SetNotificationReceiver(localReceiver) + uploadInformationFetcher := upload.NewUploadInformationFetcher() + handler := GetUploadStatusHandler{handlerConfig, uploadInformationFetcher} + + response := handler.Handle(params) + _, ok := response.(*uploadop.GetUploadStatusNotFound) + suite.True(ok) + + queriedUpload := models.Upload{} + err := suite.DB().Find(&queriedUpload, uploadUUID) + suite.Error(err) + }) + + suite.Run("Error when attempting access to another service member's upload", func() { + fakeS3 := storageTest.NewFakeS3Storage(true) + localReceiver := notifications.StubNotificationReceiver{} + + otherServiceMember := factory.BuildServiceMember(suite.DB(), nil, nil) + + orders := factory.BuildOrder(suite.DB(), nil, nil) + uploadUser1 := factory.BuildUserUpload(suite.DB(), []factory.Customization{ + { + Model: orders.UploadedOrders, + LinkOnly: true, + }, + { + Model: models.Upload{ + Filename: "FileName", + Bytes: int64(15), + ContentType: uploader.FileTypePDF, + }, + }, + }, nil) + + file := suite.Fixture(FixturePDF) + _, err := fakeS3.Store(uploadUser1.Upload.StorageKey, file.Data, "somehash", nil) + suite.NoError(err) + + params := uploadop.NewGetUploadStatusParams() + params.UploadID = strfmt.UUID(uploadUser1.Upload.ID.String()) + + req := &http.Request{} + req = suite.AuthenticateRequest(req, otherServiceMember) + params.HTTPRequest = req + + handlerConfig := suite.HandlerConfig() + handlerConfig.SetFileStorer(fakeS3) + handlerConfig.SetNotificationReceiver(localReceiver) + uploadInformationFetcher := upload.NewUploadInformationFetcher() + handler := GetUploadStatusHandler{handlerConfig, uploadInformationFetcher} + + response := handler.Handle(params) + _, ok := response.(*uploadop.GetUploadStatusForbidden) + suite.True(ok) + + queriedUpload := models.Upload{} + err = suite.DB().Find(&queriedUpload, uploadUser1.Upload.ID) + suite.NoError(err) + }) +} diff --git a/pkg/handlers/internalapi/api.go b/pkg/handlers/internalapi/api.go index cbdba27433a..31010c7e3da 100644 --- a/pkg/handlers/internalapi/api.go +++ b/pkg/handlers/internalapi/api.go @@ -193,11 +193,23 @@ func NewInternalAPI(handlerConfig handlers.HandlerConfig) *internalops.MymoveAPI postalcodeservice.NewPostalCodeValidator(clock.New()), } + mtoServiceItemCreator := mtoserviceitem.NewMTOServiceItemCreator( + handlerConfig.HHGPlanner(), + builder, + moveRouter, + ghcrateengine.NewDomesticUnpackPricer(), + ghcrateengine.NewDomesticPackPricer(), + ghcrateengine.NewDomesticLinehaulPricer(), + ghcrateengine.NewDomesticShorthaulPricer(), + ghcrateengine.NewDomesticOriginPricer(), + ghcrateengine.NewDomesticDestinationPricer(), + ghcrateengine.NewFuelSurchargePricer()) + mtoShipmentCreator := mtoshipment.NewMTOShipmentCreatorV1(builder, fetcher, moveRouter, addressCreator) shipmentRouter := mtoshipment.NewShipmentRouter() moveTaskOrderUpdater := movetaskorder.NewMoveTaskOrderUpdater( builder, - mtoserviceitem.NewMTOServiceItemCreator(handlerConfig.HHGPlanner(), builder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()), + mtoServiceItemCreator, moveRouter, signedCertificationCreator, signedCertificationUpdater, ppmEstimator, ) boatShipmentCreator := boatshipment.NewBoatShipmentCreator() @@ -233,6 +245,7 @@ func NewInternalAPI(handlerConfig handlers.HandlerConfig) *internalops.MymoveAPI ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, + mtoServiceItemCreator, ) internalAPI.MtoShipmentUpdateMTOShipmentHandler = UpdateMTOShipmentHandler{ diff --git a/pkg/handlers/internalapi/documents.go b/pkg/handlers/internalapi/documents.go index 2c648661725..0562ee39200 100644 --- a/pkg/handlers/internalapi/documents.go +++ b/pkg/handlers/internalapi/documents.go @@ -73,7 +73,7 @@ func (h ShowDocumentHandler) Handle(params documentop.ShowDocumentParams) middle return handlers.ResponseForError(appCtx.Logger(), err), err } - document, err := models.FetchDocument(appCtx.DB(), appCtx.Session(), documentID, false) + document, err := models.FetchDocument(appCtx.DB(), appCtx.Session(), documentID) if err != nil { return handlers.ResponseForError(appCtx.Logger(), err), err } diff --git a/pkg/handlers/internalapi/internal/payloads/model_to_payload.go b/pkg/handlers/internalapi/internal/payloads/model_to_payload.go index 68e9cd5b576..26b25349e02 100644 --- a/pkg/handlers/internalapi/internal/payloads/model_to_payload.go +++ b/pkg/handlers/internalapi/internal/payloads/model_to_payload.go @@ -453,12 +453,14 @@ func PayloadForUploadModel( CreatedAt: strfmt.DateTime(upload.CreatedAt), UpdatedAt: strfmt.DateTime(upload.UpdatedAt), } + tags, err := storer.Tags(upload.StorageKey) - if err != nil || len(tags) == 0 { - uploadPayload.Status = "PROCESSING" + if err != nil { + uploadPayload.Status = string(models.AVStatusPROCESSING) } else { - uploadPayload.Status = tags["av-status"] + uploadPayload.Status = string(models.GetAVStatusFromTags(tags)) } + return uploadPayload } 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 3fa132f085a..8da707d4000 100644 --- a/pkg/handlers/internalapi/internal/payloads/model_to_payload_test.go +++ b/pkg/handlers/internalapi/internal/payloads/model_to_payload_test.go @@ -22,7 +22,8 @@ func (suite *PayloadsSuite) TestFetchPPMShipment() { state := "FL" postalcode := "33621" country := models.Country{ - Country: "US", + Country: "US", + CountryName: "United States", } county := "HILLSBOROUGH" @@ -121,6 +122,37 @@ func (suite *PayloadsSuite) TestVLocation() { }) } +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()) + }) +} + func (suite *PayloadsSuite) TestSignedCertification() { suite.Run("Certification model", func() { uuid, _ := uuid.NewV4() @@ -184,34 +216,3 @@ 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/moves.go b/pkg/handlers/internalapi/moves.go index 891c990e15e..f431da62850 100644 --- a/pkg/handlers/internalapi/moves.go +++ b/pkg/handlers/internalapi/moves.go @@ -588,10 +588,10 @@ func payloadForUploadModelFromAdditionalDocumentsUpload(storer storage.FileStore UpdatedAt: strfmt.DateTime(upload.UpdatedAt), } tags, err := storer.Tags(upload.StorageKey) - if err != nil || len(tags) == 0 { - uploadPayload.Status = "PROCESSING" + if err != nil { + uploadPayload.Status = string(models.AVStatusPROCESSING) } else { - uploadPayload.Status = tags["av-status"] + uploadPayload.Status = string(models.GetAVStatusFromTags(tags)) } return uploadPayload, nil } diff --git a/pkg/handlers/internalapi/moves_test.go b/pkg/handlers/internalapi/moves_test.go index 2d32bc6eca9..40e2e9283f5 100644 --- a/pkg/handlers/internalapi/moves_test.go +++ b/pkg/handlers/internalapi/moves_test.go @@ -291,7 +291,7 @@ func (suite *HandlerSuite) TestSubmitMoveForApprovalHandler() { suite.NoError(err) // And: Returned query to have a submitted status - suite.Assertions.Equal(internalmessages.MoveStatusSUBMITTED, okResponse.Payload.Status) + suite.Assertions.Equal(internalmessages.MoveStatusNEEDSSERVICECOUNSELING, okResponse.Payload.Status) suite.Assertions.NotNil(okResponse.Payload.SubmittedAt) // And: SignedCertification was created diff --git a/pkg/handlers/internalapi/mto_shipment_test.go b/pkg/handlers/internalapi/mto_shipment_test.go index bdd7770f408..2a06643c691 100644 --- a/pkg/handlers/internalapi/mto_shipment_test.go +++ b/pkg/handlers/internalapi/mto_shipment_test.go @@ -763,7 +763,7 @@ func (suite *HandlerSuite) TestUpdateMTOShipmentHandler() { boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() - shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, nil) authRequestAndSetUpHandlerAndParams := func(originalShipment models.MTOShipment, mockShipmentUpdater *mocks.ShipmentUpdater) (UpdateMTOShipmentHandler, mtoshipmentops.UpdateMTOShipmentParams) { endpoint := fmt.Sprintf("/mto-shipments/%s", originalShipment.ID.String()) diff --git a/pkg/handlers/internalapi/orders.go b/pkg/handlers/internalapi/orders.go index 0097e94b7e3..3471329d227 100644 --- a/pkg/handlers/internalapi/orders.go +++ b/pkg/handlers/internalapi/orders.go @@ -35,10 +35,10 @@ func payloadForUploadModelFromAmendedOrdersUpload(storer storage.FileStorer, upl UpdatedAt: strfmt.DateTime(upload.UpdatedAt), } tags, err := storer.Tags(upload.StorageKey) - if err != nil || len(tags) == 0 { - uploadPayload.Status = "PROCESSING" + if err != nil { + uploadPayload.Status = string(models.AVStatusPROCESSING) } else { - uploadPayload.Status = tags["av-status"] + uploadPayload.Status = string(models.GetAVStatusFromTags(tags)) } return uploadPayload, nil } @@ -454,7 +454,6 @@ func (h UpdateOrdersHandler) Handle(params ordersop.UpdateOrdersParams) middlewa order.OriginDutyLocationGBLOC = originDutyLocationGBLOC if payload.MoveID != "" { - moveID, err := uuid.FromString(payload.MoveID.String()) if err != nil { return handlers.ResponseForError(appCtx.Logger(), err), err diff --git a/pkg/handlers/internalapi/orders_test.go b/pkg/handlers/internalapi/orders_test.go index c26d5302ecf..020d7bbb8dc 100644 --- a/pkg/handlers/internalapi/orders_test.go +++ b/pkg/handlers/internalapi/orders_test.go @@ -717,6 +717,7 @@ func (suite *HandlerSuite) TestUploadAmendedOrdersHandlerIntegration() { func (suite *HandlerSuite) TestUpdateOrdersHandler() { waf := entitlements.NewWeightAllotmentFetcher() + suite.Run("Can update CONUS orders", func() { address := factory.BuildAddress(suite.DB(), []factory.Customization{ { diff --git a/pkg/handlers/internalapi/transportation_offices.go b/pkg/handlers/internalapi/transportation_offices.go index f1f7eb706eb..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 diff --git a/pkg/handlers/internalapi/transportation_offices_test.go b/pkg/handlers/internalapi/transportation_offices_test.go index ec7bf6ff858..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) diff --git a/pkg/handlers/internalapi/uploads.go b/pkg/handlers/internalapi/uploads.go index 4167d7ed2b8..4d248598ed6 100644 --- a/pkg/handlers/internalapi/uploads.go +++ b/pkg/handlers/internalapi/uploads.go @@ -70,7 +70,7 @@ func (h CreateUploadHandler) Handle(params uploadop.CreateUploadParams) middlewa } // Fetch document to ensure user has access to it - document, docErr := models.FetchDocument(appCtx.DB(), appCtx.Session(), documentID, true) + document, docErr := models.FetchDocument(appCtx.DB(), appCtx.Session(), documentID) if docErr != nil { return handlers.ResponseForError(appCtx.Logger(), docErr), rollbackErr } @@ -267,7 +267,7 @@ func (h CreatePPMUploadHandler) Handle(params ppmop.CreatePPMUploadParams) middl documentID := uuid.FromStringOrNil(params.DocumentID.String()) // Fetch document to ensure user has access to it - document, docErr := models.FetchDocument(appCtx.DB(), appCtx.Session(), documentID, true) + document, docErr := models.FetchDocument(appCtx.DB(), appCtx.Session(), documentID) if docErr != nil { docNotFoundErr := fmt.Errorf("documentId %q was not found for this user", documentID) return ppmop.NewCreatePPMUploadNotFound().WithPayload(payloads.ClientError(handlers.NotFoundMessage, docNotFoundErr.Error(), h.GetTraceIDFromRequest(params.HTTPRequest))), docNotFoundErr diff --git a/pkg/handlers/primeapi/addresses.go b/pkg/handlers/primeapi/addresses.go new file mode 100644 index 00000000000..55263799d93 --- /dev/null +++ b/pkg/handlers/primeapi/addresses.go @@ -0,0 +1,62 @@ +package primeapi + +import ( + "context" + + "github.com/go-openapi/runtime/middleware" + "go.uber.org/zap" + + "github.com/transcom/mymove/pkg/appcontext" + addressop "github.com/transcom/mymove/pkg/gen/primeapi/primeoperations/addresses" + "github.com/transcom/mymove/pkg/handlers" + "github.com/transcom/mymove/pkg/handlers/primeapi/payloads" + "github.com/transcom/mymove/pkg/services" +) + +type GetLocationByZipCityStateHandler struct { + handlers.HandlerConfig + services.VLocation +} + +func (h GetLocationByZipCityStateHandler) Handle(params addressop.GetLocationByZipCityStateParams) middleware.Responder { + return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, + func(appCtx appcontext.AppContext) (middleware.Responder, error) { + /** Feature Flag - Alaska - Determines if AK can be included/excluded **/ + isAlaskaEnabled := false + akFeatureFlagName := "enable_alaska" + flag, err := h.FeatureFlagFetcher().GetBooleanFlagForUser(context.TODO(), appCtx, akFeatureFlagName, map[string]string{}) + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", akFeatureFlagName), zap.Error(err)) + } else { + isAlaskaEnabled = flag.Match + } + + /** Feature Flag - Hawaii - Determines if HI can be included/excluded **/ + isHawaiiEnabled := false + hiFeatureFlagName := "enable_hawaii" + flag, err = h.FeatureFlagFetcher().GetBooleanFlagForUser(context.TODO(), appCtx, hiFeatureFlagName, map[string]string{}) + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", hiFeatureFlagName), zap.Error(err)) + } else { + isHawaiiEnabled = flag.Match + } + + // build states to exlude filter list + statesToExclude := make([]string, 0) + if !isAlaskaEnabled { + statesToExclude = append(statesToExclude, "AK") + } + if !isHawaiiEnabled { + statesToExclude = append(statesToExclude, "HI") + } + + locationList, err := h.GetLocationsByZipCityState(appCtx, params.Search, statesToExclude) + if err != nil { + appCtx.Logger().Error("Error searching for Zip/City/State: ", zap.Error(err)) + return addressop.NewGetLocationByZipCityStateInternalServerError(), err + } + + returnPayload := payloads.VLocations(*locationList) + return addressop.NewGetLocationByZipCityStateOK().WithPayload(returnPayload), nil + }) +} diff --git a/pkg/handlers/primeapi/api.go b/pkg/handlers/primeapi/api.go index 2d8814925bc..c2452f0583e 100644 --- a/pkg/handlers/primeapi/api.go +++ b/pkg/handlers/primeapi/api.go @@ -53,7 +53,8 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primeoperations.MymoveAP moveWeights := move.NewMoveWeights(mtoshipment.NewShipmentReweighRequester(), waf) uploadCreator := upload.NewUploadCreator(handlerConfig.FileStorer()) ppmEstimator := ppmshipment.NewEstimatePPM(handlerConfig.DTODPlanner(), &paymentrequesthelper.RequestPaymentHelper{}) - serviceItemUpdater := mtoserviceitem.NewMTOServiceItemUpdater(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher) + serviceItemUpdater := mtoserviceitem.NewMTOServiceItemUpdater(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) + vLocation := address.NewVLocation() userUploader, err := uploader.NewUserUploader(handlerConfig.FileStorer(), uploader.MaxCustomerUserUploadFileSizeLimit) if err != nil { @@ -111,9 +112,15 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primeoperations.MymoveAP mtoserviceitem.NewServiceRequestDocumentUploadCreator(handlerConfig.FileStorer()), } + primeAPI.AddressesGetLocationByZipCityStateHandler = GetLocationByZipCityStateHandler{ + handlerConfig, + vLocation, + } + primeAPI.MtoShipmentUpdateShipmentDestinationAddressHandler = UpdateShipmentDestinationAddressHandler{ handlerConfig, shipmentaddressupdate.NewShipmentAddressUpdateRequester(handlerConfig.HHGPlanner(), addressCreator, moveRouter), + vLocation, } addressUpdater := address.NewAddressUpdater() @@ -158,6 +165,7 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primeoperations.MymoveAP primeAPI.MtoShipmentUpdateMTOShipmentAddressHandler = UpdateMTOShipmentAddressHandler{ handlerConfig, mtoshipment.NewMTOShipmentAddressUpdater(handlerConfig.HHGPlanner(), addressCreator, addressUpdater), + vLocation, } primeAPI.MtoShipmentCreateMTOAgentHandler = CreateMTOAgentHandler{ 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..edb0b5e42a6 100644 --- a/pkg/handlers/primeapi/mto_service_item.go +++ b/pkg/handlers/primeapi/mto_service_item.go @@ -25,12 +25,15 @@ import ( // THIS WILL NEED TO BE UPDATED AS WE CONTINUE TO ADD MORE SERVICE ITEMS. // We will eventually remove this when all service items are added. var CreateableServiceItemMap = map[primemessages.MTOServiceItemModelType]bool{ - primemessages.MTOServiceItemModelTypeMTOServiceItemOriginSIT: true, - primemessages.MTOServiceItemModelTypeMTOServiceItemDestSIT: true, - primemessages.MTOServiceItemModelTypeMTOServiceItemShuttle: true, - primemessages.MTOServiceItemModelTypeMTOServiceItemInternationalShuttle: true, - primemessages.MTOServiceItemModelTypeMTOServiceItemDomesticCrating: true, - primemessages.MTOServiceItemModelTypeMTOServiceItemInternationalCrating: true, + primemessages.MTOServiceItemModelTypeMTOServiceItemOriginSIT: true, + primemessages.MTOServiceItemModelTypeMTOServiceItemDestSIT: true, + primemessages.MTOServiceItemModelTypeMTOServiceItemInternationalOriginSIT: true, + primemessages.MTOServiceItemModelTypeMTOServiceItemInternationalDestSIT: true, + primemessages.MTOServiceItemModelTypeMTOServiceItemShuttle: true, + primemessages.MTOServiceItemModelTypeMTOServiceItemDomesticShuttle: true, + primemessages.MTOServiceItemModelTypeMTOServiceItemInternationalShuttle: true, + primemessages.MTOServiceItemModelTypeMTOServiceItemDomesticCrating: true, + primemessages.MTOServiceItemModelTypeMTOServiceItemInternationalCrating: true, } // CreateMTOServiceItemHandler is the handler to create MTO service items diff --git a/pkg/handlers/primeapi/mto_service_item_test.go b/pkg/handlers/primeapi/mto_service_item_test.go index 362ca66f6be..54ef8eb8cb4 100644 --- a/pkg/handlers/primeapi/mto_service_item_test.go +++ b/pkg/handlers/primeapi/mto_service_item_test.go @@ -43,7 +43,7 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemHandler() { mtoServiceItem models.MTOServiceItem } - makeSubtestDataWithPPMShipmentType := func(isPPM bool) (subtestData *localSubtestData) { + makeSubtestDataWithPPMShipmentType := func(isPPM bool, isInternational bool) (subtestData *localSubtestData) { subtestData = &localSubtestData{} mtoShipmentID, _ := uuid.NewV4() @@ -62,15 +62,34 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemHandler() { }, }, nil) } else { - subtestData.mtoShipment = factory.BuildMTOShipment(suite.DB(), []factory.Customization{ - { - Model: mto, - LinkOnly: true, - }, - }, nil) + if isInternational { + subtestData.mtoShipment = factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: mto, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + }, + }, + }, nil) + } else { + subtestData.mtoShipment = factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: mto, + LinkOnly: true, + }, + }, nil) + } + } + + if isInternational { + factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeIOFSIT) + } else { + factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDOFSIT) } - factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDOFSIT) req := httptest.NewRequest("POST", "/mto-service-items", nil) sitEntryDate := time.Now() sitPostalCode := "00000" @@ -84,10 +103,16 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemHandler() { // that we properly create the address coming in from the API. actualPickupAddress := factory.BuildAddress(nil, nil, []factory.Trait{factory.GetTraitAddress2}) + serviceCode := models.ReService{Code: models.ReServiceCodeDOFSIT} + + if isInternational { + serviceCode = models.ReService{Code: models.ReServiceCodeIOFSIT} + } + subtestData.mtoServiceItem = models.MTOServiceItem{ MoveTaskOrderID: mto.ID, MTOShipmentID: &subtestData.mtoShipment.ID, - ReService: models.ReService{Code: models.ReServiceCodeDOFSIT}, + ReService: serviceCode, Reason: models.StringPointer("lorem ipsum"), SITEntryDate: &sitEntryDate, SITPostalCode: &sitPostalCode, @@ -104,7 +129,11 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemHandler() { } makeSubtestData := func() (subtestData *localSubtestData) { - return makeSubtestDataWithPPMShipmentType(false) + return makeSubtestDataWithPPMShipmentType(false, false) + } + + makeSubtestInternationalData := func() (subtestData *localSubtestData) { + return makeSubtestDataWithPPMShipmentType(false, true) } suite.Run("Successful POST - Integration Test", func() { @@ -143,6 +172,34 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemHandler() { suite.NotZero(okResponse.Payload[0].ID()) }) + suite.Run("Successful POST International - Integration Test", func() { + subtestData := makeSubtestInternationalData() + moveRouter := moverouter.NewMoveRouter() + planner := &routemocks.Planner{} + planner.On("ZipTransitDistance", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + false, + false, + ).Return(400, nil) + creator := mtoserviceitem.NewMTOServiceItemCreator(planner, builder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) + handler := CreateMTOServiceItemHandler{ + suite.HandlerConfig(), + creator, + mtoChecker, + } + + // Validate incoming payload + suite.NoError(subtestData.params.Body.Validate(strfmt.Default)) + + response := handler.Handle(subtestData.params) + suite.IsType(&mtoserviceitemops.CreateMTOServiceItemOK{}, response) + okResponse := response.(*mtoserviceitemops.CreateMTOServiceItemOK) + + suite.NotZero(okResponse.Payload[0].ID()) + }) + suite.Run("Successful POST for Creating Shuttling without PrimeEstimatedWeight set - Integration Test", func() { mto := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) mtoShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ @@ -502,7 +559,7 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemHandler() { }) suite.Run("POST failure - Shipment fetch not found", func() { - subtestData := makeSubtestDataWithPPMShipmentType(true) + subtestData := makeSubtestDataWithPPMShipmentType(true, false) moveRouter := moverouter.NewMoveRouter() planner := &routemocks.Planner{} planner.On("ZipTransitDistance", @@ -534,7 +591,7 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemHandler() { }) suite.Run("POST failure - 422 - PPM not allowed to create service item", func() { - subtestData := makeSubtestDataWithPPMShipmentType(true) + subtestData := makeSubtestDataWithPPMShipmentType(true, false) moveRouter := moverouter.NewMoveRouter() planner := &routemocks.Planner{} planner.On("ZipTransitDistance", @@ -1040,8 +1097,8 @@ func (suite *HandlerSuite) TestCreateMTOServiceItemOriginSITHandlerWithDOFSITWit }, }, nil) factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDOFSIT) - sitEntryDate := time.Date(2024, time.February, 28, 0, 0, 0, 0, time.UTC) - sitDepartureDate := time.Date(2024, time.February, 27, 0, 0, 0, 0, time.UTC) + sitEntryDate := time.Date(2024, time.February, 27, 0, 0, 0, 0, time.UTC) + sitDepartureDate := time.Date(2024, time.February, 28, 0, 0, 0, 0, time.UTC) sitPostalCode := "00000" // Original customer pickup address @@ -1693,7 +1750,7 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemDDDSIT() { ).Return(400, nil) subtestData.handler = UpdateMTOServiceItemHandler{ suite.HandlerConfig(), - mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher), + mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()), } // create the params struct @@ -1977,7 +2034,7 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemDOPSIT() { ).Return(400, nil) subtestData.handler = UpdateMTOServiceItemHandler{ suite.HandlerConfig(), - mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher), + mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()), } // create the params struct diff --git a/pkg/handlers/primeapi/mto_shipment.go b/pkg/handlers/primeapi/mto_shipment.go index a93967aea89..0fad3b2ff99 100644 --- a/pkg/handlers/primeapi/mto_shipment.go +++ b/pkg/handlers/primeapi/mto_shipment.go @@ -1,6 +1,9 @@ package primeapi import ( + "context" + "fmt" + "github.com/go-openapi/runtime/middleware" "github.com/gofrs/uuid" "go.uber.org/zap" @@ -19,6 +22,7 @@ import ( type UpdateShipmentDestinationAddressHandler struct { handlers.HandlerConfig services.ShipmentAddressUpdateRequester + services.VLocation } // Handle creates the address update request for non-SIT @@ -32,6 +36,52 @@ func (h UpdateShipmentDestinationAddressHandler) Handle(params mtoshipmentops.Up eTag := params.IfMatch + /** Feature Flag - Alaska - Determines if AK can be included/excluded **/ + isAlaskaEnabled := false + akFeatureFlagName := "enable_alaska" + flag, err := h.FeatureFlagFetcher().GetBooleanFlagForUser(context.TODO(), appCtx, akFeatureFlagName, map[string]string{}) + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", akFeatureFlagName), zap.Error(err)) + } else { + isAlaskaEnabled = flag.Match + } + + /** Feature Flag - Hawaii - Determines if HI can be included/excluded **/ + isHawaiiEnabled := false + hiFeatureFlagName := "enable_hawaii" + flag, err = h.FeatureFlagFetcher().GetBooleanFlagForUser(context.TODO(), appCtx, hiFeatureFlagName, map[string]string{}) + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", hiFeatureFlagName), zap.Error(err)) + } else { + isHawaiiEnabled = flag.Match + } + + // build states to exlude filter list + statesToExclude := make([]string, 0) + if !isAlaskaEnabled { + statesToExclude = append(statesToExclude, "AK") + } + if !isHawaiiEnabled { + statesToExclude = append(statesToExclude, "HI") + } + + addressSearch := addressUpdate.NewAddress.City + ", " + addressUpdate.NewAddress.State + " " + addressUpdate.NewAddress.PostalCode + + locationList, err := h.GetLocationsByZipCityState(appCtx, addressSearch, statesToExclude, true) + if err != nil { + serverError := apperror.NewInternalServerError("Error searching for address") + errStr := serverError.Error() // we do this because InternalServerError wants a *string + appCtx.Logger().Warn(serverError.Error()) + payload := payloads.InternalServerError(&errStr, h.GetTraceIDFromRequest(params.HTTPRequest)) + return mtoshipmentops.NewUpdateShipmentDestinationAddressInternalServerError().WithPayload(payload), serverError + } else if len(*locationList) == 0 { + unprocessableErr := apperror.NewUnprocessableEntityError( + fmt.Sprintf("primeapi.UpdateShipmentDestinationAddress: could not find the provided location: %s", addressSearch)) + appCtx.Logger().Warn(unprocessableErr.Error()) + payload := payloads.ValidationError(unprocessableErr.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), nil) + return mtoshipmentops.NewUpdateShipmentDestinationAddressUnprocessableEntity().WithPayload(payload), unprocessableErr + } + response, err := h.ShipmentAddressUpdateRequester.RequestShipmentDeliveryAddressUpdate(appCtx, shipmentID, addressUpdate.NewAddress, addressUpdate.ContractorRemarks, eTag) if err != nil { diff --git a/pkg/handlers/primeapi/mto_shipment_address.go b/pkg/handlers/primeapi/mto_shipment_address.go index 5f699f384c1..395fc89f11a 100644 --- a/pkg/handlers/primeapi/mto_shipment_address.go +++ b/pkg/handlers/primeapi/mto_shipment_address.go @@ -1,6 +1,9 @@ package primeapi import ( + "context" + "fmt" + "github.com/go-openapi/runtime/middleware" "github.com/gofrs/uuid" "go.uber.org/zap" @@ -19,6 +22,7 @@ import ( type UpdateMTOShipmentAddressHandler struct { handlers.HandlerConfig MTOShipmentAddressUpdater services.MTOShipmentAddressUpdater + services.VLocation } // Handle updates an address on a shipment @@ -60,6 +64,52 @@ func (h UpdateMTOShipmentAddressHandler) Handle(params mtoshipmentops.UpdateMTOS newAddress := payloads.AddressModel(payload) newAddress.ID = addressID + /** Feature Flag - Alaska - Determines if AK can be included/excluded **/ + isAlaskaEnabled := false + akFeatureFlagName := "enable_alaska" + flag, err := h.FeatureFlagFetcher().GetBooleanFlagForUser(context.TODO(), appCtx, akFeatureFlagName, map[string]string{}) + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", akFeatureFlagName), zap.Error(err)) + } else { + isAlaskaEnabled = flag.Match + } + + /** Feature Flag - Hawaii - Determines if HI can be included/excluded **/ + isHawaiiEnabled := false + hiFeatureFlagName := "enable_hawaii" + flag, err = h.FeatureFlagFetcher().GetBooleanFlagForUser(context.TODO(), appCtx, hiFeatureFlagName, map[string]string{}) + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", hiFeatureFlagName), zap.Error(err)) + } else { + isHawaiiEnabled = flag.Match + } + + // build states to exlude filter list + statesToExclude := make([]string, 0) + if !isAlaskaEnabled { + statesToExclude = append(statesToExclude, "AK") + } + if !isHawaiiEnabled { + statesToExclude = append(statesToExclude, "HI") + } + + addressSearch := newAddress.City + ", " + newAddress.State + " " + newAddress.PostalCode + + locationList, err := h.GetLocationsByZipCityState(appCtx, addressSearch, statesToExclude, true) + if err != nil { + serverError := apperror.NewInternalServerError("Error searching for address") + errStr := serverError.Error() // we do this because InternalServerError wants a *string + appCtx.Logger().Warn(serverError.Error()) + payload := payloads.InternalServerError(&errStr, h.GetTraceIDFromRequest(params.HTTPRequest)) + return mtoshipmentops.NewUpdateMTOShipmentAddressInternalServerError().WithPayload(payload), serverError + } else if len(*locationList) == 0 { + unprocessableErr := apperror.NewUnprocessableEntityError( + fmt.Sprintf("primeapi.UpdateMTOShipmentAddress: could not find the provided location: %s", addressSearch)) + appCtx.Logger().Warn(unprocessableErr.Error()) + payload := payloads.ValidationError(unprocessableErr.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), nil) + return mtoshipmentops.NewUpdateMTOShipmentAddressUnprocessableEntity().WithPayload(payload), unprocessableErr + } + // Call the service object updatedAddress, err := h.MTOShipmentAddressUpdater.UpdateMTOShipmentAddress(appCtx, newAddress, mtoShipmentID, eTag, true) diff --git a/pkg/handlers/primeapi/mto_shipment_address_test.go b/pkg/handlers/primeapi/mto_shipment_address_test.go index 71235074946..cca597695c7 100644 --- a/pkg/handlers/primeapi/mto_shipment_address_test.go +++ b/pkg/handlers/primeapi/mto_shipment_address_test.go @@ -15,7 +15,9 @@ import ( "github.com/transcom/mymove/pkg/handlers/primeapi/payloads" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/route/mocks" + "github.com/transcom/mymove/pkg/services" "github.com/transcom/mymove/pkg/services/address" + servicemocks "github.com/transcom/mymove/pkg/services/mocks" mtoshipment "github.com/transcom/mymove/pkg/services/mto_shipment" ) @@ -43,24 +45,27 @@ func (suite *HandlerSuite) TestUpdateMTOShipmentAddressHandler() { planner := &mocks.Planner{} addressCreator := address.NewAddressCreator() addressUpdater := address.NewAddressUpdater() + vLocationServices := address.NewVLocation() planner.On("ZipTransitDistance", mock.AnythingOfType("*appcontext.appContext"), mock.Anything, mock.Anything, false, ).Return(400, nil) + // Create handler handler := UpdateMTOShipmentAddressHandler{ suite.HandlerConfig(), mtoshipment.NewMTOShipmentAddressUpdater(planner, addressCreator, addressUpdater), + vLocationServices, } return handler, availableMove } newAddress := models.Address{ StreetAddress1: "7 Q St", - City: "Framington", - State: "MA", + City: "Acmar", + State: "AL", PostalCode: "35004", } @@ -120,7 +125,7 @@ func (suite *HandlerSuite) TestUpdateMTOShipmentAddressHandler() { StreetAddress3: models.StringPointer("441 SW Río de la Plata Drive"), City: "Alameda", State: "CA", - PostalCode: "35004", + PostalCode: "94502", } // Update with new address @@ -353,4 +358,208 @@ func (suite *HandlerSuite) TestUpdateMTOShipmentAddressHandler() { response := handler.Handle(params) suite.IsType(&mtoshipmentops.UpdateMTOShipmentAddressUnprocessableEntity{}, response) }) + + suite.Run("Failure - Unprocessable when updating address with invalid data", func() { + // Testcase: address is updated on a shipment that's available to MTO with invalid address + // Expected: Failure response 422 + // Under Test: UpdateMTOShipmentAddress handler code and mtoShipmentAddressUpdater service object + handler, availableMove := setupTestData() + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: availableMove, + LinkOnly: true, + }, + }, nil) + newAddress2 := models.Address{ + StreetAddress1: "7 Q St", + StreetAddress2: models.StringPointer("6622 Airport Way S #1430"), + StreetAddress3: models.StringPointer("441 SW Río de la Plata Drive"), + City: "Bad City", + State: "CA", + PostalCode: "99999", // invalid postal code + } + + // Update with new address + payload := payloads.Address(&newAddress2) + req := httptest.NewRequest("PUT", fmt.Sprintf("/mto-shipments/%s/addresses/%s", shipment.ID.String(), shipment.ID.String()), nil) + params := mtoshipmentops.UpdateMTOShipmentAddressParams{ + HTTPRequest: req, + AddressID: *handlers.FmtUUID(shipment.PickupAddress.ID), + MtoShipmentID: *handlers.FmtUUID(shipment.ID), + Body: payload, + IfMatch: etag.GenerateEtag(shipment.PickupAddress.UpdatedAt), + } + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + // Run handler and check response + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.UpdateMTOShipmentAddressUnprocessableEntity{}, response) + }) + + suite.Run("Failure - Unprocessable with AK FF off and valid AK address", func() { + // Testcase: address is updated on a shipment that's available to MTO with AK address but FF off + // Expected: Failure response 422 + // Under Test: UpdateMTOShipmentAddress handler code and mtoShipmentAddressUpdater service object + handler, availableMove := setupTestData() + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: availableMove, + LinkOnly: true, + }, + }, nil) + newAddress2 := models.Address{ + StreetAddress1: "7 Q St", + StreetAddress2: models.StringPointer("6622 Airport Way S #1430"), + StreetAddress3: models.StringPointer("441 SW Río de la Plata Drive"), + City: "JUNEAU", + State: "AK", + PostalCode: "99801", + } + + // setting the AK flag to false and use a valid address + handlerConfig := suite.HandlerConfig() + + expectedFeatureFlag := services.FeatureFlag{ + Key: "enable_alaska", + Match: false, + } + + mockFeatureFlagFetcher := &servicemocks.FeatureFlagFetcher{} + mockFeatureFlagFetcher.On("GetBooleanFlagForUser", + mock.Anything, + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("string"), + mock.Anything, + ).Return(expectedFeatureFlag, nil) + handlerConfig.SetFeatureFlagFetcher(mockFeatureFlagFetcher) + handler.HandlerConfig = handlerConfig + + // Update with new address + payload := payloads.Address(&newAddress2) + req := httptest.NewRequest("PUT", fmt.Sprintf("/mto-shipments/%s/addresses/%s", shipment.ID.String(), shipment.ID.String()), nil) + params := mtoshipmentops.UpdateMTOShipmentAddressParams{ + HTTPRequest: req, + AddressID: *handlers.FmtUUID(shipment.PickupAddress.ID), + MtoShipmentID: *handlers.FmtUUID(shipment.ID), + Body: payload, + IfMatch: etag.GenerateEtag(shipment.PickupAddress.UpdatedAt), + } + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + // Run handler and check response + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.UpdateMTOShipmentAddressUnprocessableEntity{}, response) + }) + + suite.Run("Failure - Unprocessable with HI FF off and valid HI address", func() { + // Testcase: address is updated on a shipment that's available to MTO with HI address but FF off + // Expected: Failure response 422 + // Under Test: UpdateMTOShipmentAddress handler code and mtoShipmentAddressUpdater service object + handler, availableMove := setupTestData() + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: availableMove, + LinkOnly: true, + }, + }, nil) + newAddress2 := models.Address{ + StreetAddress1: "7 Q St", + StreetAddress2: models.StringPointer("6622 Airport Way S #1430"), + StreetAddress3: models.StringPointer("441 SW Río de la Plata Drive"), + City: "HONOLULU", + State: "HI", + PostalCode: "96835", + } + + // setting the HI flag to false and use a valid address + handlerConfig := suite.HandlerConfig() + + expectedFeatureFlag := services.FeatureFlag{ + Key: "enable_alaska", + Match: false, + } + + mockFeatureFlagFetcher := &servicemocks.FeatureFlagFetcher{} + mockFeatureFlagFetcher.On("GetBooleanFlagForUser", + mock.Anything, + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("string"), + mock.Anything, + ).Return(expectedFeatureFlag, nil) + handlerConfig.SetFeatureFlagFetcher(mockFeatureFlagFetcher) + handler.HandlerConfig = handlerConfig + + // Update with new address + payload := payloads.Address(&newAddress2) + req := httptest.NewRequest("PUT", fmt.Sprintf("/mto-shipments/%s/addresses/%s", shipment.ID.String(), shipment.ID.String()), nil) + params := mtoshipmentops.UpdateMTOShipmentAddressParams{ + HTTPRequest: req, + AddressID: *handlers.FmtUUID(shipment.PickupAddress.ID), + MtoShipmentID: *handlers.FmtUUID(shipment.ID), + Body: payload, + IfMatch: etag.GenerateEtag(shipment.PickupAddress.UpdatedAt), + } + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + // Run handler and check response + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.UpdateMTOShipmentAddressUnprocessableEntity{}, response) + }) + + suite.Run("Failure - Internal Error mock GetLocationsByZipCityState return error", func() { + // Testcase: address is updated on a shipment that's available to MTO with invalid address + // Expected: Failure response 422 + // Under Test: UpdateMTOShipmentAddress handler code and mtoShipmentAddressUpdater service object + handler, availableMove := setupTestData() + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: availableMove, + LinkOnly: true, + }, + }, nil) + newAddress2 := models.Address{ + StreetAddress1: "7 Q St", + StreetAddress2: models.StringPointer("6622 Airport Way S #1430"), + StreetAddress3: models.StringPointer("441 SW Río de la Plata Drive"), + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + } + + // Update with new address + payload := payloads.Address(&newAddress2) + req := httptest.NewRequest("PUT", fmt.Sprintf("/mto-shipments/%s/addresses/%s", shipment.ID.String(), shipment.ID.String()), nil) + params := mtoshipmentops.UpdateMTOShipmentAddressParams{ + HTTPRequest: req, + AddressID: *handlers.FmtUUID(shipment.PickupAddress.ID), + MtoShipmentID: *handlers.FmtUUID(shipment.ID), + Body: payload, + IfMatch: etag.GenerateEtag(shipment.PickupAddress.UpdatedAt), + } + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + expectedError := models.ErrFetchNotFound + vLocationFetcher := &servicemocks.VLocation{} + vLocationFetcher.On("GetLocationsByZipCityState", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + ).Return(nil, expectedError).Once() + + handler.VLocation = vLocationFetcher + + // Run handler and check response + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.UpdateMTOShipmentAddressInternalServerError{}, response) + }) } diff --git a/pkg/handlers/primeapi/mto_shipment_test.go b/pkg/handlers/primeapi/mto_shipment_test.go index 1a9b23790ac..be1af9713d3 100644 --- a/pkg/handlers/primeapi/mto_shipment_test.go +++ b/pkg/handlers/primeapi/mto_shipment_test.go @@ -36,6 +36,7 @@ import ( func (suite *HandlerSuite) TestUpdateShipmentDestinationAddressHandler() { req := httptest.NewRequest("POST", "/mto-shipments/{mtoShipmentID}/shipment-address-updates", nil) + vLocationServices := address.NewVLocation() makeSubtestData := func() mtoshipmentops.UpdateShipmentDestinationAddressParams { contractorRemark := "This is a contractor remark" @@ -57,12 +58,130 @@ func (suite *HandlerSuite) TestUpdateShipmentDestinationAddressHandler() { return params } + + suite.Run("POST failure - 500 Internal Server GetLocationsByZipCityState returns error", func() { + subtestData := makeSubtestData() + mockCreator := mocks.ShipmentAddressUpdateRequester{} + + expectedError := models.ErrFetchNotFound + vLocationFetcher := &mocks.VLocation{} + vLocationFetcher.On("GetLocationsByZipCityState", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + ).Return(nil, expectedError).Once() + + handler := UpdateShipmentDestinationAddressHandler{ + HandlerConfig: suite.HandlerConfig(), + ShipmentAddressUpdateRequester: &mockCreator, + VLocation: vLocationFetcher, + } + + response := handler.Handle(subtestData) + suite.IsType(&mtoshipmentops.UpdateShipmentDestinationAddressInternalServerError{}, response) + }) + + suite.Run("POST failure - 422 Unprocessable Entity Error Invalid Address", func() { + subtestData := makeSubtestData() + mockCreator := mocks.ShipmentAddressUpdateRequester{} + vLocationServices := address.NewVLocation() + handler := UpdateShipmentDestinationAddressHandler{ + suite.HandlerConfig(), + &mockCreator, + vLocationServices, + } + + subtestData.Body.NewAddress.City = handlers.FmtString("Bad City") + // Validate incoming payload + suite.NoError(subtestData.Body.Validate(strfmt.Default)) + + response := handler.Handle(subtestData) + suite.IsType(&mtoshipmentops.UpdateShipmentDestinationAddressUnprocessableEntity{}, response) + }) + + suite.Run("POST failure - 422 Unprocessable Entity Error Valid AK Address FF off", func() { + subtestData := makeSubtestData() + mockCreator := mocks.ShipmentAddressUpdateRequester{} + vLocationServices := address.NewVLocation() + + // setting the AK flag to false and use a valid address + handlerConfig := suite.HandlerConfig() + + expectedFeatureFlag := services.FeatureFlag{ + Key: "enable_alaska", + Match: false, + } + + mockFeatureFlagFetcher := &mocks.FeatureFlagFetcher{} + mockFeatureFlagFetcher.On("GetBooleanFlagForUser", + mock.Anything, + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("string"), + mock.Anything, + ).Return(expectedFeatureFlag, nil) + handlerConfig.SetFeatureFlagFetcher(mockFeatureFlagFetcher) + handler := UpdateShipmentDestinationAddressHandler{ + handlerConfig, + &mockCreator, + vLocationServices, + } + + subtestData.Body.NewAddress.City = handlers.FmtString("JUNEAU") + subtestData.Body.NewAddress.State = handlers.FmtString("AK") + subtestData.Body.NewAddress.PostalCode = handlers.FmtString("99801") + // Validate incoming payload + suite.NoError(subtestData.Body.Validate(strfmt.Default)) + + response := handler.Handle(subtestData) + suite.IsType(&mtoshipmentops.UpdateShipmentDestinationAddressUnprocessableEntity{}, response) + }) + + suite.Run("POST failure - 422 Unprocessable Entity Error Valid HI Address FF off", func() { + subtestData := makeSubtestData() + mockCreator := mocks.ShipmentAddressUpdateRequester{} + vLocationServices := address.NewVLocation() + + // setting the AK flag to false and use a valid address + handlerConfig := suite.HandlerConfig() + + expectedFeatureFlag := services.FeatureFlag{ + Key: "enable_hawaii", + Match: false, + } + + mockFeatureFlagFetcher := &mocks.FeatureFlagFetcher{} + mockFeatureFlagFetcher.On("GetBooleanFlagForUser", + mock.Anything, + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("string"), + mock.Anything, + ).Return(expectedFeatureFlag, nil) + handlerConfig.SetFeatureFlagFetcher(mockFeatureFlagFetcher) + handler := UpdateShipmentDestinationAddressHandler{ + handlerConfig, + &mockCreator, + vLocationServices, + } + + subtestData.Body.NewAddress.City = handlers.FmtString("HONOLULU") + subtestData.Body.NewAddress.State = handlers.FmtString("HI") + subtestData.Body.NewAddress.PostalCode = handlers.FmtString("96835") + // Validate incoming payload + suite.NoError(subtestData.Body.Validate(strfmt.Default)) + + response := handler.Handle(subtestData) + suite.IsType(&mtoshipmentops.UpdateShipmentDestinationAddressUnprocessableEntity{}, response) + }) + suite.Run("POST failure - 422 Unprocessable Entity Error", func() { subtestData := makeSubtestData() mockCreator := mocks.ShipmentAddressUpdateRequester{} handler := UpdateShipmentDestinationAddressHandler{ suite.HandlerConfig(), &mockCreator, + vLocationServices, } // InvalidInputError should generate an UnprocessableEntity response error // Need verrs incorporated to satisfy swagger validation @@ -95,6 +214,7 @@ func (suite *HandlerSuite) TestUpdateShipmentDestinationAddressHandler() { handler := UpdateShipmentDestinationAddressHandler{ suite.HandlerConfig(), &mockCreator, + vLocationServices, } // NewConflictError should generate a RequestConflict response error err := apperror.NewConflictError(uuid.Nil, "unable to create ShipmentAddressUpdate") @@ -125,6 +245,7 @@ func (suite *HandlerSuite) TestUpdateShipmentDestinationAddressHandler() { handler := UpdateShipmentDestinationAddressHandler{ suite.HandlerConfig(), &mockCreator, + vLocationServices, } // NewNotFoundError should generate a RequestNotFound response error err := apperror.NewNotFoundError(uuid.Nil, "unable to create ShipmentAddressUpdate") @@ -155,6 +276,7 @@ func (suite *HandlerSuite) TestUpdateShipmentDestinationAddressHandler() { handler := UpdateShipmentDestinationAddressHandler{ suite.HandlerConfig(), &mockCreator, + vLocationServices, } // NewQueryError should generate an InternalServerError response error err := apperror.NewQueryError("", nil, "unable to reach database") diff --git a/pkg/handlers/primeapi/payloads/model_to_payload.go b/pkg/handlers/primeapi/payloads/model_to_payload.go index 3c8a3f1b292..5f5106e6569 100644 --- a/pkg/handlers/primeapi/payloads/model_to_payload.go +++ b/pkg/handlers/primeapi/payloads/model_to_payload.go @@ -706,6 +706,23 @@ func MTOServiceItem(mtoServiceItem *models.MTOServiceItem) primemessages.MTOServ SitCustomerContacted: handlers.FmtDatePtr(mtoServiceItem.SITCustomerContacted), SitRequestedDelivery: handlers.FmtDatePtr(mtoServiceItem.SITRequestedDelivery), } + case models.ReServiceCodeIOFSIT, models.ReServiceCodeIOASIT, models.ReServiceCodeIOPSIT, models.ReServiceCodeIOSFSC: + var sitDepartureDate time.Time + if mtoServiceItem.SITDepartureDate != nil { + sitDepartureDate = *mtoServiceItem.SITDepartureDate + } + payload = &primemessages.MTOServiceItemInternationalOriginSIT{ + ReServiceCode: handlers.FmtString(string(mtoServiceItem.ReService.Code)), + Reason: mtoServiceItem.Reason, + SitDepartureDate: handlers.FmtDate(sitDepartureDate), + SitEntryDate: handlers.FmtDatePtr(mtoServiceItem.SITEntryDate), + SitPostalCode: mtoServiceItem.SITPostalCode, + SitHHGActualOrigin: Address(mtoServiceItem.SITOriginHHGActualAddress), + SitHHGOriginalOrigin: Address(mtoServiceItem.SITOriginHHGOriginalAddress), + RequestApprovalsRequestedStatus: *mtoServiceItem.RequestedApprovalsRequestedStatus, + SitCustomerContacted: handlers.FmtDatePtr(mtoServiceItem.SITCustomerContacted), + SitRequestedDelivery: handlers.FmtDatePtr(mtoServiceItem.SITRequestedDelivery), + } case models.ReServiceCodeDDFSIT, models.ReServiceCodeDDASIT, models.ReServiceCodeDDDSIT, models.ReServiceCodeDDSFSC: var sitDepartureDate, firstAvailableDeliveryDate1, firstAvailableDeliveryDate2, dateOfContact1, dateOfContact2 time.Time var timeMilitary1, timeMilitary2 *string @@ -750,6 +767,50 @@ func MTOServiceItem(mtoServiceItem *models.MTOServiceItem) primemessages.MTOServ SitCustomerContacted: handlers.FmtDatePtr(mtoServiceItem.SITCustomerContacted), SitRequestedDelivery: handlers.FmtDatePtr(mtoServiceItem.SITRequestedDelivery), } + case models.ReServiceCodeIDFSIT, models.ReServiceCodeIDASIT, models.ReServiceCodeIDDSIT, models.ReServiceCodeIDSFSC: + var sitDepartureDate, firstAvailableDeliveryDate1, firstAvailableDeliveryDate2, dateOfContact1, dateOfContact2 time.Time + var timeMilitary1, timeMilitary2 *string + + if mtoServiceItem.SITDepartureDate != nil { + sitDepartureDate = *mtoServiceItem.SITDepartureDate + } + + firstContact := GetCustomerContact(mtoServiceItem.CustomerContacts, models.CustomerContactTypeFirst) + secondContact := GetCustomerContact(mtoServiceItem.CustomerContacts, models.CustomerContactTypeSecond) + timeMilitary1 = &firstContact.TimeMilitary + timeMilitary2 = &secondContact.TimeMilitary + + if !firstContact.DateOfContact.IsZero() { + dateOfContact1 = firstContact.DateOfContact + } + + if !secondContact.DateOfContact.IsZero() { + dateOfContact2 = secondContact.DateOfContact + } + + if !firstContact.FirstAvailableDeliveryDate.IsZero() { + firstAvailableDeliveryDate1 = firstContact.FirstAvailableDeliveryDate + } + + if !secondContact.FirstAvailableDeliveryDate.IsZero() { + firstAvailableDeliveryDate2 = secondContact.FirstAvailableDeliveryDate + } + + payload = &primemessages.MTOServiceItemInternationalDestSIT{ + ReServiceCode: handlers.FmtString(string(mtoServiceItem.ReService.Code)), + Reason: mtoServiceItem.Reason, + DateOfContact1: handlers.FmtDate(dateOfContact1), + TimeMilitary1: handlers.FmtStringPtrNonEmpty(timeMilitary1), + FirstAvailableDeliveryDate1: handlers.FmtDate(firstAvailableDeliveryDate1), + DateOfContact2: handlers.FmtDate(dateOfContact2), + TimeMilitary2: handlers.FmtStringPtrNonEmpty(timeMilitary2), + FirstAvailableDeliveryDate2: handlers.FmtDate(firstAvailableDeliveryDate2), + SitDepartureDate: handlers.FmtDate(sitDepartureDate), + SitEntryDate: handlers.FmtDatePtr(mtoServiceItem.SITEntryDate), + SitDestinationFinalAddress: Address(mtoServiceItem.SITDestinationFinalAddress), + SitCustomerContacted: handlers.FmtDatePtr(mtoServiceItem.SITCustomerContacted), + SitRequestedDelivery: handlers.FmtDatePtr(mtoServiceItem.SITRequestedDelivery), + } case models.ReServiceCodeDCRT, models.ReServiceCodeDUCRT: item := GetDimension(mtoServiceItem.Dimensions, models.DimensionTypeItem) @@ -814,7 +875,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), @@ -1145,3 +1206,31 @@ func GetCustomerContact(customerContacts models.MTOServiceItemCustomerContacts, return models.MTOServiceItemCustomerContact{} } + +// VLocation payload +func VLocation(vLocation *models.VLocation) *primemessages.VLocation { + if vLocation == nil { + return nil + } + if *vLocation == (models.VLocation{}) { + return nil + } + + return &primemessages.VLocation{ + City: vLocation.CityName, + State: vLocation.StateName, + PostalCode: vLocation.UsprZipID, + County: &vLocation.UsprcCountyNm, + UsPostRegionCitiesID: *handlers.FmtUUID(*vLocation.UsPostRegionCitiesID), + } +} + +// VLocations payload +func VLocations(vLocations models.VLocations) primemessages.VLocations { + payload := make(primemessages.VLocations, len(vLocations)) + for i, vLocation := range vLocations { + copyOfVLocation := vLocation + payload[i] = VLocation(©OfVLocation) + } + return payload +} diff --git a/pkg/handlers/primeapi/payloads/model_to_payload_test.go b/pkg/handlers/primeapi/payloads/model_to_payload_test.go index f070c4342d2..3ec3fda6327 100644 --- a/pkg/handlers/primeapi/payloads/model_to_payload_test.go +++ b/pkg/handlers/primeapi/payloads/model_to_payload_test.go @@ -740,6 +740,136 @@ func (suite *PayloadsSuite) TestAddress() { suite.Equal(strfmt.UUID(""), result.UsPostRegionCitiesID) } +func (suite *PayloadsSuite) TestMTOServiceItemDestSIT() { + reServiceCode := models.ReServiceCodeDDFSIT + reason := "reason" + dateOfContact1 := time.Now() + timeMilitary1 := "1500Z" + firstAvailableDeliveryDate1 := dateOfContact1.AddDate(0, 0, 10) + dateOfContact2 := time.Now().AddDate(0, 0, 5) + timeMilitary2 := "1300Z" + firstAvailableDeliveryDate2 := dateOfContact2.AddDate(0, 0, 10) + sitDepartureDate := time.Now().AddDate(0, 1, 0) + sitEntryDate := time.Now().AddDate(0, 0, -30) + finalAddress := models.Address{ + StreetAddress1: "dummyStreet", + City: "dummyCity", + State: "FL", + PostalCode: "55555", + } + mtoShipmentID := uuid.Must(uuid.NewV4()) + + mtoServiceItemDestSIT := &models.MTOServiceItem{ + ID: uuid.Must(uuid.NewV4()), + ReService: models.ReService{Code: reServiceCode}, + Reason: &reason, + SITDepartureDate: &sitDepartureDate, + SITEntryDate: &sitEntryDate, + SITDestinationFinalAddress: &finalAddress, + MTOShipmentID: &mtoShipmentID, + CustomerContacts: models.MTOServiceItemCustomerContacts{ + models.MTOServiceItemCustomerContact{ + DateOfContact: dateOfContact1, + TimeMilitary: timeMilitary1, + FirstAvailableDeliveryDate: firstAvailableDeliveryDate1, + Type: models.CustomerContactTypeFirst, + }, + models.MTOServiceItemCustomerContact{ + DateOfContact: dateOfContact2, + TimeMilitary: timeMilitary2, + FirstAvailableDeliveryDate: firstAvailableDeliveryDate2, + Type: models.CustomerContactTypeSecond, + }, + }, + } + + resultDestSIT := MTOServiceItem(mtoServiceItemDestSIT) + suite.NotNil(resultDestSIT) + destSIT, ok := resultDestSIT.(*primemessages.MTOServiceItemDestSIT) + suite.True(ok) + + suite.Equal(string(reServiceCode), string(*destSIT.ReServiceCode)) + suite.Equal(reason, *destSIT.Reason) + suite.Equal(strfmt.Date(sitDepartureDate).String(), destSIT.SitDepartureDate.String()) + suite.Equal(strfmt.Date(sitEntryDate).String(), destSIT.SitEntryDate.String()) + suite.Equal(strfmt.Date(dateOfContact1).String(), destSIT.DateOfContact1.String()) + suite.Equal(timeMilitary1, *destSIT.TimeMilitary1) + suite.Equal(strfmt.Date(firstAvailableDeliveryDate1).String(), destSIT.FirstAvailableDeliveryDate1.String()) + suite.Equal(strfmt.Date(dateOfContact2).String(), destSIT.DateOfContact2.String()) + suite.Equal(timeMilitary2, *destSIT.TimeMilitary2) + suite.Equal(strfmt.Date(firstAvailableDeliveryDate2).String(), destSIT.FirstAvailableDeliveryDate2.String()) + suite.Equal(finalAddress.StreetAddress1, *destSIT.SitDestinationFinalAddress.StreetAddress1) + suite.Equal(finalAddress.City, *destSIT.SitDestinationFinalAddress.City) + suite.Equal(finalAddress.State, *destSIT.SitDestinationFinalAddress.State) + suite.Equal(finalAddress.PostalCode, *destSIT.SitDestinationFinalAddress.PostalCode) + suite.Equal(mtoShipmentID.String(), destSIT.MtoShipmentID().String()) +} + +func (suite *PayloadsSuite) TestMTOServiceItemInternationalDestSIT() { + reServiceCode := models.ReServiceCodeIDFSIT + reason := "reason" + dateOfContact1 := time.Now() + timeMilitary1 := "1500Z" + firstAvailableDeliveryDate1 := dateOfContact1.AddDate(0, 0, 10) + dateOfContact2 := time.Now().AddDate(0, 0, 5) + timeMilitary2 := "1300Z" + firstAvailableDeliveryDate2 := dateOfContact2.AddDate(0, 0, 10) + sitDepartureDate := time.Now().AddDate(0, 1, 0) + sitEntryDate := time.Now().AddDate(0, 0, -30) + finalAddress := models.Address{ + StreetAddress1: "dummyStreet", + City: "dummyCity", + State: "FL", + PostalCode: "55555", + } + mtoShipmentID := uuid.Must(uuid.NewV4()) + + mtoServiceItemDestSIT := &models.MTOServiceItem{ + ID: uuid.Must(uuid.NewV4()), + ReService: models.ReService{Code: reServiceCode}, + Reason: &reason, + SITDepartureDate: &sitDepartureDate, + SITEntryDate: &sitEntryDate, + SITDestinationFinalAddress: &finalAddress, + MTOShipmentID: &mtoShipmentID, + CustomerContacts: models.MTOServiceItemCustomerContacts{ + models.MTOServiceItemCustomerContact{ + DateOfContact: dateOfContact1, + TimeMilitary: timeMilitary1, + FirstAvailableDeliveryDate: firstAvailableDeliveryDate1, + Type: models.CustomerContactTypeFirst, + }, + models.MTOServiceItemCustomerContact{ + DateOfContact: dateOfContact2, + TimeMilitary: timeMilitary2, + FirstAvailableDeliveryDate: firstAvailableDeliveryDate2, + Type: models.CustomerContactTypeSecond, + }, + }, + } + + resultDestSIT := MTOServiceItem(mtoServiceItemDestSIT) + suite.NotNil(resultDestSIT) + destSIT, ok := resultDestSIT.(*primemessages.MTOServiceItemInternationalDestSIT) + suite.True(ok) + + suite.Equal(string(reServiceCode), string(*destSIT.ReServiceCode)) + suite.Equal(reason, *destSIT.Reason) + suite.Equal(strfmt.Date(sitDepartureDate).String(), destSIT.SitDepartureDate.String()) + suite.Equal(strfmt.Date(sitEntryDate).String(), destSIT.SitEntryDate.String()) + suite.Equal(strfmt.Date(dateOfContact1).String(), destSIT.DateOfContact1.String()) + suite.Equal(timeMilitary1, *destSIT.TimeMilitary1) + suite.Equal(strfmt.Date(firstAvailableDeliveryDate1).String(), destSIT.FirstAvailableDeliveryDate1.String()) + suite.Equal(strfmt.Date(dateOfContact2).String(), destSIT.DateOfContact2.String()) + suite.Equal(timeMilitary2, *destSIT.TimeMilitary2) + suite.Equal(strfmt.Date(firstAvailableDeliveryDate2).String(), destSIT.FirstAvailableDeliveryDate2.String()) + suite.Equal(finalAddress.StreetAddress1, *destSIT.SitDestinationFinalAddress.StreetAddress1) + suite.Equal(finalAddress.City, *destSIT.SitDestinationFinalAddress.City) + suite.Equal(finalAddress.State, *destSIT.SitDestinationFinalAddress.State) + suite.Equal(finalAddress.PostalCode, *destSIT.SitDestinationFinalAddress.PostalCode) + suite.Equal(mtoShipmentID.String(), destSIT.MtoShipmentID().String()) +} + func (suite *PayloadsSuite) TestMTOServiceItemDCRTandDOFSITandDDFSIT() { reServiceCode := models.ReServiceCodeDCRT reServiceCodeSIT := models.ReServiceCodeDOFSIT @@ -854,6 +984,120 @@ func (suite *PayloadsSuite) TestMTOServiceItemDCRTandDOFSITandDDFSIT() { suite.True(ok) } +func (suite *PayloadsSuite) TestMTOServiceItemICRTandIOFSITandIDFSIT() { + reServiceCode := models.ReServiceCodeICRT + reServiceCodeSIT := models.ReServiceCodeIOFSIT + reServiceCodeIDFSIT := models.ReServiceCodeIDFSIT + + reason := "reason" + dateOfContact1 := time.Now() + timeMilitary1 := "1500Z" + firstAvailableDeliveryDate1 := dateOfContact1.AddDate(0, 0, 10) + dateOfContact2 := time.Now().AddDate(0, 0, 5) + timeMilitary2 := "1300Z" + firstAvailableDeliveryDate2 := dateOfContact2.AddDate(0, 0, 10) + + mtoServiceItemICRT := &models.MTOServiceItem{ + ID: uuid.Must(uuid.NewV4()), + ReService: models.ReService{Code: reServiceCode}, + Reason: &reason, + CustomerContacts: models.MTOServiceItemCustomerContacts{ + models.MTOServiceItemCustomerContact{ + DateOfContact: dateOfContact1, + TimeMilitary: timeMilitary1, + FirstAvailableDeliveryDate: firstAvailableDeliveryDate1, + Type: models.CustomerContactTypeFirst, + }, + models.MTOServiceItemCustomerContact{ + DateOfContact: dateOfContact2, + TimeMilitary: timeMilitary2, + FirstAvailableDeliveryDate: firstAvailableDeliveryDate2, + Type: models.CustomerContactTypeSecond, + }, + }, + } + year, month, day := time.Now().Date() + aWeekAgo := time.Date(year, month, day-7, 0, 0, 0, 0, time.UTC) + departureDate := aWeekAgo.Add(time.Hour * 24 * 30) + actualPickupAddress := factory.BuildAddress(nil, nil, []factory.Trait{factory.GetTraitAddress2}) + requestApprovalRequestedStatus := false + mtoServiceItemIOFSIT := &models.MTOServiceItem{ + ID: uuid.Must(uuid.NewV4()), + ReService: models.ReService{Code: reServiceCodeSIT}, + Reason: &reason, + SITDepartureDate: &departureDate, + SITEntryDate: &aWeekAgo, + SITPostalCode: models.StringPointer("90210"), + SITOriginHHGActualAddress: &actualPickupAddress, + SITCustomerContacted: &aWeekAgo, + SITRequestedDelivery: &aWeekAgo, + SITOriginHHGOriginalAddress: &models.Address{ + StreetAddress1: "dummyStreet2", + City: "dummyCity2", + State: "FL", + PostalCode: "55555", + }, + RequestedApprovalsRequestedStatus: &requestApprovalRequestedStatus, + CustomerContacts: models.MTOServiceItemCustomerContacts{ + models.MTOServiceItemCustomerContact{ + DateOfContact: dateOfContact1, + TimeMilitary: timeMilitary1, + FirstAvailableDeliveryDate: firstAvailableDeliveryDate1, + Type: models.CustomerContactTypeFirst, + }, + models.MTOServiceItemCustomerContact{ + DateOfContact: dateOfContact2, + TimeMilitary: timeMilitary2, + FirstAvailableDeliveryDate: firstAvailableDeliveryDate2, + Type: models.CustomerContactTypeSecond, + }, + }, + } + mtoServiceItemIDFSIT := &models.MTOServiceItem{ + ID: uuid.Must(uuid.NewV4()), + ReService: models.ReService{Code: reServiceCodeIDFSIT}, + Reason: &reason, + SITDepartureDate: &departureDate, + SITEntryDate: &aWeekAgo, + SITPostalCode: models.StringPointer("90210"), + SITOriginHHGActualAddress: &actualPickupAddress, + SITCustomerContacted: &aWeekAgo, + SITRequestedDelivery: &aWeekAgo, + SITOriginHHGOriginalAddress: &models.Address{ + StreetAddress1: "dummyStreet2", + City: "dummyCity2", + State: "FL", + PostalCode: "55555", + }, + RequestedApprovalsRequestedStatus: &requestApprovalRequestedStatus, + CustomerContacts: models.MTOServiceItemCustomerContacts{ + models.MTOServiceItemCustomerContact{ + DateOfContact: dateOfContact1, + TimeMilitary: timeMilitary1, + FirstAvailableDeliveryDate: firstAvailableDeliveryDate1, + Type: models.CustomerContactTypeFirst, + }, + models.MTOServiceItemCustomerContact{ + DateOfContact: dateOfContact2, + TimeMilitary: timeMilitary2, + FirstAvailableDeliveryDate: firstAvailableDeliveryDate2, + Type: models.CustomerContactTypeSecond, + }, + }, + } + + resultICRT := MTOServiceItem(mtoServiceItemICRT) + resultIOFSIT := MTOServiceItem(mtoServiceItemIOFSIT) + resultIDFSIT := MTOServiceItem(mtoServiceItemIDFSIT) + + suite.NotNil(resultICRT) + suite.NotNil(resultIOFSIT) + suite.NotNil(resultIDFSIT) + _, ok := resultICRT.(*primemessages.MTOServiceItemInternationalCrating) + + suite.True(ok) +} + func (suite *PayloadsSuite) TestMTOServiceItemICRTandIUCRT() { icrtReServiceCode := models.ReServiceCodeICRT iucrtReServiceCode := models.ReServiceCodeIUCRT @@ -960,7 +1204,7 @@ func (suite *PayloadsSuite) TestMTOServiceItemDDSHUT() { suite.NotNil(resultDDSHUT) - _, ok := resultDDSHUT.(*primemessages.MTOServiceItemShuttle) + _, ok := resultDDSHUT.(*primemessages.MTOServiceItemDomesticShuttle) suite.True(ok) } @@ -1244,3 +1488,30 @@ func (suite *PayloadsSuite) TestMTOServiceItemsPODFSC() { suite.Equal(portLocation.Port.PortCode, internationalFuelSurchargeItem.PortCode) suite.Equal(podfscServiceItem.ReService.Code.String(), internationalFuelSurchargeItem.ReServiceCode) } + +func (suite *PayloadsSuite) TestVLocation() { + suite.Run("correctly maps VLocation with all fields populated", func() { + city := "LOS ANGELES" + state := "CA" + postalCode := "90210" + county := "LOS ANGELES" + usPostRegionCityID := uuid.Must(uuid.NewV4()) + + vLocation := &models.VLocation{ + CityName: city, + StateName: state, + UsprZipID: postalCode, + UsprcCountyNm: county, + UsPostRegionCitiesID: &usPostRegionCityID, + } + + payload := VLocation(vLocation) + + suite.IsType(payload, &primemessages.VLocation{}) + suite.Equal(handlers.FmtUUID(usPostRegionCityID), &payload.UsPostRegionCitiesID, "Expected UsPostRegionCitiesID to match") + suite.Equal(city, payload.City, "Expected City to match") + suite.Equal(state, payload.State, "Expected State to match") + suite.Equal(postalCode, payload.PostalCode, "Expected PostalCode to match") + suite.Equal(county, *(payload.County), "Expected County to match") + }) +} diff --git a/pkg/handlers/primeapi/payloads/payload_to_model.go b/pkg/handlers/primeapi/payloads/payload_to_model.go index 53362a88b84..c01b4913c18 100644 --- a/pkg/handlers/primeapi/payloads/payload_to_model.go +++ b/pkg/handlers/primeapi/payloads/payload_to_model.go @@ -233,7 +233,7 @@ func PPMShipmentModelFromCreate(ppmShipment *primemessages.CreatePPMShipment) *m StreetAddress1: "Deprecated Endpoint Prime V2", StreetAddress2: models.StringPointer("Endpoint no longer supported"), StreetAddress3: models.StringPointer("Update address field to appropriate values"), - City: "DEPV2", + City: "Beverly Hills", State: "CA", PostalCode: "90210", } @@ -470,6 +470,49 @@ func MTOServiceItemModel(mtoServiceItem primemessages.MTOServiceItem) (*models.M model.SITOriginHHGActualAddressID = &model.SITOriginHHGActualAddress.ID } + case primemessages.MTOServiceItemModelTypeMTOServiceItemInternationalOriginSIT: + + originsit := mtoServiceItem.(*primemessages.MTOServiceItemInternationalOriginSIT) + + if originsit.ReServiceCode != nil { + model.ReService.Code = models.ReServiceCode(*originsit.ReServiceCode) + } + + model.Reason = originsit.Reason + // Check for reason required field on a IOASIT + if model.ReService.Code == models.ReServiceCodeIOASIT { + reasonVerrs := validateReasonInternationalOriginSIT(*originsit) + + if reasonVerrs.HasAny() { + return nil, reasonVerrs + } + } + + if model.ReService.Code == models.ReServiceCodeIOFSIT { + reasonVerrs := validateReasonInternationalOriginSIT(*originsit) + + if reasonVerrs.HasAny() { + return nil, reasonVerrs + } + } + + sitEntryDate := handlers.FmtDatePtrToPopPtr(originsit.SitEntryDate) + + if sitEntryDate != nil { + model.SITEntryDate = sitEntryDate + } + + if originsit.SitDepartureDate != nil { + model.SITDepartureDate = handlers.FmtDatePtrToPopPtr(originsit.SitDepartureDate) + } + + model.SITPostalCode = originsit.SitPostalCode + + model.SITOriginHHGActualAddress = AddressModel(originsit.SitHHGActualOrigin) + if model.SITOriginHHGActualAddress != nil { + model.SITOriginHHGActualAddressID = &model.SITOriginHHGActualAddress.ID + } + case primemessages.MTOServiceItemModelTypeMTOServiceItemDestSIT: destsit := mtoServiceItem.(*primemessages.MTOServiceItemDestSIT) @@ -529,6 +572,65 @@ func MTOServiceItemModel(mtoServiceItem primemessages.MTOServiceItem) (*models.M model.SITDestinationFinalAddressID = &model.SITDestinationFinalAddress.ID } + case primemessages.MTOServiceItemModelTypeMTOServiceItemInternationalDestSIT: + destsit := mtoServiceItem.(*primemessages.MTOServiceItemInternationalDestSIT) + + if destsit.ReServiceCode != nil { + model.ReService.Code = models.ReServiceCode(*destsit.ReServiceCode) + + } + + model.Reason = destsit.Reason + sitEntryDate := handlers.FmtDatePtrToPopPtr(destsit.SitEntryDate) + + // Check for required fields on a IDFSIT + if model.ReService.Code == models.ReServiceCodeIDFSIT { + verrs := validateIDFSITForCreate(*destsit) + reasonVerrs := validateReasonInternationalDestSIT(*destsit) + + if verrs.HasAny() { + return nil, verrs + } + + if reasonVerrs.HasAny() { + return nil, reasonVerrs + } + } + + var customerContacts models.MTOServiceItemCustomerContacts + + if destsit.TimeMilitary1 != nil && destsit.FirstAvailableDeliveryDate1 != nil && destsit.DateOfContact1 != nil { + customerContacts = append(customerContacts, models.MTOServiceItemCustomerContact{ + Type: models.CustomerContactTypeFirst, + DateOfContact: time.Time(*destsit.DateOfContact1), + TimeMilitary: *destsit.TimeMilitary1, + FirstAvailableDeliveryDate: time.Time(*destsit.FirstAvailableDeliveryDate1), + }) + } + if destsit.TimeMilitary2 != nil && destsit.FirstAvailableDeliveryDate2 != nil && destsit.DateOfContact2 != nil { + customerContacts = append(customerContacts, models.MTOServiceItemCustomerContact{ + Type: models.CustomerContactTypeSecond, + DateOfContact: time.Time(*destsit.DateOfContact2), + TimeMilitary: *destsit.TimeMilitary2, + FirstAvailableDeliveryDate: time.Time(*destsit.FirstAvailableDeliveryDate2), + }) + } + + model.CustomerContacts = customerContacts + + if sitEntryDate != nil { + model.SITEntryDate = sitEntryDate + } + + if destsit.SitDepartureDate != nil { + model.SITDepartureDate = handlers.FmtDatePtrToPopPtr(destsit.SitDepartureDate) + } + + model.SITDestinationFinalAddress = AddressModel(destsit.SitDestinationFinalAddress) + if model.SITDestinationFinalAddress != nil { + model.SITDestinationFinalAddressID = &model.SITDestinationFinalAddress.ID + } + case primemessages.MTOServiceItemModelTypeMTOServiceItemShuttle: shuttleService := mtoServiceItem.(*primemessages.MTOServiceItemShuttle) // values to get from payload @@ -537,6 +639,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 @@ -852,6 +962,31 @@ func validateDDFSITForCreate(m primemessages.MTOServiceItemDestSIT) *validate.Er return verrs } +// validateIDFSITForCreate validates IDFSIT service item has all required fields +func validateIDFSITForCreate(m primemessages.MTOServiceItemInternationalDestSIT) *validate.Errors { + verrs := validate.NewErrors() + + if m.FirstAvailableDeliveryDate1 == nil && m.DateOfContact1 != nil && m.TimeMilitary1 != nil { + verrs.Add("firstAvailableDeliveryDate1", "firstAvailableDeliveryDate1, dateOfContact1, and timeMilitary1 must be provided together in body.") + } + if m.DateOfContact1 == nil && m.TimeMilitary1 != nil && m.FirstAvailableDeliveryDate1 != nil { + verrs.Add("DateOfContact1", "dateOfContact1, timeMilitary1, and firstAvailableDeliveryDate1 must be provided together in body.") + } + if m.TimeMilitary1 == nil && m.DateOfContact1 != nil && m.FirstAvailableDeliveryDate1 != nil { + verrs.Add("timeMilitary1", "timeMilitary1, dateOfContact1, and firstAvailableDeliveryDate1 must be provided together in body.") + } + if m.FirstAvailableDeliveryDate2 == nil && m.DateOfContact2 != nil && m.TimeMilitary2 != nil { + verrs.Add("firstAvailableDeliveryDate2", "firstAvailableDeliveryDate2, dateOfContact2, and timeMilitary2 must be provided together in body.") + } + if m.DateOfContact2 == nil && m.TimeMilitary2 != nil && m.FirstAvailableDeliveryDate2 != nil { + verrs.Add("DateOfContact2", "dateOfContact2, firstAvailableDeliveryDate2, and timeMilitary2 must be provided together in body.") + } + if m.TimeMilitary2 == nil && m.DateOfContact2 != nil && m.FirstAvailableDeliveryDate2 != nil { + verrs.Add("timeMilitary2", "timeMilitary2, firstAvailableDeliveryDate2, and dateOfContact2 must be provided together in body.") + } + return verrs +} + // validateDestSITForUpdate validates DDDSIT service item has all required fields func validateDestSITForUpdate(m primemessages.UpdateMTOServiceItemSIT) *validate.Errors { verrs := validate.NewErrors() @@ -887,6 +1022,16 @@ func validateReasonDestSIT(m primemessages.MTOServiceItemDestSIT) *validate.Erro return verrs } +// validateReasonInternationalDestSIT validates that International Destination SIT service items have required Reason field +func validateReasonInternationalDestSIT(m primemessages.MTOServiceItemInternationalDestSIT) *validate.Errors { + verrs := validate.NewErrors() + + if m.Reason == nil || m.Reason == models.StringPointer("") { + verrs.Add("reason", "reason is required in body.") + } + return verrs +} + // validateReasonOriginSIT validates that Origin SIT service items have required Reason field func validateReasonOriginSIT(m primemessages.MTOServiceItemOriginSIT) *validate.Errors { verrs := validate.NewErrors() @@ -896,3 +1041,29 @@ func validateReasonOriginSIT(m primemessages.MTOServiceItemOriginSIT) *validate. } return verrs } + +func VLocationModel(vLocation *primemessages.VLocation) *models.VLocation { + if vLocation == nil { + return nil + } + + usPostRegionCitiesID := uuid.FromStringOrNil(vLocation.UsPostRegionCitiesID.String()) + + return &models.VLocation{ + CityName: vLocation.City, + StateName: vLocation.State, + UsprZipID: vLocation.PostalCode, + UsprcCountyNm: *vLocation.County, + UsPostRegionCitiesID: &usPostRegionCitiesID, + } +} + +// validateReasonInternationalOriginSIT validates that International Origin SIT service items have required Reason field +func validateReasonInternationalOriginSIT(m primemessages.MTOServiceItemInternationalOriginSIT) *validate.Errors { + verrs := validate.NewErrors() + + if m.Reason == nil || m.Reason == models.StringPointer("") { + verrs.Add("reason", "reason is required in body.") + } + return verrs +} diff --git a/pkg/handlers/primeapi/payloads/payload_to_model_test.go b/pkg/handlers/primeapi/payloads/payload_to_model_test.go index 9b5ec6f69a5..667bad2439f 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, @@ -397,6 +397,31 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { suite.Equal(originSITDepartureDate, *handlers.FmtDatePtr(returnedModel.SITDepartureDate)) }) + suite.Run("Success - Returns international SIT origin service item model", func() { + originSITServiceItem := &primemessages.MTOServiceItemInternationalOriginSIT{ + ReServiceCode: &originServiceCode, + SitEntryDate: &originSITEntryDate, + SitDepartureDate: &originSITDepartureDate, + SitHHGActualOrigin: &sitHHGActualOriginAddress, + Reason: &originReason, + } + + originSITServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) + originSITServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + returnedModel, verrs := MTOServiceItemModel(originSITServiceItem) + + suite.NoVerrs(verrs) + suite.Equal(moveTaskOrderIDField.String(), returnedModel.MoveTaskOrderID.String()) + suite.Equal(mtoShipmentIDField.String(), returnedModel.MTOShipmentID.String()) + suite.Equal(models.ReServiceCodeDOFSIT, returnedModel.ReService.Code) + suite.Equal(originStreet1, returnedModel.SITOriginHHGActualAddress.StreetAddress1) + suite.Equal(originCity, returnedModel.SITOriginHHGActualAddress.City) + suite.Equal(originState, returnedModel.SITOriginHHGActualAddress.State) + suite.Equal(originPostalCode, returnedModel.SITOriginHHGActualAddress.PostalCode) + suite.Equal(originSITEntryDate, *handlers.FmtDatePtr(returnedModel.SITEntryDate)) + suite.Equal(originSITDepartureDate, *handlers.FmtDatePtr(returnedModel.SITDepartureDate)) + }) + suite.Run("Success - Returns SIT destination service item model", func() { destSITServiceItem := &primemessages.MTOServiceItemDestSIT{ ReServiceCode: &destServiceCode, @@ -423,6 +448,32 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { suite.Equal(destUSPRCID.String(), returnedModel.SITDestinationFinalAddress.UsPostRegionCityID.String()) }) + suite.Run("Success - Returns international SIT destination service item model", func() { + destSITServiceItem := &primemessages.MTOServiceItemInternationalDestSIT{ + ReServiceCode: &destServiceCode, + FirstAvailableDeliveryDate1: &destDate, + FirstAvailableDeliveryDate2: &destDate, + DateOfContact1: &destDate, + DateOfContact2: &destDate, + TimeMilitary1: &destTime, + TimeMilitary2: &destTime, + SitDestinationFinalAddress: &sitFinalDestAddress, + Reason: &destReason, + } + + destSITServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) + destSITServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + returnedModel, verrs := MTOServiceItemModel(destSITServiceItem) + + suite.NoVerrs(verrs) + suite.Equal(moveTaskOrderIDField.String(), returnedModel.MoveTaskOrderID.String()) + suite.Equal(mtoShipmentIDField.String(), returnedModel.MTOShipmentID.String()) + suite.Equal(models.ReServiceCodeDDFSIT, returnedModel.ReService.Code) + suite.Equal(destPostalCode, returnedModel.SITDestinationFinalAddress.PostalCode) + suite.Equal(destStreet, returnedModel.SITDestinationFinalAddress.StreetAddress1) + suite.Equal(destUSPRCID.String(), returnedModel.SITDestinationFinalAddress.UsPostRegionCityID.String()) + }) + suite.Run("Success - Returns SIT destination service item model without customer contact fields", func() { destSITServiceItem := &primemessages.MTOServiceItemDestSIT{ ReServiceCode: &destServiceCode, @@ -443,6 +494,27 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { suite.Equal(destUSPRCID.String(), returnedModel.SITDestinationFinalAddress.UsPostRegionCityID.String()) suite.Equal(destReason, *returnedModel.Reason) }) + + suite.Run("Success - Returns internatonal SIT destination service item model without customer contact fields", func() { + destSITServiceItem := &primemessages.MTOServiceItemInternationalDestSIT{ + ReServiceCode: &destServiceCode, + SitDestinationFinalAddress: &sitFinalDestAddress, + Reason: &destReason, + } + + destSITServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) + destSITServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + returnedModel, verrs := MTOServiceItemModel(destSITServiceItem) + + suite.NoVerrs(verrs) + suite.Equal(moveTaskOrderIDField.String(), returnedModel.MoveTaskOrderID.String()) + suite.Equal(mtoShipmentIDField.String(), returnedModel.MTOShipmentID.String()) + suite.Equal(models.ReServiceCodeDDFSIT, returnedModel.ReService.Code) + suite.Equal(destPostalCode, returnedModel.SITDestinationFinalAddress.PostalCode) + suite.Equal(destStreet, returnedModel.SITDestinationFinalAddress.StreetAddress1) + suite.Equal(destUSPRCID.String(), returnedModel.SITDestinationFinalAddress.UsPostRegionCityID.String()) + suite.Equal(destReason, *returnedModel.Reason) + }) } func (suite *PayloadsSuite) TestReweighModelFromUpdate() { @@ -878,3 +950,28 @@ func (suite *PayloadsSuite) TestMTOShipmentModelFromCreate_WithOptionalFields() suite.NotNil(result.DestinationAddress) suite.Equal("456 Main St", result.DestinationAddress.StreetAddress1) } + +func (suite *PayloadsSuite) TestVLocationModel() { + city := "LOS ANGELES" + state := "CA" + postalCode := "90210" + county := "LOS ANGELES" + usPostRegionCityId := uuid.Must(uuid.NewV4()) + + vLocation := &primemessages.VLocation{ + City: city, + State: state, + PostalCode: postalCode, + County: &county, + UsPostRegionCitiesID: strfmt.UUID(usPostRegionCityId.String()), + } + + payload := VLocationModel(vLocation) + + suite.IsType(payload, &models.VLocation{}) + suite.Equal(usPostRegionCityId.String(), payload.UsPostRegionCitiesID.String(), "Expected UsPostRegionCitiesID to match") + suite.Equal(city, payload.CityName, "Expected City to match") + suite.Equal(state, payload.StateName, "Expected State to match") + suite.Equal(postalCode, payload.UsprZipID, "Expected PostalCode to match") + suite.Equal(county, payload.UsprcCountyNm, "Expected County to match") +} diff --git a/pkg/handlers/primeapiv2/api.go b/pkg/handlers/primeapiv2/api.go index 2d22387e986..c84047af266 100644 --- a/pkg/handlers/primeapiv2/api.go +++ b/pkg/handlers/primeapiv2/api.go @@ -33,6 +33,7 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primev2operations.Mymove queryBuilder := query.NewQueryBuilder() moveRouter := move.NewMoveRouter() waf := entitlements.NewWeightAllotmentFetcher() + vLocation := address.NewVLocation() primeSpec, err := loads.Analyzed(primev2api.SwaggerJSON, "") if err != nil { @@ -53,9 +54,21 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primev2operations.Mymove signedCertificationUpdater := signedcertification.NewSignedCertificationUpdater() ppmEstimator := ppmshipment.NewEstimatePPM(handlerConfig.DTODPlanner(), &paymentrequesthelper.RequestPaymentHelper{}) + mtoServiceItemCreator := mtoserviceitem.NewMTOServiceItemCreator( + handlerConfig.HHGPlanner(), + queryBuilder, + moveRouter, + ghcrateengine.NewDomesticUnpackPricer(), + ghcrateengine.NewDomesticPackPricer(), + ghcrateengine.NewDomesticLinehaulPricer(), + ghcrateengine.NewDomesticShorthaulPricer(), + ghcrateengine.NewDomesticOriginPricer(), + ghcrateengine.NewDomesticDestinationPricer(), + ghcrateengine.NewFuelSurchargePricer()) + moveTaskOrderUpdater := movetaskorder.NewMoveTaskOrderUpdater( queryBuilder, - mtoserviceitem.NewMTOServiceItemCreator(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()), + mtoServiceItemCreator, moveRouter, signedCertificationCreator, signedCertificationUpdater, ppmEstimator, ) @@ -71,6 +84,7 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primev2operations.Mymove handlerConfig, shipmentCreator, movetaskorder.NewMoveTaskOrderChecker(), + vLocation, } paymentRequestRecalculator := paymentrequest.NewPaymentRequestRecalculator( paymentrequest.NewPaymentRequestCreator( @@ -97,10 +111,12 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primev2operations.Mymove ppmShipmentUpdater := ppmshipment.NewPPMShipmentUpdater(ppmEstimator, addressCreator, addressUpdater) boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() - shipmentUpdater := shipment.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipment.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, mtoServiceItemCreator) primeAPIV2.MtoShipmentUpdateMTOShipmentHandler = UpdateMTOShipmentHandler{ handlerConfig, shipmentUpdater, + handlerConfig.DTODPlanner(), + vLocation, } return primeAPIV2 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/mto_shipment.go b/pkg/handlers/primeapiv2/mto_shipment.go index 35672bbc02e..ac7cf7ddab5 100644 --- a/pkg/handlers/primeapiv2/mto_shipment.go +++ b/pkg/handlers/primeapiv2/mto_shipment.go @@ -1,6 +1,7 @@ package primeapiv2 import ( + "context" "fmt" "github.com/go-openapi/runtime/middleware" @@ -17,6 +18,7 @@ import ( "github.com/transcom/mymove/pkg/handlers/primeapi" "github.com/transcom/mymove/pkg/handlers/primeapiv2/payloads" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/route" "github.com/transcom/mymove/pkg/services" mtoshipment "github.com/transcom/mymove/pkg/services/mto_shipment" ) @@ -26,6 +28,7 @@ type CreateMTOShipmentHandler struct { handlers.HandlerConfig services.ShipmentCreator mtoAvailabilityChecker services.MoveTaskOrderChecker + services.VLocation } // Handle creates the mto shipment @@ -83,6 +86,37 @@ func (h CreateMTOShipmentHandler) Handle(params mtoshipmentops.CreateMTOShipment isUBFeatureOn = flag.Match } + /** Feature Flag - Alaska - Determines if AK can be included/excluded **/ + isAlaskaEnabled := false + akFeatureFlagName := "enable_alaska" + flag, err = h.FeatureFlagFetcher().GetBooleanFlagForUser(context.TODO(), appCtx, akFeatureFlagName, map[string]string{}) + + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", akFeatureFlagName), zap.Error(err)) + } else { + isAlaskaEnabled = flag.Match + } + + /** Feature Flag - Hawaii - Determines if HI can be included/excluded **/ + isHawaiiEnabled := false + hiFeatureFlagName := "enable_hawaii" + flag, err = h.FeatureFlagFetcher().GetBooleanFlagForUser(context.TODO(), appCtx, hiFeatureFlagName, map[string]string{}) + + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", hiFeatureFlagName), zap.Error(err)) + } else { + isHawaiiEnabled = flag.Match + } + + // build states to exlude filter list + statesToExclude := make([]string, 0) + if !isAlaskaEnabled { + statesToExclude = append(statesToExclude, "AK") + } + if !isHawaiiEnabled { + statesToExclude = append(statesToExclude, "HI") + } + // Return an error if UB shipment is sent while the feature flag is turned off. if !isUBFeatureOn && (*params.Body.ShipmentType == primev2messages.MTOShipmentTypeUNACCOMPANIEDBAGGAGE) { return mtoshipmentops.NewCreateMTOShipmentUnprocessableEntity().WithPayload(payloads.ValidationError( @@ -128,6 +162,45 @@ func (h CreateMTOShipmentHandler) Handle(params mtoshipmentops.CreateMTOShipment mtoAvailableToPrime, err := h.mtoAvailabilityChecker.MTOAvailableToPrime(appCtx, moveTaskOrderID) if mtoAvailableToPrime { + // check each address prior to creating the shipment to ensure only valid addresses are being used to create the shipment + var addresses []models.Address + + if mtoShipment.ShipmentType != models.MTOShipmentTypePPM { + if mtoShipment.PickupAddress != nil { + addresses = append(addresses, *mtoShipment.PickupAddress) + } + + if mtoShipment.DestinationAddress != nil { + addresses = append(addresses, *mtoShipment.DestinationAddress) + } + } else { + if mtoShipment.PPMShipment.PickupAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.PickupAddress) + } + + if mtoShipment.PPMShipment.DestinationAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.DestinationAddress) + } + } + + for _, address := range addresses { + addressSearch := address.City + ", " + address.State + " " + address.PostalCode + err := checkValidAddress(h.VLocation, appCtx, statesToExclude, addressSearch) + + if err != nil { + appCtx.Logger().Error("primeapi.UpdateMTOShipmentHandler error", zap.Error(err)) + switch e := err.(type) { + case apperror.UnprocessableEntityError: + payload := payloads.ValidationError(err.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), nil) + return mtoshipmentops.NewCreateMTOShipmentUnprocessableEntity().WithPayload(payload), err + default: + errStr := e.Error() // we do this because InternalServerError wants a *string + payload := payloads.InternalServerError(&errStr, h.GetTraceIDFromRequest(params.HTTPRequest)) + return mtoshipmentops.NewCreateMTOShipmentInternalServerError().WithPayload(payload), e + } + } + } + mtoShipment, err = h.ShipmentCreator.CreateShipment(appCtx, mtoShipment) } else if err == nil { appCtx.Logger().Error("primeapiv2.CreateMTOShipmentHandler error - MTO is not available to Prime") @@ -160,6 +233,7 @@ func (h CreateMTOShipmentHandler) Handle(params mtoshipmentops.CreateMTOShipment payloads.InternalServerError(nil, h.GetTraceIDFromRequest(params.HTTPRequest))), err } } + returnPayload := payloads.MTOShipment(mtoShipment) return mtoshipmentops.NewCreateMTOShipmentOK().WithPayload(returnPayload), nil }) @@ -169,6 +243,8 @@ func (h CreateMTOShipmentHandler) Handle(params mtoshipmentops.CreateMTOShipment type UpdateMTOShipmentHandler struct { handlers.HandlerConfig services.ShipmentUpdater + planner route.Planner + services.VLocation } // Handle handler that updates a mto shipment @@ -200,8 +276,92 @@ func (h UpdateMTOShipmentHandler) Handle(params mtoshipmentops.UpdateMTOShipment // Validate further prime restrictions on model mtoShipment.ShipmentType = dbShipment.ShipmentType - appCtx.Logger().Info("primeapi.UpdateMTOShipmentHandler info", zap.String("pointOfContact", params.Body.PointOfContact)) + + /** Feature Flag - Alaska - Determines if AK can be included/excluded **/ + isAlaskaEnabled := false + akFeatureFlagName := "enable_alaska" + flag, err := h.FeatureFlagFetcher().GetBooleanFlagForUser(context.TODO(), appCtx, akFeatureFlagName, map[string]string{}) + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", akFeatureFlagName), zap.Error(err)) + } else { + isAlaskaEnabled = flag.Match + } + + /** Feature Flag - Hawaii - Determines if HI can be included/excluded **/ + isHawaiiEnabled := false + hiFeatureFlagName := "enable_hawaii" + flag, err = h.FeatureFlagFetcher().GetBooleanFlagForUser(context.TODO(), appCtx, hiFeatureFlagName, map[string]string{}) + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", hiFeatureFlagName), zap.Error(err)) + } else { + isHawaiiEnabled = flag.Match + } + + // build states to exlude filter list + statesToExclude := make([]string, 0) + if !isAlaskaEnabled { + statesToExclude = append(statesToExclude, "AK") + } + if !isHawaiiEnabled { + statesToExclude = append(statesToExclude, "HI") + } + + // check each address prior to updating the shipment to ensure only valid addresses are being used + var addresses []models.Address + + if mtoShipment.ShipmentType != models.MTOShipmentTypePPM { + if mtoShipment.PickupAddress != nil { + addresses = append(addresses, *mtoShipment.PickupAddress) + } + + if mtoShipment.SecondaryPickupAddress != nil { + addresses = append(addresses, *mtoShipment.SecondaryPickupAddress) + } + + if mtoShipment.DestinationAddress != nil { + addresses = append(addresses, *mtoShipment.DestinationAddress) + } + + if mtoShipment.SecondaryDeliveryAddress != nil { + addresses = append(addresses, *mtoShipment.SecondaryDeliveryAddress) + } + } else { + if mtoShipment.PPMShipment.PickupAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.PickupAddress) + } + + if mtoShipment.PPMShipment.SecondaryPickupAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.SecondaryPickupAddress) + } + + if mtoShipment.PPMShipment.DestinationAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.DestinationAddress) + } + + if mtoShipment.PPMShipment.SecondaryDestinationAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.SecondaryDestinationAddress) + } + } + + for _, address := range addresses { + addressSearch := address.City + ", " + address.State + " " + address.PostalCode + err := checkValidAddress(h.VLocation, appCtx, statesToExclude, addressSearch) + + if err != nil { + appCtx.Logger().Error("primeapi.UpdateMTOShipmentHandler error", zap.Error(err)) + switch e := err.(type) { + case apperror.UnprocessableEntityError: + payload := payloads.ValidationError(err.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), nil) + return mtoshipmentops.NewUpdateMTOShipmentUnprocessableEntity().WithPayload(payload), e + default: + errStr := e.Error() // we do this because InternalServerError wants a *string + payload := payloads.InternalServerError(&errStr, h.GetTraceIDFromRequest(params.HTTPRequest)) + return mtoshipmentops.NewUpdateMTOShipmentInternalServerError().WithPayload(payload), e + } + } + } + mtoShipment, err = h.ShipmentUpdater.UpdateShipment(appCtx, mtoShipment, params.IfMatch, "prime-v2") if err != nil { appCtx.Logger().Error("primeapi.UpdateMTOShipmentHandler error", zap.Error(err)) @@ -227,3 +387,18 @@ func (h UpdateMTOShipmentHandler) Handle(params mtoshipmentops.UpdateMTOShipment return mtoshipmentops.NewUpdateMTOShipmentOK().WithPayload(mtoShipmentPayload), nil }) } + +func checkValidAddress(vLocation services.VLocation, appCtx appcontext.AppContext, statesToExclude []string, addressSearch string) error { + locationList, err := vLocation.GetLocationsByZipCityState(appCtx, addressSearch, statesToExclude, true) + + if err != nil { + serverError := apperror.NewInternalServerError("Error searching for address") + return serverError + } else if len(*locationList) == 0 { + unprocessableErr := apperror.NewUnprocessableEntityError( + fmt.Sprintf("primeapi.UpdateShipmentDestinationAddress: could not find the provided location: %s", addressSearch)) + return unprocessableErr + } + + return nil +} diff --git a/pkg/handlers/primeapiv2/mto_shipment_test.go b/pkg/handlers/primeapiv2/mto_shipment_test.go index 83134e3b522..73f799f0270 100644 --- a/pkg/handlers/primeapiv2/mto_shipment_test.go +++ b/pkg/handlers/primeapiv2/mto_shipment_test.go @@ -54,6 +54,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { mock.Anything, false, ).Return(400, nil) + vLocationServices := address.NewVLocation() setUpSignedCertificationCreatorMock := func(returnValue ...interface{}) services.SignedCertificationCreator { mockCreator := &mocks.SignedCertificationCreator{} @@ -142,6 +143,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { handlerConfig, shipmentCreator, mtoChecker, + vLocationServices, } // Make stubbed addresses just to collect address data for payload @@ -442,6 +444,47 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { suite.Equal(handlers.InternalServerErrMessage, *errResponse.Payload.Title, "Payload title is wrong") }) + suite.Run("POST failure - 500 GetLocationsByZipCityState", func() { + // Under Test: CreateMTOShipment handler code + // Setup: Create an mto shipment on an available move + // Expected: Failure GetLocationsByZipCityState returns internal server error + handler, move := setupTestData(false) + req := httptest.NewRequest("POST", "/mto-shipments", nil) + + params := mtoshipmentops.CreateMTOShipmentParams{ + HTTPRequest: req, + Body: &primev2messages.CreateMTOShipment{ + MoveTaskOrderID: handlers.FmtUUID(move.ID), + Agents: nil, + CustomerRemarks: nil, + PointOfContact: "John Doe", + PrimeEstimatedWeight: handlers.FmtInt64(1200), + RequestedPickupDate: handlers.FmtDatePtr(models.TimePointer(time.Now())), + ShipmentType: primev2messages.NewMTOShipmentType(primev2messages.MTOShipmentTypeHHG), + PickupAddress: struct{ primev2messages.Address }{pickupAddress}, + DestinationAddress: struct{ primev2messages.Address }{destinationAddress}, + }, + } + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + expectedError := models.ErrFetchNotFound + vLocationFetcher := &mocks.VLocation{} + vLocationFetcher.On("GetLocationsByZipCityState", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + ).Return(nil, expectedError).Once() + + handler.VLocation = vLocationFetcher + + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.CreateMTOShipmentInternalServerError{}, response) + }) + suite.Run("POST failure - 422 -- Bad agent IDs set on shipment", func() { // Under Test: CreateMTOShipmentHandler // Setup: Create a shipment with an agent that doesn't really exist, handler should return unprocessable entity diff --git a/pkg/handlers/primeapiv2/payloads/model_to_payload.go b/pkg/handlers/primeapiv2/payloads/model_to_payload.go index 106a3bd5cf3..7b39c88c803 100644 --- a/pkg/handlers/primeapiv2/payloads/model_to_payload.go +++ b/pkg/handlers/primeapiv2/payloads/model_to_payload.go @@ -617,6 +617,20 @@ func MTOServiceItem(mtoServiceItem *models.MTOServiceItem) primev2messages.MTOSe SitHHGActualOrigin: Address(mtoServiceItem.SITOriginHHGActualAddress), SitHHGOriginalOrigin: Address(mtoServiceItem.SITOriginHHGOriginalAddress), } + case models.ReServiceCodeIOFSIT, models.ReServiceCodeIOASIT, models.ReServiceCodeIOPSIT, models.ReServiceCodeIOSFSC: + var sitDepartureDate time.Time + if mtoServiceItem.SITDepartureDate != nil { + sitDepartureDate = *mtoServiceItem.SITDepartureDate + } + payload = &primev2messages.MTOServiceItemInternationalOriginSIT{ + ReServiceCode: handlers.FmtString(string(mtoServiceItem.ReService.Code)), + Reason: mtoServiceItem.Reason, + SitDepartureDate: handlers.FmtDate(sitDepartureDate), + SitEntryDate: handlers.FmtDatePtr(mtoServiceItem.SITEntryDate), + SitPostalCode: mtoServiceItem.SITPostalCode, + SitHHGActualOrigin: Address(mtoServiceItem.SITOriginHHGActualAddress), + SitHHGOriginalOrigin: Address(mtoServiceItem.SITOriginHHGOriginalAddress), + } case models.ReServiceCodeDDFSIT, models.ReServiceCodeDDASIT, models.ReServiceCodeDDDSIT, models.ReServiceCodeDDSFSC: var sitDepartureDate, firstAvailableDeliveryDate1, firstAvailableDeliveryDate2, dateOfContact1, dateOfContact2 time.Time var timeMilitary1, timeMilitary2 *string @@ -661,6 +675,50 @@ func MTOServiceItem(mtoServiceItem *models.MTOServiceItem) primev2messages.MTOSe SitCustomerContacted: handlers.FmtDatePtr(mtoServiceItem.SITCustomerContacted), SitRequestedDelivery: handlers.FmtDatePtr(mtoServiceItem.SITRequestedDelivery), } + case models.ReServiceCodeIDFSIT, models.ReServiceCodeIDASIT, models.ReServiceCodeIDDSIT, models.ReServiceCodeIDSFSC: + var sitDepartureDate, firstAvailableDeliveryDate1, firstAvailableDeliveryDate2, dateOfContact1, dateOfContact2 time.Time + var timeMilitary1, timeMilitary2 *string + + if mtoServiceItem.SITDepartureDate != nil { + sitDepartureDate = *mtoServiceItem.SITDepartureDate + } + + firstContact := GetCustomerContact(mtoServiceItem.CustomerContacts, models.CustomerContactTypeFirst) + secondContact := GetCustomerContact(mtoServiceItem.CustomerContacts, models.CustomerContactTypeSecond) + timeMilitary1 = &firstContact.TimeMilitary + timeMilitary2 = &secondContact.TimeMilitary + + if !firstContact.DateOfContact.IsZero() { + dateOfContact1 = firstContact.DateOfContact + } + + if !secondContact.DateOfContact.IsZero() { + dateOfContact2 = secondContact.DateOfContact + } + + if !firstContact.FirstAvailableDeliveryDate.IsZero() { + firstAvailableDeliveryDate1 = firstContact.FirstAvailableDeliveryDate + } + + if !secondContact.FirstAvailableDeliveryDate.IsZero() { + firstAvailableDeliveryDate2 = secondContact.FirstAvailableDeliveryDate + } + + payload = &primev2messages.MTOServiceItemInternationalDestSIT{ + ReServiceCode: handlers.FmtString(string(mtoServiceItem.ReService.Code)), + Reason: mtoServiceItem.Reason, + DateOfContact1: handlers.FmtDate(dateOfContact1), + TimeMilitary1: handlers.FmtStringPtrNonEmpty(timeMilitary1), + FirstAvailableDeliveryDate1: handlers.FmtDate(firstAvailableDeliveryDate1), + DateOfContact2: handlers.FmtDate(dateOfContact2), + TimeMilitary2: handlers.FmtStringPtrNonEmpty(timeMilitary2), + FirstAvailableDeliveryDate2: handlers.FmtDate(firstAvailableDeliveryDate2), + SitDepartureDate: handlers.FmtDate(sitDepartureDate), + SitEntryDate: handlers.FmtDatePtr(mtoServiceItem.SITEntryDate), + SitDestinationFinalAddress: Address(mtoServiceItem.SITDestinationFinalAddress), + SitCustomerContacted: handlers.FmtDatePtr(mtoServiceItem.SITCustomerContacted), + SitRequestedDelivery: handlers.FmtDatePtr(mtoServiceItem.SITRequestedDelivery), + } case models.ReServiceCodeDCRT, models.ReServiceCodeDUCRT: item := GetDimension(mtoServiceItem.Dimensions, models.DimensionTypeItem) @@ -725,7 +783,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 7b68e2a8e69..ec53f8a3457 100644 --- a/pkg/handlers/primeapiv2/payloads/model_to_payload_test.go +++ b/pkg/handlers/primeapiv2/payloads/model_to_payload_test.go @@ -640,6 +640,50 @@ func (suite *PayloadsSuite) TestMTOServiceItem() { suite.Equal(mtoServiceItemDefault.MoveTaskOrderID.String(), basicItem.MoveTaskOrderID().String()) } +func (suite *PayloadsSuite) TestMTOServiceInternationalItem() { + sitPostalCode := "55555" + mtoServiceItemIOFSIT := &models.MTOServiceItem{ + ID: uuid.Must(uuid.NewV4()), + ReService: models.ReService{Code: models.ReServiceCodeIOFSIT}, + SITDepartureDate: nil, + SITEntryDate: nil, + SITPostalCode: &sitPostalCode, + SITOriginHHGActualAddress: &models.Address{ + StreetAddress1: "dummyStreet", + City: "dummyCity", + State: "FL", + PostalCode: "55555", + }, + SITOriginHHGOriginalAddress: &models.Address{ + StreetAddress1: "dummyStreet2", + City: "dummyCity2", + State: "FL", + PostalCode: "55555", + }, + } + + resultIOFSIT := MTOServiceItem(mtoServiceItemIOFSIT) + suite.NotNil(resultIOFSIT) + sitOrigin, ok := resultIOFSIT.(*primev2messages.MTOServiceItemInternationalOriginSIT) + suite.True(ok) + suite.Equal("55555", *sitOrigin.SitPostalCode) + suite.Equal("dummyStreet", *sitOrigin.SitHHGActualOrigin.StreetAddress1) + suite.Equal("dummyStreet2", *sitOrigin.SitHHGOriginalOrigin.StreetAddress1) + + mtoServiceItemDefault := &models.MTOServiceItem{ + ID: uuid.Must(uuid.NewV4()), + ReService: models.ReService{Code: "SOME_OTHER_SERVICE_CODE"}, + MoveTaskOrderID: uuid.Must(uuid.NewV4()), + } + + resultDefault := MTOServiceItem(mtoServiceItemDefault) + suite.NotNil(resultDefault) + basicItem, ok := resultDefault.(*primev2messages.MTOServiceItemBasic) + suite.True(ok) + suite.Equal("SOME_OTHER_SERVICE_CODE", string(*basicItem.ReServiceCode)) + suite.Equal(mtoServiceItemDefault.MoveTaskOrderID.String(), basicItem.MoveTaskOrderID().String()) +} + func (suite *PayloadsSuite) TestGetCustomerContact() { customerContacts := models.MTOServiceItemCustomerContacts{ models.MTOServiceItemCustomerContact{Type: models.CustomerContactTypeFirst}, @@ -725,6 +769,72 @@ func (suite *PayloadsSuite) TestMTOServiceItemDestSIT() { suite.Equal(finalAddress.PostalCode, *destSIT.SitDestinationFinalAddress.PostalCode) suite.Equal(mtoShipmentID.String(), destSIT.MtoShipmentID().String()) } + +func (suite *PayloadsSuite) TestMTOServiceItemInternationalDestSIT() { + reServiceCode := models.ReServiceCodeIDFSIT + reason := "reason" + dateOfContact1 := time.Now() + timeMilitary1 := "1500Z" + firstAvailableDeliveryDate1 := dateOfContact1.AddDate(0, 0, 10) + dateOfContact2 := time.Now().AddDate(0, 0, 5) + timeMilitary2 := "1300Z" + firstAvailableDeliveryDate2 := dateOfContact2.AddDate(0, 0, 10) + sitDepartureDate := time.Now().AddDate(0, 1, 0) + sitEntryDate := time.Now().AddDate(0, 0, -30) + finalAddress := models.Address{ + StreetAddress1: "dummyStreet", + City: "dummyCity", + State: "FL", + PostalCode: "55555", + } + mtoShipmentID := uuid.Must(uuid.NewV4()) + + mtoServiceItemDestSIT := &models.MTOServiceItem{ + ID: uuid.Must(uuid.NewV4()), + ReService: models.ReService{Code: reServiceCode}, + Reason: &reason, + SITDepartureDate: &sitDepartureDate, + SITEntryDate: &sitEntryDate, + SITDestinationFinalAddress: &finalAddress, + MTOShipmentID: &mtoShipmentID, + CustomerContacts: models.MTOServiceItemCustomerContacts{ + models.MTOServiceItemCustomerContact{ + DateOfContact: dateOfContact1, + TimeMilitary: timeMilitary1, + FirstAvailableDeliveryDate: firstAvailableDeliveryDate1, + Type: models.CustomerContactTypeFirst, + }, + models.MTOServiceItemCustomerContact{ + DateOfContact: dateOfContact2, + TimeMilitary: timeMilitary2, + FirstAvailableDeliveryDate: firstAvailableDeliveryDate2, + Type: models.CustomerContactTypeSecond, + }, + }, + } + + resultDestSIT := MTOServiceItem(mtoServiceItemDestSIT) + suite.NotNil(resultDestSIT) + destSIT, ok := resultDestSIT.(*primev2messages.MTOServiceItemInternationalDestSIT) + suite.True(ok) + + suite.Equal(string(reServiceCode), string(*destSIT.ReServiceCode)) + suite.Equal(reason, *destSIT.Reason) + suite.Equal(strfmt.Date(sitDepartureDate).String(), destSIT.SitDepartureDate.String()) + suite.Equal(strfmt.Date(sitEntryDate).String(), destSIT.SitEntryDate.String()) + suite.Equal(strfmt.Date(dateOfContact1).String(), destSIT.DateOfContact1.String()) + suite.Equal(timeMilitary1, *destSIT.TimeMilitary1) + suite.Equal(strfmt.Date(firstAvailableDeliveryDate1).String(), destSIT.FirstAvailableDeliveryDate1.String()) + suite.Equal(strfmt.Date(dateOfContact2).String(), destSIT.DateOfContact2.String()) + suite.Equal(timeMilitary2, *destSIT.TimeMilitary2) + suite.Equal(strfmt.Date(firstAvailableDeliveryDate2).String(), destSIT.FirstAvailableDeliveryDate2.String()) + suite.Equal(finalAddress.StreetAddress1, *destSIT.SitDestinationFinalAddress.StreetAddress1) + suite.Equal(finalAddress.City, *destSIT.SitDestinationFinalAddress.City) + suite.Equal(finalAddress.State, *destSIT.SitDestinationFinalAddress.State) + suite.Equal(finalAddress.PostalCode, *destSIT.SitDestinationFinalAddress.PostalCode) + suite.Equal(mtoShipmentID.String(), destSIT.MtoShipmentID().String()) +} + func (suite *PayloadsSuite) TestMTOServiceItemDCRT() { reServiceCode := models.ReServiceCodeDCRT reason := "reason" @@ -870,7 +980,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..33c1d26c453 100644 --- a/pkg/handlers/primeapiv2/payloads/payload_to_model.go +++ b/pkg/handlers/primeapiv2/payloads/payload_to_model.go @@ -276,7 +276,7 @@ func PPMShipmentModelFromCreate(ppmShipment *primev2messages.CreatePPMShipment) StreetAddress1: "Deprecated Endpoint Prime V1", StreetAddress2: models.StringPointer("Endpoint no longer supported"), StreetAddress3: models.StringPointer("Update address field to appropriate values"), - City: "DEPV1", + City: "Beverly Hills", State: "CA", PostalCode: "90210", } @@ -562,7 +562,44 @@ func MTOServiceItemModel(mtoServiceItem primev2messages.MTOServiceItem) (*models if model.SITOriginHHGActualAddress != nil { model.SITOriginHHGActualAddressID = &model.SITOriginHHGActualAddress.ID } + case primev2messages.MTOServiceItemModelTypeMTOServiceItemInternationalOriginSIT: + originsit := mtoServiceItem.(*primev2messages.MTOServiceItemInternationalOriginSIT) + + if originsit.ReServiceCode != nil { + model.ReService.Code = models.ReServiceCode(*originsit.ReServiceCode) + } + + model.Reason = originsit.Reason + // Check for reason required field on a IOASIT + if model.ReService.Code == models.ReServiceCodeIOASIT { + reasonVerrs := validateReasonInternationalOriginSIT(*originsit) + + if reasonVerrs.HasAny() { + return nil, reasonVerrs + } + } + + if model.ReService.Code == models.ReServiceCodeIOFSIT { + reasonVerrs := validateReasonInternationalOriginSIT(*originsit) + + if reasonVerrs.HasAny() { + return nil, reasonVerrs + } + } + + sitEntryDate := handlers.FmtDatePtrToPopPtr(originsit.SitEntryDate) + + if sitEntryDate != nil { + model.SITEntryDate = sitEntryDate + } + + model.SITPostalCode = originsit.SitPostalCode + + model.SITOriginHHGActualAddress = AddressModel(originsit.SitHHGActualOrigin) + if model.SITOriginHHGActualAddress != nil { + model.SITOriginHHGActualAddressID = &model.SITOriginHHGActualAddress.ID + } case primev2messages.MTOServiceItemModelTypeMTOServiceItemDestSIT: destsit := mtoServiceItem.(*primev2messages.MTOServiceItemDestSIT) @@ -621,7 +658,64 @@ func MTOServiceItemModel(mtoServiceItem primev2messages.MTOServiceItem) (*models if model.SITDestinationFinalAddress != nil { model.SITDestinationFinalAddressID = &model.SITDestinationFinalAddress.ID } + case primev2messages.MTOServiceItemModelTypeMTOServiceItemInternationalDestSIT: + destsit := mtoServiceItem.(*primev2messages.MTOServiceItemInternationalDestSIT) + if destsit.ReServiceCode != nil { + model.ReService.Code = models.ReServiceCode(*destsit.ReServiceCode) + + } + + model.Reason = destsit.Reason + sitEntryDate := handlers.FmtDatePtrToPopPtr(destsit.SitEntryDate) + + // Check for required fields on a IDFSIT + if model.ReService.Code == models.ReServiceCodeIDFSIT { + verrs := validateIDFSITForCreate(*destsit) + reasonVerrs := validateReasonInternationalDestSIT(*destsit) + + if verrs.HasAny() { + return nil, verrs + } + + if reasonVerrs.HasAny() { + return nil, reasonVerrs + } + } + + var customerContacts models.MTOServiceItemCustomerContacts + + if destsit.TimeMilitary1 != nil && destsit.FirstAvailableDeliveryDate1 != nil && destsit.DateOfContact1 != nil { + customerContacts = append(customerContacts, models.MTOServiceItemCustomerContact{ + Type: models.CustomerContactTypeFirst, + DateOfContact: time.Time(*destsit.DateOfContact1), + TimeMilitary: *destsit.TimeMilitary1, + FirstAvailableDeliveryDate: time.Time(*destsit.FirstAvailableDeliveryDate1), + }) + } + if destsit.TimeMilitary2 != nil && destsit.FirstAvailableDeliveryDate2 != nil && destsit.DateOfContact2 != nil { + customerContacts = append(customerContacts, models.MTOServiceItemCustomerContact{ + Type: models.CustomerContactTypeSecond, + DateOfContact: time.Time(*destsit.DateOfContact2), + TimeMilitary: *destsit.TimeMilitary2, + FirstAvailableDeliveryDate: time.Time(*destsit.FirstAvailableDeliveryDate2), + }) + } + + model.CustomerContacts = customerContacts + + if sitEntryDate != nil { + model.SITEntryDate = sitEntryDate + } + + if destsit.SitDepartureDate != nil { + model.SITDepartureDate = handlers.FmtDatePtrToPopPtr(destsit.SitDepartureDate) + } + + model.SITDestinationFinalAddress = AddressModel(destsit.SitDestinationFinalAddress) + if model.SITDestinationFinalAddress != nil { + model.SITDestinationFinalAddressID = &model.SITDestinationFinalAddress.ID + } case primev2messages.MTOServiceItemModelTypeMTOServiceItemShuttle: shuttleService := mtoServiceItem.(*primev2messages.MTOServiceItemShuttle) // values to get from payload @@ -630,6 +724,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 @@ -925,6 +1027,31 @@ func validateDDFSITForCreate(m primev2messages.MTOServiceItemDestSIT) *validate. return verrs } +// validateIDFSITForCreate validates IDFSIT service item has all required fields +func validateIDFSITForCreate(m primev2messages.MTOServiceItemInternationalDestSIT) *validate.Errors { + verrs := validate.NewErrors() + + if m.FirstAvailableDeliveryDate1 == nil && m.DateOfContact1 != nil && m.TimeMilitary1 != nil { + verrs.Add("firstAvailableDeliveryDate1", "firstAvailableDeliveryDate1, dateOfContact1, and timeMilitary1 must be provided together in body.") + } + if m.DateOfContact1 == nil && m.TimeMilitary1 != nil && m.FirstAvailableDeliveryDate1 != nil { + verrs.Add("DateOfContact1", "dateOfContact1, timeMilitary1, and firstAvailableDeliveryDate1 must be provided together in body.") + } + if m.TimeMilitary1 == nil && m.DateOfContact1 != nil && m.FirstAvailableDeliveryDate1 != nil { + verrs.Add("timeMilitary1", "timeMilitary1, dateOfContact1, and firstAvailableDeliveryDate1 must be provided together in body.") + } + if m.FirstAvailableDeliveryDate2 == nil && m.DateOfContact2 != nil && m.TimeMilitary2 != nil { + verrs.Add("firstAvailableDeliveryDate2", "firstAvailableDeliveryDate2, dateOfContact2, and timeMilitary2 must be provided together in body.") + } + if m.DateOfContact2 == nil && m.TimeMilitary2 != nil && m.FirstAvailableDeliveryDate2 != nil { + verrs.Add("DateOfContact2", "dateOfContact2, firstAvailableDeliveryDate2, and timeMilitary2 must be provided together in body.") + } + if m.TimeMilitary2 == nil && m.DateOfContact2 != nil && m.FirstAvailableDeliveryDate2 != nil { + verrs.Add("timeMilitary2", "timeMilitary2, firstAvailableDeliveryDate2, and dateOfContact2 must be provided together in body.") + } + return verrs +} + // validateDestSITForUpdate validates DDDSIT service item has all required fields func validateDestSITForUpdate(m primev2messages.UpdateMTOServiceItemSIT) *validate.Errors { verrs := validate.NewErrors() @@ -960,6 +1087,16 @@ func validateReasonDestSIT(m primev2messages.MTOServiceItemDestSIT) *validate.Er return verrs } +// validateReasonInternationalDestSIT validates that International Destination SIT service items have required Reason field +func validateReasonInternationalDestSIT(m primev2messages.MTOServiceItemInternationalDestSIT) *validate.Errors { + verrs := validate.NewErrors() + + if m.Reason == nil || m.Reason == models.StringPointer("") { + verrs.Add("reason", "reason is required in body.") + } + return verrs +} + // validateReasonOriginSIT validates that Origin SIT service items have required Reason field func validateReasonOriginSIT(m primev2messages.MTOServiceItemOriginSIT) *validate.Errors { verrs := validate.NewErrors() @@ -970,6 +1107,16 @@ func validateReasonOriginSIT(m primev2messages.MTOServiceItemOriginSIT) *validat return verrs } +// validateReasonInternationalOriginSIT validates that International Origin SIT service items have required Reason field +func validateReasonInternationalOriginSIT(m primev2messages.MTOServiceItemInternationalOriginSIT) *validate.Errors { + verrs := validate.NewErrors() + + if m.Reason == nil || m.Reason == models.StringPointer("") { + verrs.Add("reason", "reason is required in body.") + } + return verrs +} + // validateBoatShipmentType validates that the shipment type is a valid boat type, and is not nil. func validateBoatShipmentType(s primev2messages.MTOShipmentType) *validate.Errors { verrs := validate.NewErrors() diff --git a/pkg/handlers/primeapiv2/payloads/payload_to_model_test.go b/pkg/handlers/primeapiv2/payloads/payload_to_model_test.go index 3df180b58ea..2f32f2f611a 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, @@ -378,6 +378,32 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { suite.Equal(destUSPRCID.String(), returnedModel.SITDestinationFinalAddress.UsPostRegionCityID.String()) }) + suite.Run("Success - Returns SIT destination service item model - international", func() { + destSITServiceItem := &primev2messages.MTOServiceItemInternationalDestSIT{ + ReServiceCode: &destServiceCode, + FirstAvailableDeliveryDate1: &destDate, + FirstAvailableDeliveryDate2: &destDate, + DateOfContact1: &destDate, + DateOfContact2: &destDate, + TimeMilitary1: &destTime, + TimeMilitary2: &destTime, + SitDestinationFinalAddress: &sitFinalDestAddress, + Reason: &destReason, + } + + destSITServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) + destSITServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + returnedModel, verrs := MTOServiceItemModel(destSITServiceItem) + + suite.NoVerrs(verrs) + suite.Equal(moveTaskOrderIDField.String(), returnedModel.MoveTaskOrderID.String()) + suite.Equal(mtoShipmentIDField.String(), returnedModel.MTOShipmentID.String()) + suite.Equal(models.ReServiceCodeDDFSIT, returnedModel.ReService.Code) + suite.Equal(destPostalCode, returnedModel.SITDestinationFinalAddress.PostalCode) + suite.Equal(destStreet, returnedModel.SITDestinationFinalAddress.StreetAddress1) + suite.Equal(destUSPRCID.String(), returnedModel.SITDestinationFinalAddress.UsPostRegionCityID.String()) + }) + suite.Run("Success - Returns SIT destination service item model without customer contact fields", func() { destSITServiceItem := &primev2messages.MTOServiceItemDestSIT{ ReServiceCode: &destServiceCode, @@ -398,6 +424,27 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { suite.Equal(destUSPRCID.String(), returnedModel.SITDestinationFinalAddress.UsPostRegionCityID.String()) suite.Equal(destReason, *returnedModel.Reason) }) + + suite.Run("Success - Returns SIT destination service item model without customer contact fields - international", func() { + destSITServiceItem := &primev2messages.MTOServiceItemInternationalDestSIT{ + ReServiceCode: &destServiceCode, + SitDestinationFinalAddress: &sitFinalDestAddress, + Reason: &destReason, + } + + destSITServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) + destSITServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + returnedModel, verrs := MTOServiceItemModel(destSITServiceItem) + + suite.NoVerrs(verrs) + suite.Equal(moveTaskOrderIDField.String(), returnedModel.MoveTaskOrderID.String()) + suite.Equal(mtoShipmentIDField.String(), returnedModel.MTOShipmentID.String()) + suite.Equal(models.ReServiceCodeDDFSIT, returnedModel.ReService.Code) + suite.Equal(destPostalCode, returnedModel.SITDestinationFinalAddress.PostalCode) + suite.Equal(destStreet, returnedModel.SITDestinationFinalAddress.StreetAddress1) + suite.Equal(destUSPRCID.String(), returnedModel.SITDestinationFinalAddress.UsPostRegionCityID.String()) + suite.Equal(destReason, *returnedModel.Reason) + }) } func (suite *PayloadsSuite) TestReweighModelFromUpdate() { @@ -641,6 +688,23 @@ func (suite *PayloadsSuite) TestValidateReasonOriginSIT() { verrs := validateReasonOriginSIT(mtoServiceItemOriginSIT) suite.True(verrs.HasAny()) }) + + suite.Run("Reason provided - international", func() { + reason := "reason" + mtoServiceItemOriginSIT := primev2messages.MTOServiceItemInternationalOriginSIT{ + Reason: &reason, + } + + verrs := validateReasonInternationalOriginSIT(mtoServiceItemOriginSIT) + suite.False(verrs.HasAny()) + }) + + suite.Run("No reason provided - international", func() { + mtoServiceItemOriginSIT := primev2messages.MTOServiceItemInternationalOriginSIT{} + + verrs := validateReasonInternationalOriginSIT(mtoServiceItemOriginSIT) + suite.True(verrs.HasAny()) + }) } func (suite *PayloadsSuite) TestShipmentAddressUpdateModel() { diff --git a/pkg/handlers/primeapiv3/api.go b/pkg/handlers/primeapiv3/api.go index 1662d151464..a8bdf1c6e0a 100644 --- a/pkg/handlers/primeapiv3/api.go +++ b/pkg/handlers/primeapiv3/api.go @@ -32,6 +32,7 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primev3operations.Mymove fetcher := fetch.NewFetcher(builder) queryBuilder := query.NewQueryBuilder() moveRouter := move.NewMoveRouter() + vLocation := address.NewVLocation() waf := entitlements.NewWeightAllotmentFetcher() primeSpec, err := loads.Analyzed(primev3api.SwaggerJSON, "") @@ -73,6 +74,7 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primev3operations.Mymove handlerConfig, shipmentCreator, movetaskorder.NewMoveTaskOrderChecker(), + vLocation, } paymentRequestRecalculator := paymentrequest.NewPaymentRequestRecalculator( paymentrequest.NewPaymentRequestCreator( @@ -95,13 +97,27 @@ func NewPrimeAPI(handlerConfig handlers.HandlerConfig) *primev3operations.Mymove addressCreator, ) + mtoServiceItemCreator := mtoserviceitem.NewMTOServiceItemCreator( + handlerConfig.HHGPlanner(), + queryBuilder, + moveRouter, + ghcrateengine.NewDomesticUnpackPricer(), + ghcrateengine.NewDomesticPackPricer(), + ghcrateengine.NewDomesticLinehaulPricer(), + ghcrateengine.NewDomesticShorthaulPricer(), + ghcrateengine.NewDomesticOriginPricer(), + ghcrateengine.NewDomesticDestinationPricer(), + ghcrateengine.NewFuelSurchargePricer()) + ppmShipmentUpdater := ppmshipment.NewPPMShipmentUpdater(ppmEstimator, addressCreator, addressUpdater) boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() - shipmentUpdater := shipment.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipment.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, mtoServiceItemCreator) primeAPIV3.MtoShipmentUpdateMTOShipmentHandler = UpdateMTOShipmentHandler{ handlerConfig, shipmentUpdater, + handlerConfig.DTODPlanner(), + vLocation, } return primeAPIV3 diff --git a/pkg/handlers/primeapiv3/move_task_order_test.go b/pkg/handlers/primeapiv3/move_task_order_test.go index 45b39cef0e4..06880f84230 100644 --- a/pkg/handlers/primeapiv3/move_task_order_test.go +++ b/pkg/handlers/primeapiv3/move_task_order_test.go @@ -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())) 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/mto_shipment.go b/pkg/handlers/primeapiv3/mto_shipment.go index cddeeaab45b..935b32d0524 100644 --- a/pkg/handlers/primeapiv3/mto_shipment.go +++ b/pkg/handlers/primeapiv3/mto_shipment.go @@ -1,6 +1,7 @@ package primeapiv3 import ( + "context" "fmt" "github.com/go-openapi/runtime/middleware" @@ -17,6 +18,7 @@ import ( "github.com/transcom/mymove/pkg/handlers/primeapi" "github.com/transcom/mymove/pkg/handlers/primeapiv3/payloads" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/route" "github.com/transcom/mymove/pkg/services" mtoshipment "github.com/transcom/mymove/pkg/services/mto_shipment" ) @@ -26,6 +28,7 @@ type CreateMTOShipmentHandler struct { handlers.HandlerConfig services.ShipmentCreator mtoAvailabilityChecker services.MoveTaskOrderChecker + services.VLocation } // Handle creates the mto shipment @@ -89,6 +92,35 @@ func (h CreateMTOShipmentHandler) Handle(params mtoshipmentops.CreateMTOShipment "Unaccompanied baggage shipments can't be created unless the unaccompanied_baggage feature flag is enabled.", h.GetTraceIDFromRequest(params.HTTPRequest), nil)), nil } + /** Feature Flag - Alaska - Determines if AK can be included/excluded **/ + isAlaskaEnabled := false + akFeatureFlagName := "enable_alaska" + flag, err = h.FeatureFlagFetcher().GetBooleanFlagForUser(context.TODO(), appCtx, akFeatureFlagName, map[string]string{}) + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", akFeatureFlagName), zap.Error(err)) + } else { + isAlaskaEnabled = flag.Match + } + + /** Feature Flag - Hawaii - Determines if HI can be included/excluded **/ + isHawaiiEnabled := false + hiFeatureFlagName := "enable_hawaii" + flag, err = h.FeatureFlagFetcher().GetBooleanFlagForUser(context.TODO(), appCtx, hiFeatureFlagName, map[string]string{}) + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", hiFeatureFlagName), zap.Error(err)) + } else { + isHawaiiEnabled = flag.Match + } + + // build states to exlude filter list + statesToExclude := make([]string, 0) + if !isAlaskaEnabled { + statesToExclude = append(statesToExclude, "AK") + } + if !isHawaiiEnabled { + statesToExclude = append(statesToExclude, "HI") + } + for _, mtoServiceItem := range params.Body.MtoServiceItems() { // restrict creation to a list if _, ok := CreateableServiceItemMap[mtoServiceItem.ModelType()]; !ok { @@ -128,6 +160,77 @@ func (h CreateMTOShipmentHandler) Handle(params mtoshipmentops.CreateMTOShipment mtoAvailableToPrime, err := h.mtoAvailabilityChecker.MTOAvailableToPrime(appCtx, moveTaskOrderID) if mtoAvailableToPrime { + // check each address prior to creating the shipment to ensure only valid addresses are being used to create the shipment + var addresses []models.Address + + if mtoShipment.ShipmentType != models.MTOShipmentTypePPM { + if mtoShipment.PickupAddress != nil { + addresses = append(addresses, *mtoShipment.PickupAddress) + } + + if mtoShipment.DestinationAddress != nil { + addresses = append(addresses, *mtoShipment.DestinationAddress) + } + + if mtoShipment.SecondaryPickupAddress != nil { + addresses = append(addresses, *mtoShipment.SecondaryPickupAddress) + } + + if mtoShipment.TertiaryPickupAddress != nil { + addresses = append(addresses, *mtoShipment.TertiaryPickupAddress) + } + + if mtoShipment.SecondaryDeliveryAddress != nil { + addresses = append(addresses, *mtoShipment.SecondaryDeliveryAddress) + } + + if mtoShipment.TertiaryDeliveryAddress != nil { + addresses = append(addresses, *mtoShipment.TertiaryDeliveryAddress) + } + } else { + if mtoShipment.PPMShipment.PickupAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.PickupAddress) + } + + if mtoShipment.PPMShipment.DestinationAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.DestinationAddress) + } + + if mtoShipment.PPMShipment.SecondaryPickupAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.SecondaryPickupAddress) + } + + if mtoShipment.PPMShipment.TertiaryPickupAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.TertiaryPickupAddress) + } + + if mtoShipment.PPMShipment.SecondaryDestinationAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.SecondaryDestinationAddress) + } + + if mtoShipment.PPMShipment.TertiaryDestinationAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.TertiaryDestinationAddress) + } + } + + for _, address := range addresses { + addressSearch := address.City + ", " + address.State + " " + address.PostalCode + err := checkValidAddress(h.VLocation, appCtx, statesToExclude, addressSearch) + + if err != nil { + appCtx.Logger().Error("primeapi.UpdateMTOShipmentHandler error", zap.Error(err)) + switch e := err.(type) { + case apperror.UnprocessableEntityError: + payload := payloads.ValidationError(err.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), nil) + return mtoshipmentops.NewCreateMTOShipmentUnprocessableEntity().WithPayload(payload), err + default: + errStr := e.Error() // we do this because InternalServerError wants a *string + payload := payloads.InternalServerError(&errStr, h.GetTraceIDFromRequest(params.HTTPRequest)) + return mtoshipmentops.NewCreateMTOShipmentInternalServerError().WithPayload(payload), e + } + } + } + mtoShipment, err = h.ShipmentCreator.CreateShipment(appCtx, mtoShipment) } else if err == nil { appCtx.Logger().Error("primeapiv3.CreateMTOShipmentHandler error - MTO is not available to Prime") @@ -165,10 +268,27 @@ func (h CreateMTOShipmentHandler) Handle(params mtoshipmentops.CreateMTOShipment }) } +func checkValidAddress(vLocation services.VLocation, appCtx appcontext.AppContext, statesToExclude []string, addressSearch string) error { + locationList, err := vLocation.GetLocationsByZipCityState(appCtx, addressSearch, statesToExclude, true) + + if err != nil { + serverError := apperror.NewInternalServerError("Error searching for address") + return serverError + } else if len(*locationList) == 0 { + unprocessableErr := apperror.NewUnprocessableEntityError( + fmt.Sprintf("primeapi.UpdateShipmentDestinationAddress: could not find the provided location: %s", addressSearch)) + return unprocessableErr + } + + return nil +} + // UpdateMTOShipmentHandler is the handler to update MTO shipments type UpdateMTOShipmentHandler struct { handlers.HandlerConfig services.ShipmentUpdater + planner route.Planner + services.VLocation } // Handle handler that updates a mto shipment @@ -203,8 +323,108 @@ func (h UpdateMTOShipmentHandler) Handle(params mtoshipmentops.UpdateMTOShipment // Validate further prime restrictions on model mtoShipment.ShipmentType = dbShipment.ShipmentType - appCtx.Logger().Info("primeapi.UpdateMTOShipmentHandler info", zap.String("pointOfContact", params.Body.PointOfContact)) + + /** Feature Flag - Alaska - Determines if AK can be included/excluded **/ + isAlaskaEnabled := false + akFeatureFlagName := "enable_alaska" + flag, err := h.FeatureFlagFetcher().GetBooleanFlagForUser(context.TODO(), appCtx, akFeatureFlagName, map[string]string{}) + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", akFeatureFlagName), zap.Error(err)) + } else { + isAlaskaEnabled = flag.Match + } + + /** Feature Flag - Hawaii - Determines if HI can be included/excluded **/ + isHawaiiEnabled := false + hiFeatureFlagName := "enable_hawaii" + flag, err = h.FeatureFlagFetcher().GetBooleanFlagForUser(context.TODO(), appCtx, hiFeatureFlagName, map[string]string{}) + if err != nil { + appCtx.Logger().Error("Error fetching feature flag", zap.String("featureFlagKey", hiFeatureFlagName), zap.Error(err)) + } else { + isHawaiiEnabled = flag.Match + } + + // build states to exlude filter list + statesToExclude := make([]string, 0) + if !isAlaskaEnabled { + statesToExclude = append(statesToExclude, "AK") + } + if !isHawaiiEnabled { + statesToExclude = append(statesToExclude, "HI") + } + + // check each address prior to updating the shipment to ensure only valid addresses are being used + var addresses []models.Address + + if mtoShipment.ShipmentType != models.MTOShipmentTypePPM { + if mtoShipment.PickupAddress != nil { + addresses = append(addresses, *mtoShipment.PickupAddress) + } + + if mtoShipment.SecondaryPickupAddress != nil { + addresses = append(addresses, *mtoShipment.SecondaryPickupAddress) + } + + if mtoShipment.TertiaryPickupAddress != nil { + addresses = append(addresses, *mtoShipment.TertiaryPickupAddress) + } + + if mtoShipment.DestinationAddress != nil { + addresses = append(addresses, *mtoShipment.DestinationAddress) + } + + if mtoShipment.SecondaryDeliveryAddress != nil { + addresses = append(addresses, *mtoShipment.SecondaryDeliveryAddress) + } + + if mtoShipment.TertiaryDeliveryAddress != nil { + addresses = append(addresses, *mtoShipment.TertiaryDeliveryAddress) + } + } else { + if mtoShipment.PPMShipment.PickupAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.PickupAddress) + } + + if mtoShipment.PPMShipment.SecondaryPickupAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.SecondaryPickupAddress) + } + + if mtoShipment.PPMShipment.TertiaryPickupAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.TertiaryPickupAddress) + } + + if mtoShipment.PPMShipment.DestinationAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.DestinationAddress) + } + + if mtoShipment.PPMShipment.SecondaryDestinationAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.SecondaryDestinationAddress) + } + + if mtoShipment.PPMShipment.TertiaryDestinationAddress != nil { + addresses = append(addresses, *mtoShipment.PPMShipment.TertiaryDestinationAddress) + } + } + + for _, address := range addresses { + addressSearch := address.City + ", " + address.State + " " + address.PostalCode + err := checkValidAddress(h.VLocation, appCtx, statesToExclude, addressSearch) + + if err != nil { + appCtx.Logger().Error("primeapi.UpdateMTOShipmentHandler error", zap.Error(err)) + switch e := err.(type) { + case apperror.UnprocessableEntityError: + payload := payloads.ValidationError(err.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), nil) + return mtoshipmentops.NewUpdateMTOShipmentUnprocessableEntity().WithPayload(payload), e + default: + errStr := e.Error() // we do this because InternalServerError wants a *string + payload := payloads.InternalServerError(&errStr, h.GetTraceIDFromRequest(params.HTTPRequest)) + return mtoshipmentops.NewUpdateMTOShipmentInternalServerError().WithPayload(payload), e + } + } + } + mtoShipment, err = h.ShipmentUpdater.UpdateShipment(appCtx, mtoShipment, params.IfMatch, "prime-v3") if err != nil { appCtx.Logger().Error("primeapi.UpdateMTOShipmentHandler error", zap.Error(err)) @@ -223,7 +443,7 @@ func (h UpdateMTOShipmentHandler) Handle(params mtoshipmentops.UpdateMTOShipment payloads.ClientError(handlers.PreconditionErrMessage, err.Error(), h.GetTraceIDFromRequest(params.HTTPRequest))), err default: return mtoshipmentops.NewUpdateMTOShipmentInternalServerError().WithPayload( - payloads.InternalServerError(nil, h.GetTraceIDFromRequest(params.HTTPRequest))), err + payloads.InternalServerError(handlers.FmtString(err.Error()), h.GetTraceIDFromRequest(params.HTTPRequest))), err } } mtoShipmentPayload := payloads.MTOShipment(mtoShipment) diff --git a/pkg/handlers/primeapiv3/mto_shipment_test.go b/pkg/handlers/primeapiv3/mto_shipment_test.go index 8bcef3ec944..3b86ba5f835 100644 --- a/pkg/handlers/primeapiv3/mto_shipment_test.go +++ b/pkg/handlers/primeapiv3/mto_shipment_test.go @@ -61,6 +61,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { mock.Anything, false, ).Return(400, nil) + vLocationServices := address.NewVLocation() setUpSignedCertificationCreatorMock := func(returnValue ...interface{}) services.SignedCertificationCreator { mockCreator := &mocks.SignedCertificationCreator{} @@ -86,10 +87,10 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { return mockUpdater } - + mtoServiceItemCreator := mtoserviceitem.NewMTOServiceItemCreator(planner, builder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) moveTaskOrderUpdater := movetaskorder.NewMoveTaskOrderUpdater( builder, - mtoserviceitem.NewMTOServiceItemCreator(planner, builder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()), + mtoServiceItemCreator, moveRouter, setUpSignedCertificationCreatorMock(nil, nil), setUpSignedCertificationUpdaterMock(nil, nil), &ppmEstimator, ) shipmentCreator := shipmentorchestrator.NewShipmentCreator(mtoShipmentCreator, ppmShipmentCreator, boatShipmentCreator, mobileHomeShipmentCreator, shipmentRouter, moveTaskOrderUpdater) @@ -112,10 +113,70 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { boatShipmentUpdater := boatshipment.NewBoatShipmentUpdater() mobileHomeShipmentUpdater := mobilehomeshipment.NewMobileHomeShipmentUpdater() mtoShipmentUpdater := mtoshipment.NewPrimeMTOShipmentUpdater(builder, fetcher, planner, moveRouter, moveWeights, suite.TestNotificationSender(), paymentRequestShipmentRecalculator, addressUpdater, addressCreator) - shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, mtoServiceItemCreator) - setupTestData := func(boatFeatureFlag bool, ubFeatureFlag bool) (CreateMTOShipmentHandler, models.Move) { + setupAddresses := func() { + // Make stubbed addresses just to collect address data for payload + newAddress := factory.BuildAddress(nil, []factory.Customization{ + { + Model: models.Address{ + ID: uuid.Must(uuid.NewV4()), + }, + }, + }, nil) + pickupAddress = primev3messages.Address{ + City: &newAddress.City, + PostalCode: &newAddress.PostalCode, + State: &newAddress.State, + StreetAddress1: &newAddress.StreetAddress1, + StreetAddress2: newAddress.StreetAddress2, + StreetAddress3: newAddress.StreetAddress3, + } + secondaryPickupAddress = primev3messages.Address{ + City: &newAddress.City, + PostalCode: &newAddress.PostalCode, + State: &newAddress.State, + StreetAddress1: &newAddress.StreetAddress1, + StreetAddress2: newAddress.StreetAddress2, + StreetAddress3: newAddress.StreetAddress3, + } + tertiaryPickupAddress = primev3messages.Address{ + City: &newAddress.City, + PostalCode: &newAddress.PostalCode, + State: &newAddress.State, + StreetAddress1: &newAddress.StreetAddress1, + StreetAddress2: newAddress.StreetAddress2, + StreetAddress3: newAddress.StreetAddress3, + } + newAddress = factory.BuildAddress(nil, nil, []factory.Trait{factory.GetTraitAddress2}) + destinationAddress = primev3messages.Address{ + City: &newAddress.City, + PostalCode: &newAddress.PostalCode, + State: &newAddress.State, + StreetAddress1: &newAddress.StreetAddress1, + StreetAddress2: newAddress.StreetAddress2, + StreetAddress3: newAddress.StreetAddress3, + } + secondaryDestinationAddress = primev3messages.Address{ + City: &newAddress.City, + PostalCode: &newAddress.PostalCode, + State: &newAddress.State, + StreetAddress1: &newAddress.StreetAddress1, + StreetAddress2: newAddress.StreetAddress2, + StreetAddress3: newAddress.StreetAddress3, + } + tertiaryDestinationAddress = primev3messages.Address{ + City: &newAddress.City, + PostalCode: &newAddress.PostalCode, + State: &newAddress.State, + StreetAddress1: &newAddress.StreetAddress1, + StreetAddress2: newAddress.StreetAddress2, + StreetAddress3: newAddress.StreetAddress3, + } + } + setupTestData := func(boatFeatureFlag bool, ubFeatureFlag bool) (CreateMTOShipmentHandler, models.Move) { + vLocationServices := address.NewVLocation() move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) handlerConfig := suite.HandlerConfig() expectedFeatureFlag := services.FeatureFlag{ @@ -197,67 +258,26 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { handlerConfig, shipmentCreator, mtoChecker, + vLocationServices, } - // Make stubbed addresses just to collect address data for payload - newAddress := factory.BuildAddress(nil, []factory.Customization{ - { - Model: models.Address{ - ID: uuid.Must(uuid.NewV4()), - }, - }, - }, nil) - pickupAddress = primev3messages.Address{ - City: &newAddress.City, - PostalCode: &newAddress.PostalCode, - State: &newAddress.State, - StreetAddress1: &newAddress.StreetAddress1, - StreetAddress2: newAddress.StreetAddress2, - StreetAddress3: newAddress.StreetAddress3, - } - secondaryPickupAddress = primev3messages.Address{ - City: &newAddress.City, - PostalCode: &newAddress.PostalCode, - State: &newAddress.State, - StreetAddress1: &newAddress.StreetAddress1, - StreetAddress2: newAddress.StreetAddress2, - StreetAddress3: newAddress.StreetAddress3, - } - tertiaryPickupAddress = primev3messages.Address{ - City: &newAddress.City, - PostalCode: &newAddress.PostalCode, - State: &newAddress.State, - StreetAddress1: &newAddress.StreetAddress1, - StreetAddress2: newAddress.StreetAddress2, - StreetAddress3: newAddress.StreetAddress3, - } - newAddress = factory.BuildAddress(nil, nil, []factory.Trait{factory.GetTraitAddress2}) - destinationAddress = primev3messages.Address{ - City: &newAddress.City, - PostalCode: &newAddress.PostalCode, - State: &newAddress.State, - StreetAddress1: &newAddress.StreetAddress1, - StreetAddress2: newAddress.StreetAddress2, - StreetAddress3: newAddress.StreetAddress3, - } - secondaryDestinationAddress = primev3messages.Address{ - City: &newAddress.City, - PostalCode: &newAddress.PostalCode, - State: &newAddress.State, - StreetAddress1: &newAddress.StreetAddress1, - StreetAddress2: newAddress.StreetAddress2, - StreetAddress3: newAddress.StreetAddress3, - } - tertiaryDestinationAddress = primev3messages.Address{ - City: &newAddress.City, - PostalCode: &newAddress.PostalCode, - State: &newAddress.State, - StreetAddress1: &newAddress.StreetAddress1, - StreetAddress2: newAddress.StreetAddress2, - StreetAddress3: newAddress.StreetAddress3, - } + setupAddresses() return handler, move + } + + setupTestDataWithoutFF := func() (CreateMTOShipmentHandler, models.Move) { + vLocationServices := address.NewVLocation() + move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + + handler := CreateMTOShipmentHandler{ + suite.HandlerConfig(), + shipmentCreator, + mtoChecker, + vLocationServices, + } + setupAddresses() + return handler, move } suite.Run("Successful POST - Integration Test", func() { @@ -363,20 +383,20 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { address1 := models.Address{ StreetAddress1: "some address", - City: "city", + City: "Beverly Hills", State: "CA", PostalCode: "90210", } address2 := models.Address{ StreetAddress1: "some address", - City: "city", + City: "Scott Afb", State: "IL", PostalCode: "62225", } address3 := models.Address{ StreetAddress1: "some address", - City: "city", + City: "Suffolk", State: "VA", PostalCode: "23435", } @@ -542,6 +562,12 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { mock.AnythingOfType("*models.PPMShipment")). Return(nil, nil) + ppmEstimator.On("MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(nil, nil) + ppmEstimator.On("FinalIncentiveWithDefaultChecks", mock.AnythingOfType("*appcontext.appContext"), mock.AnythingOfType("models.PPMShipment"), @@ -551,6 +577,8 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { patchHandler := UpdateMTOShipmentHandler{ suite.HandlerConfig(), shipmentUpdater, + planner, + vLocationServices, } patchReq := httptest.NewRequest("PATCH", fmt.Sprintf("/mto-shipments/%s", createdPPM.ShipmentID.String()), nil) @@ -711,13 +739,13 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { address1 := models.Address{ StreetAddress1: "some address", - City: "city", + City: "Beverly Hills", State: "CA", PostalCode: "90210", } addressWithEmptyStreet1 := models.Address{ StreetAddress1: "", - City: "city", + City: "Beverly Hills", State: "CA", PostalCode: "90210", } @@ -832,6 +860,8 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { patchHandler := UpdateMTOShipmentHandler{ suite.HandlerConfig(), shipmentUpdater, + planner, + vLocationServices, } patchReq := httptest.NewRequest("PATCH", fmt.Sprintf("/mto-shipments/%s", createdPPM.ShipmentID.String()), nil) @@ -854,7 +884,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { // as empty on the server side. // ************************************************************************************* ppmDestinationAddressOptionalStreet1ContainingWhitespaces := primev3messages.PPMDestinationAddress{ - City: models.StringPointer("SomeCity"), + City: models.StringPointer("Beverly Hills"), Country: models.StringPointer("US"), PostalCode: models.StringPointer("90210"), State: models.StringPointer("CA"), @@ -1547,10 +1577,12 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { // Setup: If underlying CreateMTOShipment returns error, handler should return 422 response // Expected: 422 Response returned - shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, mtoServiceItemCreator) patchHandler := UpdateMTOShipmentHandler{ suite.HandlerConfig(), shipmentUpdater, + planner, + vLocationServices, } now := time.Now() @@ -1558,7 +1590,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { { Model: models.Address{ StreetAddress1: "some address", - City: "city", + City: "Beverly Hills", State: "CA", PostalCode: "90210", }, @@ -1567,7 +1599,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { { Model: models.Address{ StreetAddress1: "some address", - City: "city", + City: "Beverly Hills", State: "CA", PostalCode: "90210", }, @@ -1622,10 +1654,12 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { // Setup: If underlying UpdateMTOShipment returns error, handler should return 422 response // Expected: 422 Response returned - shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, mtoServiceItemCreator) patchHandler := UpdateMTOShipmentHandler{ suite.HandlerConfig(), shipmentUpdater, + planner, + vLocationServices, } move := factory.BuildAvailableToPrimeMove(suite.DB(), []factory.Customization{}, nil) @@ -1670,10 +1704,12 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { // Setup: If underlying CreateMTOShipment returns error, handler should return 422 response // Expected: 422 Response returned - shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater) + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, mtoServiceItemCreator) patchHandler := UpdateMTOShipmentHandler{ suite.HandlerConfig(), shipmentUpdater, + planner, + vLocationServices, } now := time.Now() @@ -1681,7 +1717,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { { Model: models.Address{ StreetAddress1: "some address", - City: "city", + City: "Beverly Hills", State: "CA", PostalCode: "90210", }, @@ -1690,7 +1726,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { { Model: models.Address{ StreetAddress1: "some address", - City: "city", + City: "Beverly Hills", State: "CA", PostalCode: "90210", }, @@ -1699,7 +1735,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { { Model: models.Address{ StreetAddress1: "some address", - City: "city", + City: "Beverly Hills", State: "CA", PostalCode: "90210", }, @@ -1708,7 +1744,7 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { { Model: models.Address{ StreetAddress1: "some address", - City: "city", + City: "Beverly Hills", State: "CA", PostalCode: "90210", }, @@ -1755,6 +1791,1063 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandler() { response := patchResponse.(*mtoshipmentops.UpdateMTOShipmentOK) suite.IsType(&mtoshipmentops.UpdateMTOShipmentOK{}, response) }) + + suite.Run("PATCH failure - Invalid address.", func() { + // Under Test: UpdateMTOShipmentHandler + // Setup: Set an invalid zip + // Expected: 422 Response returned + + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, mtoServiceItemCreator) + patchHandler := UpdateMTOShipmentHandler{ + suite.HandlerConfig(), + shipmentUpdater, + planner, + vLocationServices, + } + + now := time.Now() + mto_shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "some pickup address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some second pickup address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.SecondaryPickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some third pickup address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.TertiaryPickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some delivery address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.DeliveryAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some second delivery address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.SecondaryDeliveryAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some third delivery address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.TertiaryDeliveryAddress, + }, + }, nil) + move := factory.BuildMoveWithPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + AvailableToPrimeAt: &now, + ApprovedAt: &now, + Status: models.MoveStatusAPPROVED, + }, + }, + }, nil) + + var testMove models.Move + err := suite.DB().EagerPreload("MTOShipments.PPMShipment").Find(&testMove, move.ID) + suite.NoError(err) + var testMtoShipment models.MTOShipment + err = suite.DB().Find(&testMtoShipment, mto_shipment.ID) + suite.NoError(err) + testMtoShipment.MoveTaskOrderID = testMove.ID + testMtoShipment.MoveTaskOrder = testMove + err = suite.DB().Save(&testMtoShipment) + suite.NoError(err) + testMove.MTOShipments = append(testMove.MTOShipments, mto_shipment) + err = suite.DB().Save(&testMove) + suite.NoError(err) + + patchReq := httptest.NewRequest("PATCH", fmt.Sprintf("/mto-shipments/%s", testMove.MTOShipments[0].ID), nil) + + eTag := etag.GenerateEtag(testMtoShipment.UpdatedAt) + patchParams := mtoshipmentops.UpdateMTOShipmentParams{ + HTTPRequest: patchReq, + MtoShipmentID: strfmt.UUID(testMtoShipment.ID.String()), + IfMatch: eTag, + } + tertiaryAddress := GetTestAddress() + tertiaryAddress.PostalCode = handlers.FmtString("99999") + patchParams.Body = &primev3messages.UpdateMTOShipment{ + TertiaryDeliveryAddress: struct{ primev3messages.Address }{tertiaryAddress}, + } + patchResponse := patchHandler.Handle(patchParams) + errResponse := patchResponse.(*mtoshipmentops.UpdateMTOShipmentUnprocessableEntity) + suite.IsType(&mtoshipmentops.UpdateMTOShipmentUnprocessableEntity{}, errResponse) + }) + + suite.Run("PATCH failure - Internal Server error GetLocationsByZipCityState", func() { + // Under Test: UpdateMTOShipmentHandler + // Setup: Mock location to return an error + // Expected: 500 Response returned + handler, move := setupTestData(false, true) + req := httptest.NewRequest("POST", "/mto-shipments", nil) + + params := mtoshipmentops.CreateMTOShipmentParams{ + HTTPRequest: req, + Body: &primev3messages.CreateMTOShipment{ + MoveTaskOrderID: handlers.FmtUUID(move.ID), + Agents: nil, + CustomerRemarks: nil, + PointOfContact: "John Doe", + PrimeEstimatedWeight: handlers.FmtInt64(1200), + RequestedPickupDate: handlers.FmtDatePtr(models.TimePointer(time.Now())), + ShipmentType: primev3messages.NewMTOShipmentType(primev3messages.MTOShipmentTypeHHG), + PickupAddress: struct{ primev3messages.Address }{pickupAddress}, + DestinationAddress: struct{ primev3messages.Address }{destinationAddress}, + }, + } + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + expectedError := models.ErrFetchNotFound + vLocationFetcher := &mocks.VLocation{} + vLocationFetcher.On("GetLocationsByZipCityState", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + mock.Anything, + mock.Anything, + ).Return(nil, expectedError).Once() + handler.VLocation = vLocationFetcher + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.CreateMTOShipmentInternalServerError{}, response) + }) + + suite.Run("PATCH success - valid AK address FF is on", func() { + // Under Test: UpdateMTOShipmentHandler + // Setup: Set an valid AK address but turn FF on + // Expected: 200 Response returned + + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, mtoServiceItemCreator) + patchHandler := UpdateMTOShipmentHandler{ + suite.HandlerConfig(), + shipmentUpdater, + planner, + vLocationServices, + } + + now := time.Now() + mto_shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "some pickup address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some second pickup address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.SecondaryPickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some third pickup address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.TertiaryPickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some delivery address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.DeliveryAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some second delivery address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.SecondaryDeliveryAddress, + }, + }, nil) + move := factory.BuildMoveWithPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + AvailableToPrimeAt: &now, + ApprovedAt: &now, + Status: models.MoveStatusAPPROVED, + }, + }, + }, nil) + + var testMove models.Move + err := suite.DB().EagerPreload("MTOShipments.PPMShipment").Find(&testMove, move.ID) + suite.NoError(err) + var testMtoShipment models.MTOShipment + err = suite.DB().Find(&testMtoShipment, mto_shipment.ID) + suite.NoError(err) + testMtoShipment.MoveTaskOrderID = testMove.ID + testMtoShipment.MoveTaskOrder = testMove + err = suite.DB().Save(&testMtoShipment) + suite.NoError(err) + testMove.MTOShipments = append(testMove.MTOShipments, mto_shipment) + err = suite.DB().Save(&testMove) + suite.NoError(err) + + patchReq := httptest.NewRequest("PATCH", fmt.Sprintf("/mto-shipments/%s", testMove.MTOShipments[0].ID), nil) + + eTag := etag.GenerateEtag(testMtoShipment.UpdatedAt) + patchParams := mtoshipmentops.UpdateMTOShipmentParams{ + HTTPRequest: patchReq, + MtoShipmentID: strfmt.UUID(testMtoShipment.ID.String()), + IfMatch: eTag, + } + alaskaAddress := primev3messages.Address{ + City: handlers.FmtString("Juneau"), + PostalCode: handlers.FmtString("99801"), + State: handlers.FmtString("AK"), + StreetAddress1: handlers.FmtString("Some AK street"), + } + patchParams.Body = &primev3messages.UpdateMTOShipment{ + TertiaryDeliveryAddress: struct{ primev3messages.Address }{alaskaAddress}, + } + + // setting the AK flag to true + handlerConfig := suite.HandlerConfig() + + expectedFeatureFlag := services.FeatureFlag{ + Key: "enable_alaska", + Match: true, + } + + mockFeatureFlagFetcher := &mocks.FeatureFlagFetcher{} + mockFeatureFlagFetcher.On("GetBooleanFlagForUser", + mock.Anything, + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("string"), + mock.Anything, + ).Return(expectedFeatureFlag, nil) + handlerConfig.SetFeatureFlagFetcher(mockFeatureFlagFetcher) + patchHandler.HandlerConfig = handlerConfig + patchResponse := patchHandler.Handle(patchParams) + errResponse := patchResponse.(*mtoshipmentops.UpdateMTOShipmentOK) + suite.IsType(&mtoshipmentops.UpdateMTOShipmentOK{}, errResponse) + }) + + suite.Run("PATCH success - valid HI address FF is on", func() { + // Under Test: UpdateMTOShipmentHandler + // Setup: Set an valid HI address but turn FF on + // Expected: 200 Response returned + + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, mtoServiceItemCreator) + patchHandler := UpdateMTOShipmentHandler{ + suite.HandlerConfig(), + shipmentUpdater, + planner, + vLocationServices, + } + + now := time.Now() + mto_shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "some pickup address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some second pickup address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.SecondaryPickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some third pickup address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.TertiaryPickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some delivery address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.DeliveryAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some second delivery address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.SecondaryDeliveryAddress, + }, + }, nil) + move := factory.BuildMoveWithPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + AvailableToPrimeAt: &now, + ApprovedAt: &now, + Status: models.MoveStatusAPPROVED, + }, + }, + }, nil) + + var testMove models.Move + err := suite.DB().EagerPreload("MTOShipments.PPMShipment").Find(&testMove, move.ID) + suite.NoError(err) + var testMtoShipment models.MTOShipment + err = suite.DB().Find(&testMtoShipment, mto_shipment.ID) + suite.NoError(err) + testMtoShipment.MoveTaskOrderID = testMove.ID + testMtoShipment.MoveTaskOrder = testMove + err = suite.DB().Save(&testMtoShipment) + suite.NoError(err) + testMove.MTOShipments = append(testMove.MTOShipments, mto_shipment) + err = suite.DB().Save(&testMove) + suite.NoError(err) + + patchReq := httptest.NewRequest("PATCH", fmt.Sprintf("/mto-shipments/%s", testMove.MTOShipments[0].ID), nil) + + eTag := etag.GenerateEtag(testMtoShipment.UpdatedAt) + patchParams := mtoshipmentops.UpdateMTOShipmentParams{ + HTTPRequest: patchReq, + MtoShipmentID: strfmt.UUID(testMtoShipment.ID.String()), + IfMatch: eTag, + } + hawaiiAddress := primev3messages.Address{ + City: handlers.FmtString("HONOLULU"), + PostalCode: handlers.FmtString("96835"), + State: handlers.FmtString("HI"), + StreetAddress1: handlers.FmtString("Some HI street"), + } + patchParams.Body = &primev3messages.UpdateMTOShipment{ + TertiaryDeliveryAddress: struct{ primev3messages.Address }{hawaiiAddress}, + } + + // setting the HI flag to true + handlerConfig := suite.HandlerConfig() + + expectedFeatureFlag := services.FeatureFlag{ + Key: "enable_hawaii", + Match: true, + } + + mockFeatureFlagFetcher := &mocks.FeatureFlagFetcher{} + mockFeatureFlagFetcher.On("GetBooleanFlagForUser", + mock.Anything, + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("string"), + mock.Anything, + ).Return(expectedFeatureFlag, nil) + handlerConfig.SetFeatureFlagFetcher(mockFeatureFlagFetcher) + patchHandler.HandlerConfig = handlerConfig + patchResponse := patchHandler.Handle(patchParams) + errResponse := patchResponse.(*mtoshipmentops.UpdateMTOShipmentOK) + suite.IsType(&mtoshipmentops.UpdateMTOShipmentOK{}, errResponse) + }) + + suite.Run("PATCH failure - valid AK address FF is off", func() { + // Under Test: UpdateMTOShipmentHandler + // Setup: Set an valid AK address but turn FF off + // Expected: 422 Response returned + + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, mtoServiceItemCreator) + patchHandler := UpdateMTOShipmentHandler{ + suite.HandlerConfig(), + shipmentUpdater, + planner, + vLocationServices, + } + + now := time.Now() + mto_shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "some pickup address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some second pickup address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.SecondaryPickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some third pickup address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.TertiaryPickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some delivery address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.DeliveryAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some second delivery address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.SecondaryDeliveryAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some third delivery address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.TertiaryDeliveryAddress, + }, + }, nil) + move := factory.BuildMoveWithPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + AvailableToPrimeAt: &now, + ApprovedAt: &now, + Status: models.MoveStatusAPPROVED, + }, + }, + }, nil) + + var testMove models.Move + err := suite.DB().EagerPreload("MTOShipments.PPMShipment").Find(&testMove, move.ID) + suite.NoError(err) + var testMtoShipment models.MTOShipment + err = suite.DB().Find(&testMtoShipment, mto_shipment.ID) + suite.NoError(err) + testMtoShipment.MoveTaskOrderID = testMove.ID + testMtoShipment.MoveTaskOrder = testMove + err = suite.DB().Save(&testMtoShipment) + suite.NoError(err) + testMove.MTOShipments = append(testMove.MTOShipments, mto_shipment) + err = suite.DB().Save(&testMove) + suite.NoError(err) + + patchReq := httptest.NewRequest("PATCH", fmt.Sprintf("/mto-shipments/%s", testMove.MTOShipments[0].ID), nil) + + eTag := etag.GenerateEtag(testMtoShipment.UpdatedAt) + patchParams := mtoshipmentops.UpdateMTOShipmentParams{ + HTTPRequest: patchReq, + MtoShipmentID: strfmt.UUID(testMtoShipment.ID.String()), + IfMatch: eTag, + } + alaskaAddress := primev3messages.Address{ + City: handlers.FmtString("Juneau"), + PostalCode: handlers.FmtString("99801"), + State: handlers.FmtString("AK"), + StreetAddress1: handlers.FmtString("Some AK street"), + } + patchParams.Body = &primev3messages.UpdateMTOShipment{ + TertiaryDeliveryAddress: struct{ primev3messages.Address }{alaskaAddress}, + } + + // setting the AK flag to false + handlerConfig := suite.HandlerConfig() + + expectedFeatureFlag := services.FeatureFlag{ + Key: "enable_alaska", + Match: false, + } + + mockFeatureFlagFetcher := &mocks.FeatureFlagFetcher{} + mockFeatureFlagFetcher.On("GetBooleanFlagForUser", + mock.Anything, + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("string"), + mock.Anything, + ).Return(expectedFeatureFlag, nil) + handlerConfig.SetFeatureFlagFetcher(mockFeatureFlagFetcher) + patchHandler.HandlerConfig = handlerConfig + patchResponse := patchHandler.Handle(patchParams) + errResponse := patchResponse.(*mtoshipmentops.UpdateMTOShipmentUnprocessableEntity) + suite.IsType(&mtoshipmentops.UpdateMTOShipmentUnprocessableEntity{}, errResponse) + }) + + suite.Run("PATCH failure - valid HI address FF is off", func() { + // Under Test: UpdateMTOShipmentHandler + // Setup: Set an valid HI address but turn FF off + // Expected: 422 Response returned + + shipmentUpdater := shipmentorchestrator.NewShipmentUpdater(mtoShipmentUpdater, ppmShipmentUpdater, boatShipmentUpdater, mobileHomeShipmentUpdater, mtoServiceItemCreator) + patchHandler := UpdateMTOShipmentHandler{ + suite.HandlerConfig(), + shipmentUpdater, + planner, + vLocationServices, + } + + now := time.Now() + mto_shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "some pickup address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some second pickup address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.SecondaryPickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some third pickup address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.TertiaryPickupAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some delivery address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.DeliveryAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some second delivery address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.SecondaryDeliveryAddress, + }, + { + Model: models.Address{ + StreetAddress1: "some third delivery address", + City: "Beverly Hills", + State: "CA", + PostalCode: "90210", + }, + Type: &factory.Addresses.TertiaryDeliveryAddress, + }, + }, nil) + move := factory.BuildMoveWithPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + AvailableToPrimeAt: &now, + ApprovedAt: &now, + Status: models.MoveStatusAPPROVED, + }, + }, + }, nil) + + var testMove models.Move + err := suite.DB().EagerPreload("MTOShipments.PPMShipment").Find(&testMove, move.ID) + suite.NoError(err) + var testMtoShipment models.MTOShipment + err = suite.DB().Find(&testMtoShipment, mto_shipment.ID) + suite.NoError(err) + testMtoShipment.MoveTaskOrderID = testMove.ID + testMtoShipment.MoveTaskOrder = testMove + err = suite.DB().Save(&testMtoShipment) + suite.NoError(err) + testMove.MTOShipments = append(testMove.MTOShipments, mto_shipment) + err = suite.DB().Save(&testMove) + suite.NoError(err) + + patchReq := httptest.NewRequest("PATCH", fmt.Sprintf("/mto-shipments/%s", testMove.MTOShipments[0].ID), nil) + + eTag := etag.GenerateEtag(testMtoShipment.UpdatedAt) + patchParams := mtoshipmentops.UpdateMTOShipmentParams{ + HTTPRequest: patchReq, + MtoShipmentID: strfmt.UUID(testMtoShipment.ID.String()), + IfMatch: eTag, + } + hawaiiAddress := primev3messages.Address{ + City: handlers.FmtString("HONOLULU"), + PostalCode: handlers.FmtString("HI"), + State: handlers.FmtString("96835"), + StreetAddress1: handlers.FmtString("Some HI street"), + } + patchParams.Body = &primev3messages.UpdateMTOShipment{ + TertiaryDeliveryAddress: struct{ primev3messages.Address }{hawaiiAddress}, + } + + // setting the HI flag to false + handlerConfig := suite.HandlerConfig() + + expectedFeatureFlag := services.FeatureFlag{ + Key: "enable_hawaii", + Match: false, + } + + mockFeatureFlagFetcher := &mocks.FeatureFlagFetcher{} + mockFeatureFlagFetcher.On("GetBooleanFlagForUser", + mock.Anything, + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("string"), + mock.Anything, + ).Return(expectedFeatureFlag, nil) + handlerConfig.SetFeatureFlagFetcher(mockFeatureFlagFetcher) + patchHandler.HandlerConfig = handlerConfig + patchResponse := patchHandler.Handle(patchParams) + errResponse := patchResponse.(*mtoshipmentops.UpdateMTOShipmentUnprocessableEntity) + suite.IsType(&mtoshipmentops.UpdateMTOShipmentUnprocessableEntity{}, errResponse) + }) + + suite.Run("POST failure - 422 - Invalid address", func() { + // Under Test: CreateMTOShipment handler code + // Setup: Create an mto shipment on an available move + // Expected: Failure, invalid address + handler, move := setupTestDataWithoutFF() + req := httptest.NewRequest("POST", "/mto-shipments", nil) + + params := mtoshipmentops.CreateMTOShipmentParams{ + HTTPRequest: req, + Body: &primev3messages.CreateMTOShipment{ + MoveTaskOrderID: handlers.FmtUUID(move.ID), + Agents: nil, + CustomerRemarks: nil, + PointOfContact: "John Doe", + PrimeEstimatedWeight: handlers.FmtInt64(1200), + RequestedPickupDate: handlers.FmtDatePtr(models.TimePointer(time.Now())), + ShipmentType: primev3messages.NewMTOShipmentType(primev3messages.MTOShipmentTypeHHG), + PickupAddress: struct{ primev3messages.Address }{pickupAddress}, + SecondaryPickupAddress: struct{ primev3messages.Address }{secondaryPickupAddress}, + TertiaryPickupAddress: struct{ primev3messages.Address }{tertiaryPickupAddress}, + DestinationAddress: struct{ primev3messages.Address }{destinationAddress}, + SecondaryDestinationAddress: struct{ primev3messages.Address }{secondaryDestinationAddress}, + TertiaryDestinationAddress: struct{ primev3messages.Address }{tertiaryDestinationAddress}, + }, + } + + // set bad data for address so the validation fails + params.Body.PickupAddress.City = handlers.FmtString("Bad City") + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.CreateMTOShipmentUnprocessableEntity{}, response) + }) + + suite.Run("POST failure - 422 - Doesn't return results for valid AK address if FF returns false", func() { + // Under Test: CreateMTOShipment handler code + // Setup: Create an mto shipment on an available move + // Expected: Failure, valid AK address but AK FF off, no results + handler, move := setupTestDataWithoutFF() + req := httptest.NewRequest("POST", "/mto-shipments", nil) + + params := mtoshipmentops.CreateMTOShipmentParams{ + HTTPRequest: req, + Body: &primev3messages.CreateMTOShipment{ + MoveTaskOrderID: handlers.FmtUUID(move.ID), + Agents: nil, + CustomerRemarks: nil, + PointOfContact: "John Doe", + PrimeEstimatedWeight: handlers.FmtInt64(1200), + RequestedPickupDate: handlers.FmtDatePtr(models.TimePointer(time.Now())), + ShipmentType: primev3messages.NewMTOShipmentType(primev3messages.MTOShipmentTypeHHG), + PickupAddress: struct{ primev3messages.Address }{pickupAddress}, + SecondaryPickupAddress: struct{ primev3messages.Address }{secondaryPickupAddress}, + TertiaryPickupAddress: struct{ primev3messages.Address }{tertiaryPickupAddress}, + DestinationAddress: struct{ primev3messages.Address }{destinationAddress}, + SecondaryDestinationAddress: struct{ primev3messages.Address }{secondaryDestinationAddress}, + TertiaryDestinationAddress: struct{ primev3messages.Address }{tertiaryDestinationAddress}, + }, + } + + // setting the AK flag to false and use a valid address + handlerConfig := suite.HandlerConfig() + + expectedFeatureFlag := services.FeatureFlag{ + Key: "enable_alaska", + Match: false, + } + + mockFeatureFlagFetcher := &mocks.FeatureFlagFetcher{} + mockFeatureFlagFetcher.On("GetBooleanFlag", + mock.Anything, // context.Context + mock.Anything, // *zap.Logger + mock.AnythingOfType("string"), // entityID (userID) + mock.AnythingOfType("string"), // key + mock.Anything, // flagContext (map[string]string) + ).Return(expectedFeatureFlag, nil) + handlerConfig.SetFeatureFlagFetcher(mockFeatureFlagFetcher) + mockFeatureFlagFetcher.On("GetBooleanFlagForUser", + mock.Anything, + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("string"), + mock.Anything, + ).Return(expectedFeatureFlag, nil) + handlerConfig.SetFeatureFlagFetcher(mockFeatureFlagFetcher) + handler.HandlerConfig = handlerConfig + params.Body.PickupAddress.City = handlers.FmtString("JUNEAU") + params.Body.PickupAddress.State = handlers.FmtString("AK") + params.Body.PickupAddress.PostalCode = handlers.FmtString("99801") + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.CreateMTOShipmentUnprocessableEntity{}, response) + }) + + suite.Run("POST failure - 422 - Doesn't return results for valid HI address if FF returns false", func() { + // Under Test: CreateMTOShipment handler code + // Setup: Create an mto shipment on an available move + // Expected: Failure, valid HI address but HI FF off, no results + handler, move := setupTestDataWithoutFF() + req := httptest.NewRequest("POST", "/mto-shipments", nil) + + params := mtoshipmentops.CreateMTOShipmentParams{ + HTTPRequest: req, + Body: &primev3messages.CreateMTOShipment{ + MoveTaskOrderID: handlers.FmtUUID(move.ID), + Agents: nil, + CustomerRemarks: nil, + PointOfContact: "John Doe", + PrimeEstimatedWeight: handlers.FmtInt64(1200), + RequestedPickupDate: handlers.FmtDatePtr(models.TimePointer(time.Now())), + ShipmentType: primev3messages.NewMTOShipmentType(primev3messages.MTOShipmentTypeHHG), + PickupAddress: struct{ primev3messages.Address }{pickupAddress}, + SecondaryPickupAddress: struct{ primev3messages.Address }{secondaryPickupAddress}, + TertiaryPickupAddress: struct{ primev3messages.Address }{tertiaryPickupAddress}, + DestinationAddress: struct{ primev3messages.Address }{destinationAddress}, + SecondaryDestinationAddress: struct{ primev3messages.Address }{secondaryDestinationAddress}, + TertiaryDestinationAddress: struct{ primev3messages.Address }{tertiaryDestinationAddress}, + }, + } + + // setting the HI flag to false and use a valid address + handlerConfig := suite.HandlerConfig() + + expectedFeatureFlag := services.FeatureFlag{ + Key: "enable_hawaii", + Match: false, + } + + mockFeatureFlagFetcher := &mocks.FeatureFlagFetcher{} + mockFeatureFlagFetcher.On("GetBooleanFlag", + mock.Anything, // context.Context + mock.Anything, // *zap.Logger + mock.AnythingOfType("string"), // entityID (userID) + mock.AnythingOfType("string"), // key + mock.Anything, // flagContext (map[string]string) + ).Return(expectedFeatureFlag, nil) + handlerConfig.SetFeatureFlagFetcher(mockFeatureFlagFetcher) + mockFeatureFlagFetcher.On("GetBooleanFlagForUser", + mock.Anything, + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("string"), + mock.Anything, + ).Return(expectedFeatureFlag, nil) + handlerConfig.SetFeatureFlagFetcher(mockFeatureFlagFetcher) + handler.HandlerConfig = handlerConfig + params.Body.PickupAddress.City = handlers.FmtString("HONOLULU") + params.Body.PickupAddress.State = handlers.FmtString("HI") + params.Body.PickupAddress.PostalCode = handlers.FmtString("96835") + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.CreateMTOShipmentUnprocessableEntity{}, response) + }) + + suite.Run("POST success - 200 - valid AK address if FF ON", func() { + // Under Test: CreateMTOShipment handler code + // Setup: Create an mto shipment on an available move + // Expected: Success, valid AK address AK FF ON + handler, move := setupTestData(false, true) + req := httptest.NewRequest("POST", "/mto-shipments", nil) + + params := mtoshipmentops.CreateMTOShipmentParams{ + HTTPRequest: req, + Body: &primev3messages.CreateMTOShipment{ + MoveTaskOrderID: handlers.FmtUUID(move.ID), + Agents: nil, + CustomerRemarks: nil, + PointOfContact: "John Doe", + PrimeEstimatedWeight: handlers.FmtInt64(1200), + RequestedPickupDate: handlers.FmtDatePtr(models.TimePointer(time.Now())), + ShipmentType: primev3messages.NewMTOShipmentType(primev3messages.MTOShipmentTypeHHG), + PickupAddress: struct{ primev3messages.Address }{pickupAddress}, + SecondaryPickupAddress: struct{ primev3messages.Address }{secondaryPickupAddress}, + TertiaryPickupAddress: struct{ primev3messages.Address }{tertiaryPickupAddress}, + DestinationAddress: struct{ primev3messages.Address }{destinationAddress}, + SecondaryDestinationAddress: struct{ primev3messages.Address }{secondaryDestinationAddress}, + TertiaryDestinationAddress: struct{ primev3messages.Address }{tertiaryDestinationAddress}, + }, + } + + // setting the AK flag to false and use a valid address + handlerConfig := suite.HandlerConfig() + + expectedFeatureFlag := services.FeatureFlag{ + Key: "enable_alaska", + Match: true, + } + + mockFeatureFlagFetcher := &mocks.FeatureFlagFetcher{} + mockFeatureFlagFetcher.On("GetBooleanFlag", + mock.Anything, // context.Context + mock.Anything, // *zap.Logger + mock.AnythingOfType("string"), // entityID (userID) + mock.AnythingOfType("string"), // key + mock.Anything, // flagContext (map[string]string) + ).Return(expectedFeatureFlag, nil) + handlerConfig.SetFeatureFlagFetcher(mockFeatureFlagFetcher) + mockFeatureFlagFetcher.On("GetBooleanFlagForUser", + mock.Anything, + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("string"), + mock.Anything, + ).Return(expectedFeatureFlag, nil) + handlerConfig.SetFeatureFlagFetcher(mockFeatureFlagFetcher) + handler.HandlerConfig = handlerConfig + params.Body.PickupAddress.City = handlers.FmtString("JUNEAU") + params.Body.PickupAddress.State = handlers.FmtString("AK") + params.Body.PickupAddress.PostalCode = handlers.FmtString("99801") + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.CreateMTOShipmentOK{}, response) + }) + + suite.Run("POST success - 200 - valid HI address if FF ON", func() { + // Under Test: CreateMTOShipment handler code + // Setup: Create an mto shipment on an available move + // Expected: Success, valid HI address HI FF ON + handler, move := setupTestData(false, true) + req := httptest.NewRequest("POST", "/mto-shipments", nil) + + params := mtoshipmentops.CreateMTOShipmentParams{ + HTTPRequest: req, + Body: &primev3messages.CreateMTOShipment{ + MoveTaskOrderID: handlers.FmtUUID(move.ID), + Agents: nil, + CustomerRemarks: nil, + PointOfContact: "John Doe", + PrimeEstimatedWeight: handlers.FmtInt64(1200), + RequestedPickupDate: handlers.FmtDatePtr(models.TimePointer(time.Now())), + ShipmentType: primev3messages.NewMTOShipmentType(primev3messages.MTOShipmentTypeHHG), + PickupAddress: struct{ primev3messages.Address }{pickupAddress}, + SecondaryPickupAddress: struct{ primev3messages.Address }{secondaryPickupAddress}, + TertiaryPickupAddress: struct{ primev3messages.Address }{tertiaryPickupAddress}, + DestinationAddress: struct{ primev3messages.Address }{destinationAddress}, + SecondaryDestinationAddress: struct{ primev3messages.Address }{secondaryDestinationAddress}, + TertiaryDestinationAddress: struct{ primev3messages.Address }{tertiaryDestinationAddress}, + }, + } + + // setting the HI flag to false and use a valid address + handlerConfig := suite.HandlerConfig() + + expectedFeatureFlag := services.FeatureFlag{ + Key: "enable_hawaii", + Match: true, + } + + mockFeatureFlagFetcher := &mocks.FeatureFlagFetcher{} + mockFeatureFlagFetcher.On("GetBooleanFlag", + mock.Anything, // context.Context + mock.Anything, // *zap.Logger + mock.AnythingOfType("string"), // entityID (userID) + mock.AnythingOfType("string"), // key + mock.Anything, // flagContext (map[string]string) + ).Return(expectedFeatureFlag, nil) + handlerConfig.SetFeatureFlagFetcher(mockFeatureFlagFetcher) + mockFeatureFlagFetcher.On("GetBooleanFlagForUser", + mock.Anything, + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("string"), + mock.Anything, + ).Return(expectedFeatureFlag, nil) + handlerConfig.SetFeatureFlagFetcher(mockFeatureFlagFetcher) + handler.HandlerConfig = handlerConfig + params.Body.PickupAddress.City = handlers.FmtString("HONOLULU") + params.Body.PickupAddress.State = handlers.FmtString("HI") + params.Body.PickupAddress.PostalCode = handlers.FmtString("96835") + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.CreateMTOShipmentOK{}, response) + }) + + suite.Run("Failure POST - 422 - Invalid address (PPM)", func() { + // Under Test: CreateMTOShipment handler code + // Setup: Create a PPM shipment on an available move + // Expected: Failure, returns an invalid address error + handler, move := setupTestDataWithoutFF() + req := httptest.NewRequest("POST", "/mto-shipments", nil) + + counselorRemarks := "Some counselor remarks" + expectedDepartureDate := time.Now().AddDate(0, 0, 10) + sitExpected := true + sitLocation := primev3messages.SITLocationTypeDESTINATION + sitEstimatedWeight := unit.Pound(1500) + sitEstimatedEntryDate := expectedDepartureDate.AddDate(0, 0, 5) + sitEstimatedDepartureDate := sitEstimatedEntryDate.AddDate(0, 0, 20) + estimatedWeight := unit.Pound(3200) + hasProGear := true + proGearWeight := unit.Pound(400) + spouseProGearWeight := unit.Pound(250) + estimatedIncentive := 123456 + sitEstimatedCost := 67500 + + address1 := models.Address{ + StreetAddress1: "some address", + City: "Bad City", + State: "CA", + PostalCode: "90210", + } + + expectedPickupAddress := address1 + pickupAddress = primev3messages.Address{ + City: &expectedPickupAddress.City, + PostalCode: &expectedPickupAddress.PostalCode, + State: &expectedPickupAddress.State, + StreetAddress1: &expectedPickupAddress.StreetAddress1, + StreetAddress2: expectedPickupAddress.StreetAddress2, + StreetAddress3: expectedPickupAddress.StreetAddress3, + } + + expectedDestinationAddress := address1 + destinationAddress = primev3messages.Address{ + City: &expectedDestinationAddress.City, + PostalCode: &expectedDestinationAddress.PostalCode, + State: &expectedDestinationAddress.State, + StreetAddress1: &expectedDestinationAddress.StreetAddress1, + StreetAddress2: expectedDestinationAddress.StreetAddress2, + StreetAddress3: expectedDestinationAddress.StreetAddress3, + } + ppmDestinationAddress = primev3messages.PPMDestinationAddress{ + City: &expectedDestinationAddress.City, + PostalCode: &expectedDestinationAddress.PostalCode, + State: &expectedDestinationAddress.State, + StreetAddress1: &expectedDestinationAddress.StreetAddress1, + StreetAddress2: expectedDestinationAddress.StreetAddress2, + StreetAddress3: expectedDestinationAddress.StreetAddress3, + } + + params := mtoshipmentops.CreateMTOShipmentParams{ + HTTPRequest: req, + Body: &primev3messages.CreateMTOShipment{ + MoveTaskOrderID: handlers.FmtUUID(move.ID), + ShipmentType: primev3messages.NewMTOShipmentType(primev3messages.MTOShipmentTypePPM), + CounselorRemarks: &counselorRemarks, + PpmShipment: &primev3messages.CreatePPMShipment{ + ExpectedDepartureDate: handlers.FmtDate(expectedDepartureDate), + PickupAddress: struct{ primev3messages.Address }{pickupAddress}, + SecondaryPickupAddress: struct{ primev3messages.Address }{secondaryPickupAddress}, + TertiaryPickupAddress: struct{ primev3messages.Address }{tertiaryPickupAddress}, + DestinationAddress: struct { + primev3messages.PPMDestinationAddress + }{ppmDestinationAddress}, + SecondaryDestinationAddress: struct{ primev3messages.Address }{secondaryDestinationAddress}, + TertiaryDestinationAddress: struct{ primev3messages.Address }{tertiaryDestinationAddress}, + SitExpected: &sitExpected, + SitLocation: &sitLocation, + SitEstimatedWeight: handlers.FmtPoundPtr(&sitEstimatedWeight), + SitEstimatedEntryDate: handlers.FmtDate(sitEstimatedEntryDate), + SitEstimatedDepartureDate: handlers.FmtDate(sitEstimatedDepartureDate), + EstimatedWeight: handlers.FmtPoundPtr(&estimatedWeight), + HasProGear: &hasProGear, + ProGearWeight: handlers.FmtPoundPtr(&proGearWeight), + SpouseProGearWeight: handlers.FmtPoundPtr(&spouseProGearWeight), + }, + }, + } + + ppmEstimator.On("EstimateIncentiveWithDefaultChecks", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(models.CentPointer(unit.Cents(estimatedIncentive)), models.CentPointer(unit.Cents(sitEstimatedCost)), nil).Once() + + ppmEstimator.On("MaxIncentive", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("models.PPMShipment"), + mock.AnythingOfType("*models.PPMShipment")). + Return(nil, nil) + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.CreateMTOShipmentUnprocessableEntity{}, response) + }) } func GetTestAddress() primev3messages.Address { newAddress := factory.BuildAddress(nil, []factory.Customization{ diff --git a/pkg/handlers/primeapiv3/payloads/model_to_payload.go b/pkg/handlers/primeapiv3/payloads/model_to_payload.go index 971db88dc97..43b8743b18c 100644 --- a/pkg/handlers/primeapiv3/payloads/model_to_payload.go +++ b/pkg/handlers/primeapiv3/payloads/model_to_payload.go @@ -760,6 +760,20 @@ func MTOServiceItem(mtoServiceItem *models.MTOServiceItem) primev3messages.MTOSe SitHHGActualOrigin: Address(mtoServiceItem.SITOriginHHGActualAddress), SitHHGOriginalOrigin: Address(mtoServiceItem.SITOriginHHGOriginalAddress), } + case models.ReServiceCodeIOFSIT, models.ReServiceCodeIOASIT, models.ReServiceCodeIOPSIT, models.ReServiceCodeIOSFSC: + var sitDepartureDate time.Time + if mtoServiceItem.SITDepartureDate != nil { + sitDepartureDate = *mtoServiceItem.SITDepartureDate + } + payload = &primev3messages.MTOServiceItemInternationalOriginSIT{ + ReServiceCode: handlers.FmtString(string(mtoServiceItem.ReService.Code)), + Reason: mtoServiceItem.Reason, + SitDepartureDate: handlers.FmtDate(sitDepartureDate), + SitEntryDate: handlers.FmtDatePtr(mtoServiceItem.SITEntryDate), + SitPostalCode: mtoServiceItem.SITPostalCode, + SitHHGActualOrigin: Address(mtoServiceItem.SITOriginHHGActualAddress), + SitHHGOriginalOrigin: Address(mtoServiceItem.SITOriginHHGOriginalAddress), + } case models.ReServiceCodeDDFSIT, models.ReServiceCodeDDASIT, models.ReServiceCodeDDDSIT, models.ReServiceCodeDDSFSC: var sitDepartureDate, firstAvailableDeliveryDate1, firstAvailableDeliveryDate2, dateOfContact1, dateOfContact2 time.Time var timeMilitary1, timeMilitary2 *string @@ -804,7 +818,50 @@ func MTOServiceItem(mtoServiceItem *models.MTOServiceItem) primev3messages.MTOSe SitCustomerContacted: handlers.FmtDatePtr(mtoServiceItem.SITCustomerContacted), SitRequestedDelivery: handlers.FmtDatePtr(mtoServiceItem.SITRequestedDelivery), } + case models.ReServiceCodeIDFSIT, models.ReServiceCodeIDASIT, models.ReServiceCodeIDDSIT, models.ReServiceCodeIDSFSC: + var sitDepartureDate, firstAvailableDeliveryDate1, firstAvailableDeliveryDate2, dateOfContact1, dateOfContact2 time.Time + var timeMilitary1, timeMilitary2 *string + + if mtoServiceItem.SITDepartureDate != nil { + sitDepartureDate = *mtoServiceItem.SITDepartureDate + } + + firstContact := GetCustomerContact(mtoServiceItem.CustomerContacts, models.CustomerContactTypeFirst) + secondContact := GetCustomerContact(mtoServiceItem.CustomerContacts, models.CustomerContactTypeSecond) + timeMilitary1 = &firstContact.TimeMilitary + timeMilitary2 = &secondContact.TimeMilitary + + if !firstContact.DateOfContact.IsZero() { + dateOfContact1 = firstContact.DateOfContact + } + + if !secondContact.DateOfContact.IsZero() { + dateOfContact2 = secondContact.DateOfContact + } + + if !firstContact.FirstAvailableDeliveryDate.IsZero() { + firstAvailableDeliveryDate1 = firstContact.FirstAvailableDeliveryDate + } + if !secondContact.FirstAvailableDeliveryDate.IsZero() { + firstAvailableDeliveryDate2 = secondContact.FirstAvailableDeliveryDate + } + + payload = &primev3messages.MTOServiceItemInternationalDestSIT{ + ReServiceCode: handlers.FmtString(string(mtoServiceItem.ReService.Code)), + Reason: mtoServiceItem.Reason, + DateOfContact1: handlers.FmtDate(dateOfContact1), + TimeMilitary1: handlers.FmtStringPtrNonEmpty(timeMilitary1), + FirstAvailableDeliveryDate1: handlers.FmtDate(firstAvailableDeliveryDate1), + DateOfContact2: handlers.FmtDate(dateOfContact2), + TimeMilitary2: handlers.FmtStringPtrNonEmpty(timeMilitary2), + FirstAvailableDeliveryDate2: handlers.FmtDate(firstAvailableDeliveryDate2), + SitDepartureDate: handlers.FmtDate(sitDepartureDate), + SitEntryDate: handlers.FmtDatePtr(mtoServiceItem.SITEntryDate), + SitDestinationFinalAddress: Address(mtoServiceItem.SITDestinationFinalAddress), + SitCustomerContacted: handlers.FmtDatePtr(mtoServiceItem.SITCustomerContacted), + SitRequestedDelivery: handlers.FmtDatePtr(mtoServiceItem.SITRequestedDelivery), + } case models.ReServiceCodeDCRT, models.ReServiceCodeDUCRT: item := GetDimension(mtoServiceItem.Dimensions, models.DimensionTypeItem) crate := GetDimension(mtoServiceItem.Dimensions, models.DimensionTypeCrate) @@ -868,12 +925,25 @@ 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), ActualWeight: handlers.FmtPoundPtr(mtoServiceItem.ActualWeight), } + + case models.ReServiceCodePODFSC, models.ReServiceCodePOEFSC: + var portCode string + if mtoServiceItem.POELocation != nil { + portCode = mtoServiceItem.POELocation.Port.PortCode + } else if mtoServiceItem.PODLocation != nil { + portCode = mtoServiceItem.PODLocation.Port.PortCode + } + payload = &primev3messages.MTOServiceItemInternationalFuelSurcharge{ + ReServiceCode: string(mtoServiceItem.ReService.Code), + PortCode: portCode, + } + case models.ReServiceCodeIDSHUT, models.ReServiceCodeIOSHUT: shuttleSI := &primev3messages.MTOServiceItemInternationalShuttle{ ReServiceCode: handlers.FmtString(string(mtoServiceItem.ReService.Code)), @@ -900,19 +970,6 @@ func MTOServiceItem(mtoServiceItem *models.MTOServiceItem) primev3messages.MTOSe } payload = shuttleSI - - case models.ReServiceCodePODFSC, models.ReServiceCodePOEFSC: - var portCode string - if mtoServiceItem.POELocation != nil { - portCode = mtoServiceItem.POELocation.Port.PortCode - } else if mtoServiceItem.PODLocation != nil { - portCode = mtoServiceItem.PODLocation.Port.PortCode - } - payload = &primev3messages.MTOServiceItemInternationalFuelSurcharge{ - ReServiceCode: string(mtoServiceItem.ReService.Code), - PortCode: portCode, - } - default: // otherwise, basic service item payload = &primev3messages.MTOServiceItemBasic{ diff --git a/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go b/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go index a5a2d1d8d15..3de4cb386fb 100644 --- a/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go +++ b/pkg/handlers/primeapiv3/payloads/model_to_payload_test.go @@ -1002,6 +1002,72 @@ func (suite *PayloadsSuite) TestMTOServiceItemDestSIT() { suite.Equal(finalAddress.PostalCode, *destSIT.SitDestinationFinalAddress.PostalCode) suite.Equal(mtoShipmentID.String(), destSIT.MtoShipmentID().String()) } + +func (suite *PayloadsSuite) TestMTOServiceItemInternationalDestSIT() { + reServiceCode := models.ReServiceCodeIDFSIT + reason := "reason" + dateOfContact1 := time.Now() + timeMilitary1 := "1500Z" + firstAvailableDeliveryDate1 := dateOfContact1.AddDate(0, 0, 10) + dateOfContact2 := time.Now().AddDate(0, 0, 5) + timeMilitary2 := "1300Z" + firstAvailableDeliveryDate2 := dateOfContact2.AddDate(0, 0, 10) + sitDepartureDate := time.Now().AddDate(0, 1, 0) + sitEntryDate := time.Now().AddDate(0, 0, -30) + finalAddress := models.Address{ + StreetAddress1: "dummyStreet", + City: "dummyCity", + State: "FL", + PostalCode: "55555", + } + mtoShipmentID := uuid.Must(uuid.NewV4()) + + mtoServiceItemDestSIT := &models.MTOServiceItem{ + ID: uuid.Must(uuid.NewV4()), + ReService: models.ReService{Code: reServiceCode}, + Reason: &reason, + SITDepartureDate: &sitDepartureDate, + SITEntryDate: &sitEntryDate, + SITDestinationFinalAddress: &finalAddress, + MTOShipmentID: &mtoShipmentID, + CustomerContacts: models.MTOServiceItemCustomerContacts{ + models.MTOServiceItemCustomerContact{ + DateOfContact: dateOfContact1, + TimeMilitary: timeMilitary1, + FirstAvailableDeliveryDate: firstAvailableDeliveryDate1, + Type: models.CustomerContactTypeFirst, + }, + models.MTOServiceItemCustomerContact{ + DateOfContact: dateOfContact2, + TimeMilitary: timeMilitary2, + FirstAvailableDeliveryDate: firstAvailableDeliveryDate2, + Type: models.CustomerContactTypeSecond, + }, + }, + } + + resultDestSIT := MTOServiceItem(mtoServiceItemDestSIT) + suite.NotNil(resultDestSIT) + destSIT, ok := resultDestSIT.(*primev3messages.MTOServiceItemInternationalDestSIT) + suite.True(ok) + + suite.Equal(string(reServiceCode), string(*destSIT.ReServiceCode)) + suite.Equal(reason, *destSIT.Reason) + suite.Equal(strfmt.Date(sitDepartureDate).String(), destSIT.SitDepartureDate.String()) + suite.Equal(strfmt.Date(sitEntryDate).String(), destSIT.SitEntryDate.String()) + suite.Equal(strfmt.Date(dateOfContact1).String(), destSIT.DateOfContact1.String()) + suite.Equal(timeMilitary1, *destSIT.TimeMilitary1) + suite.Equal(strfmt.Date(firstAvailableDeliveryDate1).String(), destSIT.FirstAvailableDeliveryDate1.String()) + suite.Equal(strfmt.Date(dateOfContact2).String(), destSIT.DateOfContact2.String()) + suite.Equal(timeMilitary2, *destSIT.TimeMilitary2) + suite.Equal(strfmt.Date(firstAvailableDeliveryDate2).String(), destSIT.FirstAvailableDeliveryDate2.String()) + suite.Equal(finalAddress.StreetAddress1, *destSIT.SitDestinationFinalAddress.StreetAddress1) + suite.Equal(finalAddress.City, *destSIT.SitDestinationFinalAddress.City) + suite.Equal(finalAddress.State, *destSIT.SitDestinationFinalAddress.State) + suite.Equal(finalAddress.PostalCode, *destSIT.SitDestinationFinalAddress.PostalCode) + suite.Equal(mtoShipmentID.String(), destSIT.MtoShipmentID().String()) +} + func (suite *PayloadsSuite) TestMTOServiceItemDCRT() { reServiceCode := models.ReServiceCodeDCRT reason := "reason" @@ -1147,7 +1213,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..6c1baafd4c1 100644 --- a/pkg/handlers/primeapiv3/payloads/payload_to_model.go +++ b/pkg/handlers/primeapiv3/payloads/payload_to_model.go @@ -727,7 +727,44 @@ func MTOServiceItemModel(mtoServiceItem primev3messages.MTOServiceItem) (*models if model.SITOriginHHGActualAddress != nil { model.SITOriginHHGActualAddressID = &model.SITOriginHHGActualAddress.ID } + case primev3messages.MTOServiceItemModelTypeMTOServiceItemInternationalOriginSIT: + originsit := mtoServiceItem.(*primev3messages.MTOServiceItemInternationalOriginSIT) + + if originsit.ReServiceCode != nil { + model.ReService.Code = models.ReServiceCode(*originsit.ReServiceCode) + } + + model.Reason = originsit.Reason + // Check for reason required field on a DDFSIT + if model.ReService.Code == models.ReServiceCodeDOASIT { + reasonVerrs := validateReasonInternationalOriginSIT(*originsit) + + if reasonVerrs.HasAny() { + return nil, reasonVerrs + } + } + + if model.ReService.Code == models.ReServiceCodeDOFSIT { + reasonVerrs := validateReasonInternationalOriginSIT(*originsit) + + if reasonVerrs.HasAny() { + return nil, reasonVerrs + } + } + + sitEntryDate := handlers.FmtDatePtrToPopPtr(originsit.SitEntryDate) + + if sitEntryDate != nil { + model.SITEntryDate = sitEntryDate + } + + model.SITPostalCode = originsit.SitPostalCode + + model.SITOriginHHGActualAddress = AddressModel(originsit.SitHHGActualOrigin) + if model.SITOriginHHGActualAddress != nil { + model.SITOriginHHGActualAddressID = &model.SITOriginHHGActualAddress.ID + } case primev3messages.MTOServiceItemModelTypeMTOServiceItemDestSIT: destsit := mtoServiceItem.(*primev3messages.MTOServiceItemDestSIT) @@ -782,6 +819,64 @@ func MTOServiceItemModel(mtoServiceItem primev3messages.MTOServiceItem) (*models model.SITDepartureDate = handlers.FmtDatePtrToPopPtr(destsit.SitDepartureDate) } + model.SITDestinationFinalAddress = AddressModel(destsit.SitDestinationFinalAddress) + if model.SITDestinationFinalAddress != nil { + model.SITDestinationFinalAddressID = &model.SITDestinationFinalAddress.ID + } + case primev3messages.MTOServiceItemModelTypeMTOServiceItemInternationalDestSIT: + destsit := mtoServiceItem.(*primev3messages.MTOServiceItemInternationalDestSIT) + + if destsit.ReServiceCode != nil { + model.ReService.Code = models.ReServiceCode(*destsit.ReServiceCode) + + } + + model.Reason = destsit.Reason + sitEntryDate := handlers.FmtDatePtrToPopPtr(destsit.SitEntryDate) + + // Check for required fields on a IDFSIT + if model.ReService.Code == models.ReServiceCodeIDFSIT { + verrs := validateIDFSITForCreate(*destsit) + reasonVerrs := validateReasonInternationalDestSIT(*destsit) + + if verrs.HasAny() { + return nil, verrs + } + + if reasonVerrs.HasAny() { + return nil, reasonVerrs + } + } + + var customerContacts models.MTOServiceItemCustomerContacts + + if destsit.TimeMilitary1 != nil && destsit.FirstAvailableDeliveryDate1 != nil && destsit.DateOfContact1 != nil { + customerContacts = append(customerContacts, models.MTOServiceItemCustomerContact{ + Type: models.CustomerContactTypeFirst, + DateOfContact: time.Time(*destsit.DateOfContact1), + TimeMilitary: *destsit.TimeMilitary1, + FirstAvailableDeliveryDate: time.Time(*destsit.FirstAvailableDeliveryDate1), + }) + } + if destsit.TimeMilitary2 != nil && destsit.FirstAvailableDeliveryDate2 != nil && destsit.DateOfContact2 != nil { + customerContacts = append(customerContacts, models.MTOServiceItemCustomerContact{ + Type: models.CustomerContactTypeSecond, + DateOfContact: time.Time(*destsit.DateOfContact2), + TimeMilitary: *destsit.TimeMilitary2, + FirstAvailableDeliveryDate: time.Time(*destsit.FirstAvailableDeliveryDate2), + }) + } + + model.CustomerContacts = customerContacts + + if sitEntryDate != nil { + model.SITEntryDate = sitEntryDate + } + + if destsit.SitDepartureDate != nil { + model.SITDepartureDate = handlers.FmtDatePtrToPopPtr(destsit.SitDepartureDate) + } + model.SITDestinationFinalAddress = AddressModel(destsit.SitDestinationFinalAddress) if model.SITDestinationFinalAddress != nil { model.SITDestinationFinalAddressID = &model.SITDestinationFinalAddress.ID @@ -795,6 +890,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 @@ -1089,6 +1192,31 @@ func validateDDFSITForCreate(m primev3messages.MTOServiceItemDestSIT) *validate. return verrs } +// validateIDFSITForCreate validates IDFSIT service item has all required fields +func validateIDFSITForCreate(m primev3messages.MTOServiceItemInternationalDestSIT) *validate.Errors { + verrs := validate.NewErrors() + + if m.FirstAvailableDeliveryDate1 == nil && m.DateOfContact1 != nil && m.TimeMilitary1 != nil { + verrs.Add("firstAvailableDeliveryDate1", "firstAvailableDeliveryDate1, dateOfContact1, and timeMilitary1 must be provided together in body.") + } + if m.DateOfContact1 == nil && m.TimeMilitary1 != nil && m.FirstAvailableDeliveryDate1 != nil { + verrs.Add("DateOfContact1", "dateOfContact1, timeMilitary1, and firstAvailableDeliveryDate1 must be provided together in body.") + } + if m.TimeMilitary1 == nil && m.DateOfContact1 != nil && m.FirstAvailableDeliveryDate1 != nil { + verrs.Add("timeMilitary1", "timeMilitary1, dateOfContact1, and firstAvailableDeliveryDate1 must be provided together in body.") + } + if m.FirstAvailableDeliveryDate2 == nil && m.DateOfContact2 != nil && m.TimeMilitary2 != nil { + verrs.Add("firstAvailableDeliveryDate2", "firstAvailableDeliveryDate2, dateOfContact2, and timeMilitary2 must be provided together in body.") + } + if m.DateOfContact2 == nil && m.TimeMilitary2 != nil && m.FirstAvailableDeliveryDate2 != nil { + verrs.Add("DateOfContact1", "dateOfContact2, firstAvailableDeliveryDate2, and timeMilitary2 must be provided together in body.") + } + if m.TimeMilitary2 == nil && m.DateOfContact2 != nil && m.FirstAvailableDeliveryDate2 != nil { + verrs.Add("timeMilitary2", "timeMilitary2, firstAvailableDeliveryDate2, and dateOfContact2 must be provided together in body.") + } + return verrs +} + // validateDestSITForUpdate validates DDDSIT service item has all required fields func validateDestSITForUpdate(m primev3messages.UpdateMTOServiceItemSIT) *validate.Errors { verrs := validate.NewErrors() @@ -1124,6 +1252,16 @@ func validateReasonDestSIT(m primev3messages.MTOServiceItemDestSIT) *validate.Er return verrs } +// validateReasonInternationalDestSIT validates that International Destination SIT service items have required Reason field +func validateReasonInternationalDestSIT(m primev3messages.MTOServiceItemInternationalDestSIT) *validate.Errors { + verrs := validate.NewErrors() + + if m.Reason == nil || m.Reason == models.StringPointer("") { + verrs.Add("reason", "reason is required in body.") + } + return verrs +} + // validateReasonOriginSIT validates that Origin SIT service items have required Reason field func validateReasonOriginSIT(m primev3messages.MTOServiceItemOriginSIT) *validate.Errors { verrs := validate.NewErrors() @@ -1134,6 +1272,16 @@ func validateReasonOriginSIT(m primev3messages.MTOServiceItemOriginSIT) *validat return verrs } +// validateReasonInternationalOriginSIT validates that International Origin SIT service items have required Reason field +func validateReasonInternationalOriginSIT(m primev3messages.MTOServiceItemInternationalOriginSIT) *validate.Errors { + verrs := validate.NewErrors() + + if m.Reason == nil || m.Reason == models.StringPointer("") { + verrs.Add("reason", "reason is required in body.") + } + return verrs +} + // validateBoatShipmentType validates that the shipment type is a valid boat type, and is not nil. func validateBoatShipmentType(s primev3messages.MTOShipmentType) *validate.Errors { verrs := validate.NewErrors() diff --git a/pkg/handlers/primeapiv3/payloads/payload_to_model_test.go b/pkg/handlers/primeapiv3/payloads/payload_to_model_test.go index fd9430379f0..2d99bd58d77 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, @@ -378,6 +378,32 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { suite.Equal(destUSPRCID.String(), returnedModel.SITDestinationFinalAddress.UsPostRegionCityID.String()) }) + suite.Run("Success - Returns international SIT destination service item model", func() { + destSITServiceItem := &primev3messages.MTOServiceItemInternationalDestSIT{ + ReServiceCode: &destServiceCode, + FirstAvailableDeliveryDate1: &destDate, + FirstAvailableDeliveryDate2: &destDate, + DateOfContact1: &destDate, + DateOfContact2: &destDate, + TimeMilitary1: &destTime, + TimeMilitary2: &destTime, + SitDestinationFinalAddress: &sitFinalDestAddress, + Reason: &destReason, + } + + destSITServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) + destSITServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + returnedModel, verrs := MTOServiceItemModel(destSITServiceItem) + + suite.NoVerrs(verrs) + suite.Equal(moveTaskOrderIDField.String(), returnedModel.MoveTaskOrderID.String()) + suite.Equal(mtoShipmentIDField.String(), returnedModel.MTOShipmentID.String()) + suite.Equal(models.ReServiceCodeDDFSIT, returnedModel.ReService.Code) + suite.Equal(destPostalCode, returnedModel.SITDestinationFinalAddress.PostalCode) + suite.Equal(destStreet, returnedModel.SITDestinationFinalAddress.StreetAddress1) + suite.Equal(destUSPRCID.String(), returnedModel.SITDestinationFinalAddress.UsPostRegionCityID.String()) + }) + suite.Run("Success - Returns SIT destination service item model without customer contact fields", func() { destSITServiceItem := &primev3messages.MTOServiceItemDestSIT{ ReServiceCode: &destServiceCode, @@ -398,6 +424,27 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { suite.Equal(destUSPRCID.String(), returnedModel.SITDestinationFinalAddress.UsPostRegionCityID.String()) suite.Equal(destReason, *returnedModel.Reason) }) + + suite.Run("Success - Returns international SIT destination service item model without customer contact fields", func() { + destSITServiceItem := &primev3messages.MTOServiceItemInternationalDestSIT{ + ReServiceCode: &destServiceCode, + SitDestinationFinalAddress: &sitFinalDestAddress, + Reason: &destReason, + } + + destSITServiceItem.SetMoveTaskOrderID(handlers.FmtUUID(moveTaskOrderIDField)) + destSITServiceItem.SetMtoShipmentID(*mtoShipmentIDString) + returnedModel, verrs := MTOServiceItemModel(destSITServiceItem) + + suite.NoVerrs(verrs) + suite.Equal(moveTaskOrderIDField.String(), returnedModel.MoveTaskOrderID.String()) + suite.Equal(mtoShipmentIDField.String(), returnedModel.MTOShipmentID.String()) + suite.Equal(models.ReServiceCodeDDFSIT, returnedModel.ReService.Code) + suite.Equal(destPostalCode, returnedModel.SITDestinationFinalAddress.PostalCode) + suite.Equal(destStreet, returnedModel.SITDestinationFinalAddress.StreetAddress1) + suite.Equal(destUSPRCID.String(), returnedModel.SITDestinationFinalAddress.UsPostRegionCityID.String()) + suite.Equal(destReason, *returnedModel.Reason) + }) } func (suite *PayloadsSuite) TestReweighModelFromUpdate() { @@ -724,6 +771,23 @@ func (suite *PayloadsSuite) TestValidateReasonOriginSIT() { verrs := validateReasonOriginSIT(mtoServiceItemOriginSIT) suite.True(verrs.HasAny()) }) + + suite.Run("Reason provided - international", func() { + reason := "reason" + mtoServiceItemOriginSIT := primev3messages.MTOServiceItemInternationalOriginSIT{ + Reason: &reason, + } + + verrs := validateReasonInternationalOriginSIT(mtoServiceItemOriginSIT) + suite.False(verrs.HasAny()) + }) + + suite.Run("No reason provided - international", func() { + mtoServiceItemOriginSIT := primev3messages.MTOServiceItemInternationalOriginSIT{} + + verrs := validateReasonInternationalOriginSIT(mtoServiceItemOriginSIT) + suite.True(verrs.HasAny()) + }) } func (suite *PayloadsSuite) TestShipmentAddressUpdateModel() { diff --git a/pkg/handlers/routing/base_routing_suite.go b/pkg/handlers/routing/base_routing_suite.go index 23e538792b7..77049e33664 100644 --- a/pkg/handlers/routing/base_routing_suite.go +++ b/pkg/handlers/routing/base_routing_suite.go @@ -85,6 +85,7 @@ func (suite *BaseRoutingSuite) RoutingConfig() *Config { handlerConfig := suite.BaseHandlerTestSuite.HandlerConfig() handlerConfig.SetAppNames(handlers.ApplicationTestServername()) handlerConfig.SetNotificationSender(suite.TestNotificationSender()) + handlerConfig.SetNotificationReceiver(suite.TestNotificationReceiver()) // Need this for any requests that will either retrieve or save files or their info. fakeS3 := storageTest.NewFakeS3Storage(true) diff --git a/pkg/handlers/routing/ghcapi_test/uploads_test.go b/pkg/handlers/routing/ghcapi_test/uploads_test.go new file mode 100644 index 00000000000..5eb27758d00 --- /dev/null +++ b/pkg/handlers/routing/ghcapi_test/uploads_test.go @@ -0,0 +1,85 @@ +package ghcapi_test + +import ( + "net/http" + "net/http/httptest" + + "github.com/transcom/mymove/pkg/factory" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/models/roles" + storageTest "github.com/transcom/mymove/pkg/storage/test" + "github.com/transcom/mymove/pkg/uploader" +) + +func (suite *GhcAPISuite) TestUploads() { + + suite.Run("Received status for upload, read tag without event queue", func() { + orders := factory.BuildOrder(suite.DB(), factory.GetTraitActiveServiceMemberUser(), nil) + uploadUser1 := factory.BuildUserUpload(suite.DB(), []factory.Customization{ + { + Model: orders.UploadedOrders, + LinkOnly: true, + }, + { + Model: models.Upload{ + Filename: "FileName", + Bytes: int64(15), + ContentType: uploader.FileTypePDF, + }, + }, + }, nil) + file := suite.Fixture("test.pdf") + _, err := suite.HandlerConfig().FileStorer().Store(uploadUser1.Upload.StorageKey, file.Data, "somehash", nil) + suite.NoError(err) + + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), factory.GetTraitActiveOfficeUser(), + []roles.RoleType{roles.RoleTypeTOO}) + req := suite.NewAuthenticatedOfficeRequest("GET", "/ghc/v1/uploads/"+uploadUser1.Upload.ID.String()+"/status", nil, officeUser) + rr := httptest.NewRecorder() + + suite.SetupSiteHandler().ServeHTTP(rr, req) + + suite.Equal(http.StatusOK, rr.Code) + suite.Equal("text/event-stream", rr.Header().Get("content-type")) + suite.Equal("id: 0\nevent: message\ndata: CLEAN\n\nid: 1\nevent: close\ndata: Connection closed\n\n", rr.Body.String()) + }) + + suite.Run("Received statuses for upload, receiving multiple statuses with event queue", func() { + orders := factory.BuildOrder(suite.DB(), factory.GetTraitActiveServiceMemberUser(), nil) + uploadUser1 := factory.BuildUserUpload(suite.DB(), []factory.Customization{ + { + Model: orders.UploadedOrders, + LinkOnly: true, + }, + { + Model: models.Upload{ + Filename: "FileName", + Bytes: int64(15), + ContentType: uploader.FileTypePDF, + }, + }, + }, nil) + file := suite.Fixture("test.pdf") + _, err := suite.HandlerConfig().FileStorer().Store(uploadUser1.Upload.StorageKey, file.Data, "somehash", nil) + suite.NoError(err) + + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), factory.GetTraitActiveOfficeUser(), + []roles.RoleType{roles.RoleTypeTOO}) + req := suite.NewAuthenticatedOfficeRequest("GET", "/ghc/v1/uploads/"+uploadUser1.Upload.ID.String()+"/status", nil, officeUser) + rr := httptest.NewRecorder() + + fakeS3, ok := suite.HandlerConfig().FileStorer().(*storageTest.FakeS3Storage) + suite.True(ok) + suite.NotNil(fakeS3, "FileStorer should be fakeS3") + + fakeS3.EmptyTags = true + suite.SetupSiteHandler().ServeHTTP(rr, req) + + suite.Equal(http.StatusOK, rr.Code) + suite.Equal("text/event-stream", rr.Header().Get("content-type")) + + suite.Contains(rr.Body.String(), "PROCESSING") + suite.Contains(rr.Body.String(), "CLEAN") + suite.Contains(rr.Body.String(), "Connection closed") + }) +} diff --git a/pkg/handlers/supportapi/api.go b/pkg/handlers/supportapi/api.go index f9970af0553..e60ec489162 100644 --- a/pkg/handlers/supportapi/api.go +++ b/pkg/handlers/supportapi/api.go @@ -95,7 +95,7 @@ func NewSupportAPIHandler(handlerConfig handlers.HandlerConfig) http.Handler { mtoserviceitem.NewMTOServiceItemCreator(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()), handlerConfig.HHGPlanner()), } - supportAPI.MtoServiceItemUpdateMTOServiceItemStatusHandler = UpdateMTOServiceItemStatusHandler{handlerConfig, mtoserviceitem.NewMTOServiceItemUpdater(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher)} + supportAPI.MtoServiceItemUpdateMTOServiceItemStatusHandler = UpdateMTOServiceItemStatusHandler{handlerConfig, mtoserviceitem.NewMTOServiceItemUpdater(handlerConfig.HHGPlanner(), queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer())} supportAPI.WebhookReceiveWebhookNotificationHandler = ReceiveWebhookNotificationHandler{handlerConfig} // Create TAC and LOA services diff --git a/pkg/handlers/supportapi/internal/payloads/model_to_payload.go b/pkg/handlers/supportapi/internal/payloads/model_to_payload.go index a43c59663a7..83d481d125a 100644 --- a/pkg/handlers/supportapi/internal/payloads/model_to_payload.go +++ b/pkg/handlers/supportapi/internal/payloads/model_to_payload.go @@ -351,12 +351,34 @@ 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), ActualWeight: handlers.FmtPoundPtr(mtoServiceItem.ActualWeight), } + case models.ReServiceCodeIDSHUT, models.ReServiceCodeIOSHUT: + market := models.MarketConus.FullString() + + if mtoServiceItem.ReService.Code == models.ReServiceCodeIOSHUT && mtoServiceItem.MTOShipment.PickupAddress != nil { + if *mtoServiceItem.MTOShipment.PickupAddress.IsOconus { + market = models.MarketOconus.FullString() + } + } + + if mtoServiceItem.ReService.Code == models.ReServiceCodeIDSHUT && mtoServiceItem.MTOShipment.DestinationAddress != nil { + if *mtoServiceItem.MTOShipment.DestinationAddress.IsOconus { + market = models.MarketOconus.FullString() + } + } + + payload = &supportmessages.MTOServiceItemInternationalShuttle{ + ReServiceCode: handlers.FmtString(string(mtoServiceItem.ReService.Code)), + Reason: mtoServiceItem.Reason, + EstimatedWeight: handlers.FmtPoundPtr(mtoServiceItem.EstimatedWeight), + ActualWeight: handlers.FmtPoundPtr(mtoServiceItem.ActualWeight), + Market: market, + } default: // otherwise, basic service item payload = &supportmessages.MTOServiceItemBasic{ diff --git a/pkg/handlers/supportapi/mto_service_item_test.go b/pkg/handlers/supportapi/mto_service_item_test.go index 0e3faa635cd..0837679cb04 100644 --- a/pkg/handlers/supportapi/mto_service_item_test.go +++ b/pkg/handlers/supportapi/mto_service_item_test.go @@ -22,6 +22,7 @@ import ( "github.com/transcom/mymove/pkg/models" routemocks "github.com/transcom/mymove/pkg/route/mocks" "github.com/transcom/mymove/pkg/services/address" + "github.com/transcom/mymove/pkg/services/ghcrateengine" moverouter "github.com/transcom/mymove/pkg/services/move" mtoserviceitem "github.com/transcom/mymove/pkg/services/mto_service_item" mtoshipment "github.com/transcom/mymove/pkg/services/mto_shipment" @@ -88,7 +89,7 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemStatusHandlerApproveSuccess() false, ).Return(400, nil) handler := UpdateMTOServiceItemStatusHandler{handlerConfig, - mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher), + mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()), } // CALL FUNCTION UNDER TEST @@ -146,7 +147,7 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemStatusHandlerRejectSuccess() false, ).Return(400, nil) handler := UpdateMTOServiceItemStatusHandler{handlerConfig, - mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher), + mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()), } // CALL FUNCTION UNDER TEST @@ -204,7 +205,7 @@ func (suite *HandlerSuite) TestUpdateMTOServiceItemStatusHandlerRejectionFailedN false, ).Return(400, nil) handler := UpdateMTOServiceItemStatusHandler{handlerConfig, - mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher), + mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()), } // CALL FUNCTION UNDER TEST diff --git a/pkg/iws/rbs_error_test.go b/pkg/iws/rbs_error_test.go new file mode 100644 index 00000000000..022ab8400b7 --- /dev/null +++ b/pkg/iws/rbs_error_test.go @@ -0,0 +1,15 @@ +package iws + +func (suite *iwsSuite) TestRbsError() { + data := ` + + 14030 + Problem with this argument: EMA_TX + ` + _, _, _, err := parseWkEmaResponse([]byte(data)) + suite.NotNil(err) + rbsError, ok := err.(*RbsError) + suite.True(ok) + suite.Equal(uint64(14030), rbsError.FaultCode) + suite.NotEmpty(rbsError.FaultMessage) +} diff --git a/pkg/models/address.go b/pkg/models/address.go index 29a6bedbcbb..b4ed2723750 100644 --- a/pkg/models/address.go +++ b/pkg/models/address.go @@ -9,6 +9,7 @@ import ( "github.com/gobuffalo/validate/v3" "github.com/gobuffalo/validate/v3/validators" "github.com/gofrs/uuid" + "github.com/pkg/errors" "go.uber.org/zap" "go.uber.org/zap/zapcore" @@ -146,6 +147,13 @@ func (a *Address) LineDisplayFormat() string { return fmt.Sprintf("%s%s%s, %s, %s %s", a.StreetAddress1, optionalStreetAddress2, optionalStreetAddress3, a.City, a.State, a.PostalCode) } +func (a *Address) IsAddressAlaska() (bool, error) { + if a == nil { + return false, errors.New("address is nil") + } + return a.State == "AK", nil +} + // NotImplementedCountryCode is the default for unimplemented country code lookup type NotImplementedCountryCode struct { message string diff --git a/pkg/models/address_test.go b/pkg/models/address_test.go index 9dfe5a7fa1c..de71c145f73 100644 --- a/pkg/models/address_test.go +++ b/pkg/models/address_test.go @@ -105,33 +105,6 @@ func (suite *ModelSuite) TestIsAddressOconusForAKState() { suite.Equal(true, result) } -func (suite *ModelSuite) TestAddressIsEmpty() { - suite.Run("empty whitespace address", func() { - testAddress := m.Address{ - StreetAddress1: " ", - State: " ", - PostalCode: " ", - } - suite.True(m.IsAddressEmpty(&testAddress)) - }) - suite.Run("empty n/a address", func() { - testAddress := m.Address{ - StreetAddress1: "n/a", - State: "n/a", - PostalCode: "n/a", - } - suite.True(m.IsAddressEmpty(&testAddress)) - }) - suite.Run("nonempty address", func() { - testAddress := m.Address{ - StreetAddress1: "street 1", - State: "state", - PostalCode: "90210", - } - suite.False(m.IsAddressEmpty(&testAddress)) - }) -} - func (suite *ModelSuite) TestAddressFormat() { country := factory.FetchOrBuildCountry(suite.DB(), nil, nil) newAddress := &m.Address{ @@ -196,6 +169,33 @@ func (suite *ModelSuite) TestPartialAddressFormat() { suite.Equal("street 1, city, state 90210", formattedAddress) } +func (suite *ModelSuite) TestAddressIsEmpty() { + suite.Run("empty whitespace address", func() { + testAddress := m.Address{ + StreetAddress1: " ", + State: " ", + PostalCode: " ", + } + suite.True(m.IsAddressEmpty(&testAddress)) + }) + suite.Run("empty n/a address", func() { + testAddress := m.Address{ + StreetAddress1: "n/a", + State: "n/a", + PostalCode: "n/a", + } + suite.True(m.IsAddressEmpty(&testAddress)) + }) + suite.Run("nonempty address", func() { + testAddress := m.Address{ + StreetAddress1: "street 1", + State: "state", + PostalCode: "90210", + } + suite.False(m.IsAddressEmpty(&testAddress)) + }) +} + func (suite *ModelSuite) Test_FetchDutyLocationGblocForAK() { setupDataForOconusDutyLocation := func(postalCode string) (m.OconusRateArea, m.UsPostRegionCity, m.DutyLocation) { usprc, err := m.FindByZipCode(suite.AppContextForTest().DB(), postalCode) @@ -385,3 +385,34 @@ func (suite *ModelSuite) Test_FetchDutyLocationGblocForAK() { suite.Equal(string(*gbloc), "MAPK") }) } + +func (suite *ModelSuite) TestIsAddressAlaska() { + var address *m.Address + bool1, err := address.IsAddressAlaska() + suite.Error(err) + suite.Equal("address is nil", err.Error()) + suite.Equal(false, bool1) + + address = &m.Address{ + StreetAddress1: "street 1", + StreetAddress2: m.StringPointer("street 2"), + StreetAddress3: m.StringPointer("street 3"), + City: "city", + PostalCode: "90210", + County: m.StringPointer("County"), + } + + bool2, err := address.IsAddressAlaska() + suite.NoError(err) + suite.Equal(m.BoolPointer(false), &bool2) + + address.State = "MT" + bool3, err := address.IsAddressAlaska() + suite.NoError(err) + suite.Equal(m.BoolPointer(false), &bool3) + + address.State = "AK" + bool4, err := address.IsAddressAlaska() + suite.NoError(err) + suite.Equal(m.BoolPointer(true), &bool4) +} diff --git a/pkg/models/document.go b/pkg/models/document.go index 6392434a6ef..d47e2544105 100644 --- a/pkg/models/document.go +++ b/pkg/models/document.go @@ -1,6 +1,7 @@ package models import ( + "fmt" "time" "github.com/gobuffalo/pop/v6" @@ -40,28 +41,66 @@ func (d *Document) Validate(_ *pop.Connection) (*validate.Errors, error) { } // FetchDocument returns a document if the user has access to that document -func FetchDocument(db *pop.Connection, session *auth.Session, id uuid.UUID, includeDeletedDocs bool) (Document, error) { - return fetchDocumentWithAccessibilityCheck(db, session, id, includeDeletedDocs, true) +func FetchDocument(db *pop.Connection, session *auth.Session, id uuid.UUID) (Document, error) { + return fetchDocumentWithAccessibilityCheck(db, session, id, true) } // FetchDocument returns a document regardless if user has access to that document -func FetchDocumentWithNoRestrictions(db *pop.Connection, session *auth.Session, id uuid.UUID, includeDeletedDocs bool) (Document, error) { - return fetchDocumentWithAccessibilityCheck(db, session, id, includeDeletedDocs, false) +func FetchDocumentWithNoRestrictions(db *pop.Connection, session *auth.Session, id uuid.UUID) (Document, error) { + return fetchDocumentWithAccessibilityCheck(db, session, id, false) } // FetchDocument returns a document if the user has access to that document -func fetchDocumentWithAccessibilityCheck(db *pop.Connection, session *auth.Session, id uuid.UUID, includeDeletedDocs bool, checkUserAccessiability bool) (Document, error) { +func fetchDocumentWithAccessibilityCheck(db *pop.Connection, session *auth.Session, id uuid.UUID, checkUserAccessiability bool) (Document, error) { var document Document + var uploads []Upload query := db.Q() + // Giving the cursors names in which they will be defined as after opened in the database function. + // Doing so we can reference the specific cursor we want by the defined name as opposed to , + // which causes syntax errors when used in the FETCH ALL IN query. + documentCursor := "documentcursor" + userUploadCursor := "useruploadcursor" + uploadCursor := "uploadcursor" - if !includeDeletedDocs { - query = query.Where("documents.deleted_at is null and u.deleted_at is null") + documentsQuery := `SELECT fetch_documents(?, ?, ?, ?);` + + err := query.RawQuery(documentsQuery, documentCursor, userUploadCursor, uploadCursor, id).Exec() + + if err != nil { + if errors.Cause(err).Error() == RecordNotFoundErrorString { + return Document{}, ErrFetchNotFound + } + // Otherwise, it's an unexpected err so we return that. + return Document{}, err + } + + // Since we know the name of the cursor we can fetch the specific one we are interested in + // using FETCH ALL IN and populate the appropriate model + fetchDocument := `FETCH ALL IN ` + documentCursor + `;` + fetchUserUploads := `FETCH ALL IN ` + userUploadCursor + `;` + fetchUploads := `FETCH ALL IN ` + uploadCursor + `;` + + err = query.RawQuery(fetchDocument).First(&document) + + if err != nil { + if errors.Cause(err).Error() == RecordNotFoundErrorString { + return Document{}, ErrFetchNotFound + } + // Otherwise, it's an unexpected err so we return that. + return Document{}, err + } + + err = query.RawQuery(fetchUserUploads).All(&document.UserUploads) + + if err != nil { + if errors.Cause(err).Error() == RecordNotFoundErrorString { + return Document{}, ErrFetchNotFound + } + // Otherwise, it's an unexpected err so we return that. + return Document{}, err } - err := query.Eager("UserUploads.Upload"). - LeftJoin("user_uploads as uu", "documents.id = uu.document_id"). - LeftJoin("uploads as u", "uu.upload_id = u.id"). - Find(&document, id) + err = query.RawQuery(fetchUploads).All(&uploads) if err != nil { if errors.Cause(err).Error() == RecordNotFoundErrorString { @@ -71,10 +110,37 @@ func fetchDocumentWithAccessibilityCheck(db *pop.Connection, session *auth.Sessi return Document{}, err } - // encountered issues trying to filter userUploads using pop. - // going with the option to filter userUploads after the query. - if !includeDeletedDocs { - document.UserUploads = document.UserUploads.FilterDeleted() + // We have an array of UserUploads inside Document model, to populate that Upload model we need to loop and apply + // the resulting uploads into the appropriate UserUpload.Upload model by matching the upload ids + for i := 0; i < len(document.UserUploads); i++ { + for j := 0; j < len(uploads); j++ { + if document.UserUploads[i].UploadID == uploads[j].ID { + document.UserUploads[i].Upload = uploads[j] + } + } + } + + // We close all the cursors we opened during the fetch_documents call + closeDocCursor := `CLOSE ` + documentCursor + `;` + closeUserCursor := `CLOSE ` + userUploadCursor + `;` + closeUploadCursor := `CLOSE ` + uploadCursor + `;` + + closeErr := query.RawQuery(closeDocCursor).Exec() + + if closeErr != nil { + return Document{}, fmt.Errorf("error closing documents cursor: %w", closeErr) + } + + closeErr = query.RawQuery(closeUserCursor).Exec() + + if closeErr != nil { + return Document{}, fmt.Errorf("error closing user uploads cursor: %w", closeErr) + } + + closeErr = query.RawQuery(closeUploadCursor).Exec() + + if closeErr != nil { + return Document{}, fmt.Errorf("error closing uploads cursor: %w", closeErr) } if checkUserAccessiability { diff --git a/pkg/models/document_test.go b/pkg/models/document_test.go index 19e4e21b8c2..d013e4ab802 100644 --- a/pkg/models/document_test.go +++ b/pkg/models/document_test.go @@ -64,7 +64,7 @@ func (suite *ModelSuite) TestFetchDocument() { t.Errorf("did not expect validation errors: %v", verrs) } - doc, _ := models.FetchDocument(suite.DB(), &session, document.ID, false) + doc, _ := models.FetchDocument(suite.DB(), &session, document.ID) suite.Equal(doc.ID, document.ID) suite.Equal(0, len(doc.UserUploads)) } @@ -103,16 +103,9 @@ func (suite *ModelSuite) TestFetchDeletedDocument() { t.Errorf("did not expect validation errors: %v", verrs) } - doc, _ := models.FetchDocument(suite.DB(), &session, document.ID, false) + doc, _ := models.FetchDocument(suite.DB(), &session, document.ID) - // fetches a nil document + // FetchDocument should not return the document since it was deleted suite.Equal(doc.ID, uuid.Nil) suite.Equal(doc.ServiceMemberID, uuid.Nil) - - doc2, _ := models.FetchDocument(suite.DB(), &session, document.ID, true) - - // fetches a nil document - suite.Equal(doc2.ID, document.ID) - suite.Equal(doc2.ServiceMemberID, serviceMember.ID) - suite.Equal(1, len(doc2.UserUploads)) } 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 236bef5af6b..afa078b2ad2 100644 --- a/pkg/models/move.go +++ b/pkg/models/move.go @@ -71,7 +71,7 @@ type Move struct { PPMType *string `db:"ppm_type"` MTOServiceItems MTOServiceItems `has_many:"mto_service_items" fk_id:"move_id"` PaymentRequests PaymentRequests `has_many:"payment_requests" fk_id:"move_id"` - MTOShipments MTOShipments `has_many:"mto_shipments" fk_id:"move_id"` + MTOShipments MTOShipments `json:"mto_shipments" has_many:"mto_shipments" fk_id:"move_id"` ReferenceID *string `db:"reference_id"` ServiceCounselingCompletedAt *time.Time `db:"service_counseling_completed_at"` PrimeCounselingCompletedAt *time.Time `db:"prime_counseling_completed_at"` @@ -98,11 +98,11 @@ type Move struct { SCAssignedID *uuid.UUID `json:"sc_assigned_id" db:"sc_assigned_id"` SCAssignedUser *OfficeUser `belongs_to:"office_users" fk_id:"sc_assigned_id"` TOOAssignedID *uuid.UUID `json:"too_assigned_id" db:"too_assigned_id"` - TOOAssignedUser *OfficeUser `belongs_to:"office_users" fk_id:"too_assigned_id"` + TOOAssignedUser *OfficeUser `json:"too_assigned" belongs_to:"office_users" fk_id:"too_assigned_id"` TIOAssignedID *uuid.UUID `json:"tio_assigned_id" db:"tio_assigned_id"` TIOAssignedUser *OfficeUser `belongs_to:"office_users" fk_id:"tio_assigned_id"` CounselingOfficeID *uuid.UUID `json:"counseling_transportation_office_id" db:"counseling_transportation_office_id"` - CounselingOffice *TransportationOffice `belongs_to:"transportation_offices" fk_id:"counseling_transportation_office_id"` + CounselingOffice *TransportationOffice `json:"counseling_transportation_office" belongs_to:"transportation_offices" fk_id:"counseling_transportation_office_id"` } type MoveWithEarliestDate struct { @@ -704,7 +704,6 @@ func GetTotalNetWeightForMove(m Move) unit.Pound { } } return totalNetWeight - } // gets total weight from all ppm and hhg shipments within a move diff --git a/pkg/models/mto_service_items.go b/pkg/models/mto_service_items.go index 42a749626ce..7948401087d 100644 --- a/pkg/models/mto_service_items.go +++ b/pkg/models/mto_service_items.go @@ -96,7 +96,9 @@ type MTOServiceItemSingle struct { SITEntryDate *time.Time `db:"sit_entry_date"` SITDepartureDate *time.Time `db:"sit_departure_date"` SITDestinationFinalAddressID *uuid.UUID `db:"sit_destination_final_address_id"` + SITDestinationFinalAddress *Address `belongs_to:"addresses" fk_id:"sit_destination_final_address_id"` SITOriginHHGOriginalAddressID *uuid.UUID `db:"sit_origin_hhg_original_address_id"` + SITDestinationOriginalAddress *Address `belongs_to:"addresses" fk_id:"sit_destination_original_address_id"` SITOriginHHGActualAddressID *uuid.UUID `db:"sit_origin_hhg_actual_address_id"` EstimatedWeight *unit.Pound `db:"estimated_weight"` ActualWeight *unit.Pound `db:"actual_weight"` @@ -152,22 +154,26 @@ func FetchRelatedDestinationSITServiceItems(tx *pop.Connection, mtoServiceItemID } func FetchServiceItem(db *pop.Connection, serviceItemID uuid.UUID) (MTOServiceItem, error) { - var serviceItem MTOServiceItem - err := db.Eager("SITDestinationOriginalAddress", - "SITDestinationFinalAddress", - "ReService", - "CustomerContacts", - "MTOShipment.PickupAddress", - "MTOShipment.DestinationAddress").Where("id = ?", serviceItemID).First(&serviceItem) - - if err != nil { - if errors.Cause(err).Error() == RecordNotFoundErrorString { - return MTOServiceItem{}, ErrFetchNotFound + if db != nil { + var serviceItem MTOServiceItem + err := db.Eager("SITDestinationOriginalAddress", + "SITDestinationFinalAddress", + "ReService", + "CustomerContacts", + "MTOShipment.PickupAddress", + "MTOShipment.DestinationAddress", + "Dimensions").Where("id = ?", serviceItemID).First(&serviceItem) + + if err != nil { + if errors.Cause(err).Error() == RecordNotFoundErrorString { + return MTOServiceItem{}, ErrFetchNotFound + } + return MTOServiceItem{}, err } - return MTOServiceItem{}, err + return serviceItem, nil + } else { + return MTOServiceItem{}, errors.New("db connection is nil; unable to fetch service item") } - - return serviceItem, nil } func FetchRelatedDestinationSITFuelCharge(tx *pop.Connection, mtoServiceItemID uuid.UUID) (MTOServiceItem, error) { @@ -214,6 +220,7 @@ type MTOServiceItemType struct { ServiceLocation *ServiceLocationType `json:"service_location"` POELocationID *uuid.UUID `json:"poe_location_id"` PODLocationID *uuid.UUID `json:"pod_location_id"` + ExternalCrate *bool `json:"external_crate"` } func (m MTOServiceItem) GetMTOServiceItemTypeFromServiceItem() MTOServiceItemType { @@ -250,6 +257,7 @@ func (m MTOServiceItem) GetMTOServiceItemTypeFromServiceItem() MTOServiceItemTyp ServiceLocation: m.ServiceLocation, POELocationID: m.POELocationID, PODLocationID: m.PODLocationID, + ExternalCrate: m.ExternalCrate, } } @@ -283,6 +291,7 @@ func (m MTOServiceItem) Value() (driver.Value, error) { var estimatedWeight int64 var actualWeight int64 var pricingEstimate int64 + var externalCrate bool if m.ID != uuid.Nil { id = m.ID.String() @@ -376,6 +385,10 @@ func (m MTOServiceItem) Value() (driver.Value, error) { standaloneCrate = *m.StandaloneCrate } + if m.ExternalCrate != nil { + externalCrate = *m.ExternalCrate + } + if m.LockedPriceCents != nil { lockedPriceCents = m.LockedPriceCents.Int64() } @@ -400,7 +413,7 @@ func (m MTOServiceItem) Value() (driver.Value, error) { pricingEstimate = m.PricingEstimate.Int64() } - s := fmt.Sprintf("(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%d,%d,%s,%s,%s,%t,%t,%s,%d,%d,%t,%d,%s,%s,%s,%s)", + s := fmt.Sprintf("(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%d,%d,%s,%s,%s,%t,%t,%s,%d,%d,%t,%d,%s,%s,%s,%s,%t)", id, moveTaskOrderID, mtoShipmentID, @@ -435,6 +448,7 @@ func (m MTOServiceItem) Value() (driver.Value, error) { poeLocationID, podLocationID, m.ReService.Code.String(), + externalCrate, ) return []byte(s), nil } diff --git a/pkg/models/mto_service_items_test.go b/pkg/models/mto_service_items_test.go index 8d55e1c4bea..00d028e3bf9 100644 --- a/pkg/models/mto_service_items_test.go +++ b/pkg/models/mto_service_items_test.go @@ -164,3 +164,55 @@ func (suite *ModelSuite) TestGetMTOServiceItemTypeFromServiceItem() { suite.NotNil(returnedShipment) }) } + +func (suite *ModelSuite) TestFetchServiceItem() { + suite.Run("successful fetch service item", func() { + move := factory.BuildMove(suite.DB(), nil, nil) + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + }, nil) + msServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeMS, + }, + }, + }, nil) + factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeCS, + }, + }, + }, nil) + serviceItem, fetchErr := models.FetchServiceItem(suite.DB(), msServiceItem.ID) + suite.NoError(fetchErr) + suite.NotNil(serviceItem) + }) + + suite.Run("failed fetch service item - db connection is nil", func() { + serviceItem, fetchErr := models.FetchServiceItem(nil, uuid.Must(uuid.NewV4())) + suite.Error(fetchErr) + suite.EqualError(fetchErr, "db connection is nil; unable to fetch service item") + suite.Empty(serviceItem) + }) + + suite.Run("failed fetch service item - record not found", func() { + nonExistentID := uuid.Must(uuid.NewV4()) + serviceItem, fetchErr := models.FetchServiceItem(suite.DB(), nonExistentID) + suite.Error(fetchErr) + suite.Equal(fetchErr, models.ErrFetchNotFound) + suite.Empty(serviceItem) + }) +} diff --git a/pkg/models/mto_shipments.go b/pkg/models/mto_shipments.go index b3c69cb52bb..1d7f6a2dada 100644 --- a/pkg/models/mto_shipments.go +++ b/pkg/models/mto_shipments.go @@ -48,6 +48,8 @@ var internationalAccessorialServiceItems = []ReServiceCode{ ReServiceCodeIDDSIT, ReServiceCodeIDSHUT, ReServiceCodeIOSHUT, + ReServiceCodeIOSFSC, + ReServiceCodeIDSFSC, } const ( @@ -118,74 +120,74 @@ const ( // MTOShipment is an object representing data for a move task order shipment type MTOShipment struct { - ID uuid.UUID `db:"id"` - MoveTaskOrder Move `belongs_to:"moves" fk_id:"move_id"` - MoveTaskOrderID uuid.UUID `db:"move_id"` - ScheduledPickupDate *time.Time `db:"scheduled_pickup_date"` - RequestedPickupDate *time.Time `db:"requested_pickup_date"` - RequestedDeliveryDate *time.Time `db:"requested_delivery_date"` - ApprovedDate *time.Time `db:"approved_date"` - FirstAvailableDeliveryDate *time.Time `db:"first_available_delivery_date"` - ActualPickupDate *time.Time `db:"actual_pickup_date"` - RequiredDeliveryDate *time.Time `db:"required_delivery_date"` - ScheduledDeliveryDate *time.Time `db:"scheduled_delivery_date"` - ActualDeliveryDate *time.Time `db:"actual_delivery_date"` - CustomerRemarks *string `db:"customer_remarks"` - CounselorRemarks *string `db:"counselor_remarks"` - PickupAddress *Address `belongs_to:"addresses" fk_id:"pickup_address_id"` - PickupAddressID *uuid.UUID `db:"pickup_address_id"` - DestinationAddress *Address `belongs_to:"addresses" fk_id:"destination_address_id"` - DestinationAddressID *uuid.UUID `db:"destination_address_id"` - DestinationType *DestinationType `db:"destination_address_type"` - MTOAgents MTOAgents `has_many:"mto_agents" fk_id:"mto_shipment_id"` - MTOServiceItems MTOServiceItems `has_many:"mto_service_items" fk_id:"mto_shipment_id"` - SecondaryPickupAddress *Address `belongs_to:"addresses" fk_id:"secondary_pickup_address_id"` - SecondaryPickupAddressID *uuid.UUID `db:"secondary_pickup_address_id"` - HasSecondaryPickupAddress *bool `db:"has_secondary_pickup_address"` - SecondaryDeliveryAddress *Address `belongs_to:"addresses" fk_id:"secondary_delivery_address_id"` - SecondaryDeliveryAddressID *uuid.UUID `db:"secondary_delivery_address_id"` - HasSecondaryDeliveryAddress *bool `db:"has_secondary_delivery_address"` - TertiaryPickupAddress *Address `belongs_to:"addresses" fk_id:"tertiary_pickup_address_id"` - TertiaryPickupAddressID *uuid.UUID `db:"tertiary_pickup_address_id"` - HasTertiaryPickupAddress *bool `db:"has_tertiary_pickup_address"` - TertiaryDeliveryAddress *Address `belongs_to:"addresses" fk_id:"tertiary_delivery_address_id"` - TertiaryDeliveryAddressID *uuid.UUID `db:"tertiary_delivery_address_id"` - HasTertiaryDeliveryAddress *bool `db:"has_tertiary_delivery_address"` - SITDaysAllowance *int `db:"sit_days_allowance"` - SITDurationUpdates SITDurationUpdates `has_many:"sit_extensions" fk_id:"mto_shipment_id"` - PrimeEstimatedWeight *unit.Pound `db:"prime_estimated_weight"` - PrimeEstimatedWeightRecordedDate *time.Time `db:"prime_estimated_weight_recorded_date"` - PrimeActualWeight *unit.Pound `db:"prime_actual_weight"` - BillableWeightCap *unit.Pound `db:"billable_weight_cap"` - BillableWeightJustification *string `db:"billable_weight_justification"` - NTSRecordedWeight *unit.Pound `db:"nts_recorded_weight"` - ShipmentType MTOShipmentType `db:"shipment_type"` - Status MTOShipmentStatus `db:"status"` - Diversion bool `db:"diversion"` - DiversionReason *string `db:"diversion_reason"` - DivertedFromShipmentID *uuid.UUID `db:"diverted_from_shipment_id"` - ActualProGearWeight *unit.Pound `db:"actual_pro_gear_weight"` - ActualSpouseProGearWeight *unit.Pound `db:"actual_spouse_pro_gear_weight"` - RejectionReason *string `db:"rejection_reason"` - Distance *unit.Miles `db:"distance"` - Reweigh *Reweigh `has_one:"reweighs" fk_id:"shipment_id"` - UsesExternalVendor bool `db:"uses_external_vendor"` - StorageFacility *StorageFacility `belongs_to:"storage_facilities" fk:"storage_facility_id"` - StorageFacilityID *uuid.UUID `db:"storage_facility_id"` - ServiceOrderNumber *string `db:"service_order_number"` - TACType *LOAType `db:"tac_type"` - SACType *LOAType `db:"sac_type"` - PPMShipment *PPMShipment `has_one:"ppm_shipment" fk_id:"shipment_id"` - BoatShipment *BoatShipment `has_one:"boat_shipment" fk_id:"shipment_id"` - DeliveryAddressUpdate *ShipmentAddressUpdate `has_one:"shipment_address_update" fk_id:"shipment_id"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt time.Time `db:"updated_at"` - DeletedAt *time.Time `db:"deleted_at"` - ShipmentLocator *string `db:"shipment_locator"` - OriginSITAuthEndDate *time.Time `db:"origin_sit_auth_end_date"` - DestinationSITAuthEndDate *time.Time `db:"dest_sit_auth_end_date"` - MobileHome *MobileHome `has_one:"mobile_home" fk_id:"shipment_id"` - MarketCode MarketCode `db:"market_code"` + ID uuid.UUID `json:"id" db:"id"` + MoveTaskOrder Move `json:"move_task_order" belongs_to:"moves" fk_id:"move_id"` + MoveTaskOrderID uuid.UUID `json:"move_task_order_id" db:"move_id"` + ScheduledPickupDate *time.Time `json:"scheduled_pickup_date" db:"scheduled_pickup_date"` + RequestedPickupDate *time.Time `json:"requested_pickup_date" db:"requested_pickup_date"` + RequestedDeliveryDate *time.Time `json:"requested_delivery_date" db:"requested_delivery_date"` + ApprovedDate *time.Time `json:"approved_date" db:"approved_date"` + FirstAvailableDeliveryDate *time.Time `json:"first_available_delivery_date" db:"first_available_delivery_date"` + ActualPickupDate *time.Time `json:"actual_pickup_date" db:"actual_pickup_date"` + RequiredDeliveryDate *time.Time `json:"required_delivery_date" db:"required_delivery_date"` + ScheduledDeliveryDate *time.Time `json:"scheduled_delivery_date" db:"scheduled_delivery_date"` + ActualDeliveryDate *time.Time `json:"actual_delivery_date" db:"actual_delivery_date"` + CustomerRemarks *string `json:"customer_remarks" db:"customer_remarks"` + CounselorRemarks *string `json:"counselor_remarks" db:"counselor_remarks"` + PickupAddress *Address `json:"pickup_address" belongs_to:"addresses" fk_id:"pickup_address_id"` + PickupAddressID *uuid.UUID `json:"pickup_address_id" db:"pickup_address_id"` + DestinationAddress *Address `json:"destination_address" belongs_to:"addresses" fk_id:"destination_address_id"` + DestinationAddressID *uuid.UUID `json:"destination_address_id" db:"destination_address_id"` + DestinationType *DestinationType `json:"destination_type" db:"destination_address_type"` + MTOAgents MTOAgents `json:"mto_agents" has_many:"mto_agents" fk_id:"mto_shipment_id"` + MTOServiceItems MTOServiceItems `json:"mto_service_items" has_many:"mto_service_items" fk_id:"mto_shipment_id"` + SecondaryPickupAddress *Address `json:"secondary_pickup_address" belongs_to:"addresses" fk_id:"secondary_pickup_address_id"` + SecondaryPickupAddressID *uuid.UUID `json:"secondary_pickup_address_id" db:"secondary_pickup_address_id"` + HasSecondaryPickupAddress *bool `json:"has_secondary_pickup_address" db:"has_secondary_pickup_address"` + SecondaryDeliveryAddress *Address `json:"secondary_delivery_address" belongs_to:"addresses" fk_id:"secondary_delivery_address_id"` + SecondaryDeliveryAddressID *uuid.UUID `json:"secondary_delivery_address_id" db:"secondary_delivery_address_id"` + HasSecondaryDeliveryAddress *bool `json:"has_secondary_delivery_address" db:"has_secondary_delivery_address"` + TertiaryPickupAddress *Address `json:"tertiary_pickup_address" belongs_to:"addresses" fk_id:"tertiary_pickup_address_id"` + TertiaryPickupAddressID *uuid.UUID `json:"tertiary_pickup_address_id" db:"tertiary_pickup_address_id"` + HasTertiaryPickupAddress *bool `json:"has_tertiary_pickup_address" db:"has_tertiary_pickup_address"` + TertiaryDeliveryAddress *Address `json:"tertiary_delivery_address" belongs_to:"addresses" fk_id:"tertiary_delivery_address_id"` + TertiaryDeliveryAddressID *uuid.UUID `json:"tertiary_delivery_address_id" db:"tertiary_delivery_address_id"` + HasTertiaryDeliveryAddress *bool `json:"has_tertiary_delivery_address" db:"has_tertiary_delivery_address"` + SITDaysAllowance *int `json:"sit_days_allowance" db:"sit_days_allowance"` + SITDurationUpdates SITDurationUpdates `json:"sit_duration_updates" has_many:"sit_extensions" fk_id:"mto_shipment_id"` + PrimeEstimatedWeight *unit.Pound `json:"prime_estimated_weight" db:"prime_estimated_weight"` + PrimeEstimatedWeightRecordedDate *time.Time `json:"prime_estimated_weight_recorded_date" db:"prime_estimated_weight_recorded_date"` + PrimeActualWeight *unit.Pound `json:"prime_actual_weight" db:"prime_actual_weight"` + BillableWeightCap *unit.Pound `json:"billable_weight_cap" db:"billable_weight_cap"` + BillableWeightJustification *string `json:"billable_weight_justification" db:"billable_weight_justification"` + NTSRecordedWeight *unit.Pound `json:"nts_recorded_weight" db:"nts_recorded_weight"` + ShipmentType MTOShipmentType `json:"shipment_type" db:"shipment_type"` + Status MTOShipmentStatus `json:"status" db:"status"` + Diversion bool `json:"diversion" db:"diversion"` + DiversionReason *string `json:"diversion_reason" db:"diversion_reason"` + DivertedFromShipmentID *uuid.UUID `json:"diverted_from_shipment_id" db:"diverted_from_shipment_id"` + ActualProGearWeight *unit.Pound `json:"actual_pro_gear_weight" db:"actual_pro_gear_weight"` + ActualSpouseProGearWeight *unit.Pound `json:"actual_spouse_pro_gear_weight" db:"actual_spouse_pro_gear_weight"` + RejectionReason *string `json:"rejection_reason" db:"rejection_reason"` + Distance *unit.Miles `json:"distance" db:"distance"` + Reweigh *Reweigh `json:"reweigh" has_one:"reweighs" fk_id:"shipment_id"` + UsesExternalVendor bool `json:"uses_external_vendor" db:"uses_external_vendor"` + StorageFacility *StorageFacility `json:"storage_facility" belongs_to:"storage_facilities" fk:"storage_facility_id"` + StorageFacilityID *uuid.UUID `json:"storage_facility_id" db:"storage_facility_id"` + ServiceOrderNumber *string `json:"service_order_number" db:"service_order_number"` + TACType *LOAType `json:"tac_type" db:"tac_type"` + SACType *LOAType `json:"sac_type" db:"sac_type"` + PPMShipment *PPMShipment `json:"ppm_shipment" has_one:"ppm_shipment" fk_id:"shipment_id"` + BoatShipment *BoatShipment `json:"boat_shipment" has_one:"boat_shipment" fk_id:"shipment_id"` + DeliveryAddressUpdate *ShipmentAddressUpdate `json:"delivery_address_update" has_one:"shipment_address_update" fk_id:"shipment_id"` + CreatedAt time.Time `json:"created_at" db:"created_at"` + UpdatedAt time.Time `json:"updated_at" db:"updated_at"` + DeletedAt *time.Time `json:"deleted_at" db:"deleted_at"` + ShipmentLocator *string `json:"shipment_locator" db:"shipment_locator"` + OriginSITAuthEndDate *time.Time `json:"origin_sit_auth_end_date" db:"origin_sit_auth_end_date"` + DestinationSITAuthEndDate *time.Time `json:"destination_sit_auth_end_date" db:"dest_sit_auth_end_date"` + MobileHome *MobileHome `json:"mobile_home" has_one:"mobile_home" fk_id:"shipment_id"` + MarketCode MarketCode `json:"market_code" db:"market_code"` } // TableName overrides the table name used by Pop. diff --git a/pkg/models/order.go b/pkg/models/order.go index 2b0601f17c7..a9120c9579f 100644 --- a/pkg/models/order.go +++ b/pkg/models/order.go @@ -70,14 +70,14 @@ type Order struct { CreatedAt time.Time `json:"created_at" db:"created_at"` UpdatedAt time.Time `json:"updated_at" db:"updated_at"` ServiceMemberID uuid.UUID `json:"service_member_id" db:"service_member_id"` - ServiceMember ServiceMember `belongs_to:"service_members" fk_id:"service_member_id"` + ServiceMember ServiceMember `json:"service_member" belongs_to:"service_members" fk_id:"service_member_id"` IssueDate time.Time `json:"issue_date" db:"issue_date"` ReportByDate time.Time `json:"report_by_date" db:"report_by_date"` OrdersType internalmessages.OrdersType `json:"orders_type" db:"orders_type"` OrdersTypeDetail *internalmessages.OrdersTypeDetail `json:"orders_type_detail" db:"orders_type_detail"` HasDependents bool `json:"has_dependents" db:"has_dependents"` SpouseHasProGear bool `json:"spouse_has_pro_gear" db:"spouse_has_pro_gear"` - OriginDutyLocation *DutyLocation `belongs_to:"duty_locations" fk_id:"origin_duty_location_id"` + OriginDutyLocation *DutyLocation `json:"origin_duty_location" belongs_to:"duty_locations" fk_id:"origin_duty_location_id"` OriginDutyLocationID *uuid.UUID `json:"origin_duty_location_id" db:"origin_duty_location_id"` NewDutyLocationID uuid.UUID `json:"new_duty_location_id" db:"new_duty_location_id"` NewDutyLocation DutyLocation `belongs_to:"duty_locations" fk_id:"new_duty_location_id"` diff --git a/pkg/models/re_contract_year.go b/pkg/models/re_contract_year.go index e24801e44a3..211f8012532 100644 --- a/pkg/models/re_contract_year.go +++ b/pkg/models/re_contract_year.go @@ -71,6 +71,20 @@ func (r *ReContractYear) Validate(_ *pop.Connection) (*validate.Errors, error) { ), nil } +// This function uses a raw query that calls db function get_contract to get the reContractYearId in respects to the requestedPickupDate +func FetchContractId(db *pop.Connection, requestedPickupDate time.Time) (uuid.UUID, error) { + if !requestedPickupDate.IsZero() { + var reContractYearId uuid.UUID + err := db.RawQuery("SELECT get_contract_id($1)", requestedPickupDate).First(&reContractYearId) + if err != nil { + return uuid.Nil, fmt.Errorf("error fetching contract year id for requested pickup date %s", requestedPickupDate) + } + + return reContractYearId, nil + } + return uuid.Nil, fmt.Errorf("error fetching contract ID - required parameters not provided") +} + func GetExpectedEscalationPriceContractsCount(contractYearName string) (ExpectedEscalationPriceContractsCount, error) { switch contractYearName { case BasePeriodYear1: diff --git a/pkg/models/re_contract_year_test.go b/pkg/models/re_contract_year_test.go index ad7b61403cc..5a29320d76f 100644 --- a/pkg/models/re_contract_year_test.go +++ b/pkg/models/re_contract_year_test.go @@ -6,6 +6,7 @@ import ( "github.com/gofrs/uuid" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/testdatagen" ) func (suite *ModelSuite) TestReContractYearValidations() { @@ -52,3 +53,62 @@ func (suite *ModelSuite) TestReContractYearValidations() { suite.verifyValidationErrors(&badDatesReContractYear, expErrors) }) } + +func (suite *ModelSuite) TestReContractYearModel() { + suite.Run("test that FetchContractId returns the contractId given a requestedPickupDate", func() { + validReContractYear := testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + Name: "Base Period Year 1", + StartDate: time.Date(2019, time.October, 1, 0, 0, 0, 0, time.UTC), + EndDate: time.Date(2020, time.September, 30, 0, 0, 0, 0, time.UTC), + Escalation: 1.03, + EscalationCompounded: 1.74, + }, + }) + + requestedPickupDate := time.Date(2019, time.October, 25, 0, 0, 0, 0, time.UTC) + contractYearId, err := models.FetchContractId(suite.DB(), requestedPickupDate) + + suite.Nil(err) + suite.NotNil(contractYearId) + suite.Equal(contractYearId, validReContractYear.ContractID) + }) + + suite.Run("test that FetchContractId returns error when no requestedPickupDate is given", func() { + testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + Name: "Base Period Year 1", + StartDate: time.Date(2019, time.October, 1, 0, 0, 0, 0, time.UTC), + EndDate: time.Date(2020, time.September, 30, 0, 0, 0, 0, time.UTC), + Escalation: 1.03, + EscalationCompounded: 1.74, + }, + }) + + var time time.Time + contractYearId, err := models.FetchContractId(suite.DB(), time) + + suite.NotNil(err) + suite.Contains(err.Error(), "error fetching contract ID - required parameters not provided") + suite.Equal(contractYearId, uuid.Nil) + }) + + suite.Run("test that FetchContractId returns error when no contract is found for given requestedPickupDate", func() { + testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + Name: "Base Period Year 1", + StartDate: time.Date(2019, time.October, 1, 0, 0, 0, 0, time.UTC), + EndDate: time.Date(2020, time.September, 30, 0, 0, 0, 0, time.UTC), + Escalation: 1.03, + EscalationCompounded: 1.74, + }, + }) + requestedPickupDate := time.Date(2019, time.September, 1, 0, 0, 0, 0, time.UTC) + + contractYearId, err := models.FetchContractId(suite.DB(), requestedPickupDate) + + suite.NotNil(err) + suite.Contains(err.Error(), "error fetching contract year id") + suite.Equal(contractYearId, uuid.Nil) + }) +} diff --git a/pkg/models/re_intl_accessorial_price.go b/pkg/models/re_intl_accessorial_price.go index b61b281b76a..431f87f845b 100644 --- a/pkg/models/re_intl_accessorial_price.go +++ b/pkg/models/re_intl_accessorial_price.go @@ -29,6 +29,17 @@ var validMarkets = []string{ string(MarketOconus), } +func (m Market) FullString() string { + switch m { + case MarketConus: + return "CONUS" + case MarketOconus: + return "OCONUS" + default: + return "" + } +} + // ReIntlAccessorialPrice model struct type ReIntlAccessorialPrice struct { ID uuid.UUID `json:"id" db:"id"` @@ -61,14 +72,3 @@ func (r *ReIntlAccessorialPrice) Validate(_ *pop.Connection) (*validate.Errors, &validators.IntIsGreaterThan{Field: r.PerUnitCents.Int(), Name: "PerUnitCents", Compared: -1}, ), nil } - -func (m Market) FullString() string { - switch m { - case MarketConus: - return "CONUS" - case MarketOconus: - return "OCONUS" - default: - return "" - } -} diff --git a/pkg/models/re_intl_transit_times.go b/pkg/models/re_intl_transit_times.go index b5652894efa..de44bb9c30a 100644 --- a/pkg/models/re_intl_transit_times.go +++ b/pkg/models/re_intl_transit_times.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 InternationalTransitTime struct { @@ -20,3 +23,17 @@ type InternationalTransitTime struct { func (InternationalTransitTime) TableName() string { return "re_intl_transit_times" } + +// fetch the re_intl_transit_time record from the db +func FetchInternationalTransitTime(db *pop.Connection, originRateAreaId uuid.UUID, destinationRateAreaId uuid.UUID) (InternationalTransitTime, error) { + var internationalTransitTime InternationalTransitTime + err := db. + Where("origin_rate_area_id = $1 and destination_rate_area_id = $2", originRateAreaId, destinationRateAreaId). + First(&internationalTransitTime) + + if err != nil { + return internationalTransitTime, apperror.NewQueryError("InternationalTransitTime", err, "could not look up intl transit time") + } + + return internationalTransitTime, nil +} diff --git a/pkg/models/re_intl_transit_times_test.go b/pkg/models/re_intl_transit_times_test.go new file mode 100644 index 00000000000..13e0ec45019 --- /dev/null +++ b/pkg/models/re_intl_transit_times_test.go @@ -0,0 +1,21 @@ +package models_test + +import ( + "github.com/gofrs/uuid" + + "github.com/transcom/mymove/pkg/models" +) + +func (suite *ModelSuite) TestIntlTransitTimesModel() { + suite.Run("test that FetchInternationalTransitTime returns the re_intl_transit_times given an originRateAreaId and a destinationRateAreaId", func() { + originRateAreaId := uuid.FromStringOrNil("6e802149-7e46-4d7a-ab57-6c4df832085d") + destinationRateAreaId := uuid.FromStringOrNil("c18e25f9-ec34-41ca-8c1b-05558c8d6364") + + fetchedIntlTransitTime, err := models.FetchInternationalTransitTime(suite.DB(), originRateAreaId, destinationRateAreaId) + + suite.Nil(err) + suite.NotNil(fetchedIntlTransitTime) + suite.Equal(originRateAreaId, fetchedIntlTransitTime.OriginRateAreaId) + suite.Equal(destinationRateAreaId, fetchedIntlTransitTime.DestinationRateAreaId) + }) +} diff --git a/pkg/models/re_rate_area_test.go b/pkg/models/re_rate_area_test.go index ab279418976..eaafcf6cbbd 100644 --- a/pkg/models/re_rate_area_test.go +++ b/pkg/models/re_rate_area_test.go @@ -42,8 +42,11 @@ func (suite *ModelSuite) TestFetchRateAreaID() { }) suite.Run("fail - receive error when not all values are provided", func() { - address := factory.BuildAddress(suite.DB(), nil, nil) - rateAreaId, err := models.FetchRateAreaID(suite.DB(), address.ID, nil, uuid.Nil) + var nilUuid uuid.UUID + nonNilUuid := uuid.Must(uuid.NewV4()) + contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + rateAreaId, err := models.FetchRateAreaID(suite.DB(), nilUuid, &nonNilUuid, contract.ID) + suite.Equal(uuid.Nil, rateAreaId) suite.Error(err) }) diff --git a/pkg/models/re_service.go b/pkg/models/re_service.go index 85136a4705a..70f78ccea2b 100644 --- a/pkg/models/re_service.go +++ b/pkg/models/re_service.go @@ -86,6 +86,8 @@ const ( ReServiceCodeIDDSIT ReServiceCode = "IDDSIT" // ReServiceCodeIDFSIT International destination 1st day SIT ReServiceCodeIDFSIT ReServiceCode = "IDFSIT" + // ReServiceCodeIDSFSC International destination SIT FSC + ReServiceCodeIDSFSC ReServiceCode = "IDSFSC" // ReServiceCodeIDSHUT International destination shuttle service ReServiceCodeIDSHUT ReServiceCode = "IDSHUT" // ReServiceCodeIHPK International HHG pack @@ -108,6 +110,8 @@ const ( ReServiceCodeIOOUB ReServiceCode = "IOOUB" // ReServiceCodeIOPSIT International origin SIT pickup ReServiceCodeIOPSIT ReServiceCode = "IOPSIT" + // ReServiceCodeIOSFSC International origin SIT FSC + ReServiceCodeIOSFSC ReServiceCode = "IOSFSC" // ReServiceCodeIOSHUT International origin shuttle service ReServiceCodeIOSHUT ReServiceCode = "IOSHUT" // ReServiceCodeIUBPK International UB pack 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/models/service_item_param_key.go b/pkg/models/service_item_param_key.go index 0c637cc7d92..7c2692b7e8d 100644 --- a/pkg/models/service_item_param_key.go +++ b/pkg/models/service_item_param_key.go @@ -161,6 +161,8 @@ const ( ServiceItemParamNameUncappedRequestTotal ServiceItemParamName = "UncappedRequestTotal" // ServiceItemParamNameLockedPriceCents is the param key name LockedPriceCents ServiceItemParamNameLockedPriceCents ServiceItemParamName = "LockedPriceCents" + // ServiceItemParamNameExternalCrate is the param key name ExternalCrate + ServiceItemParamNameExternalCrate ServiceItemParamName = "ExternalCrate" ) // ServiceItemParamType is a type of service item parameter @@ -281,6 +283,7 @@ var ValidServiceItemParamNames = []ServiceItemParamName{ ServiceItemParamNameLockedPriceCents, ServiceItemParamNamePerUnitCents, ServiceItemParamNamePortZip, + ServiceItemParamNameExternalCrate, } // ValidServiceItemParamNameStrings lists all valid service item param key names @@ -357,6 +360,7 @@ var ValidServiceItemParamNameStrings = []string{ string(ServiceItemParamNameLockedPriceCents), string(ServiceItemParamNamePerUnitCents), string(ServiceItemParamNamePortZip), + string(ServiceItemParamNameExternalCrate), } // ValidServiceItemParamTypes lists all valid service item param types diff --git a/pkg/models/upload.go b/pkg/models/upload.go index d6afc2d0d4a..c03c4ec2bd2 100644 --- a/pkg/models/upload.go +++ b/pkg/models/upload.go @@ -13,6 +13,26 @@ import ( "github.com/transcom/mymove/pkg/db/utilities" ) +// Used tangentally in association with an Upload to provide status of anti-virus scan +// AVStatusType represents the type of the anti-virus status, whether it is still processing, clean or infected +type AVStatusType string + +const ( + // AVStatusPROCESSING string PROCESSING + AVStatusPROCESSING AVStatusType = "PROCESSING" + // AVStatusCLEAN string CLEAN + AVStatusCLEAN AVStatusType = "CLEAN" + // AVStatusINFECTED string INFECTED + AVStatusINFECTED AVStatusType = "INFECTED" +) + +func GetAVStatusFromTags(tags map[string]string) AVStatusType { + if status, exists := tags["av-status"]; exists { + return AVStatusType(status) + } + return AVStatusType(AVStatusPROCESSING) +} + // UploadType represents the type of upload this is, whether is it uploaded for a User or for the Prime type UploadType string diff --git a/pkg/models/user_upload.go b/pkg/models/user_upload.go index 49ef6bf845a..e3826d9aacb 100644 --- a/pkg/models/user_upload.go +++ b/pkg/models/user_upload.go @@ -102,7 +102,7 @@ func FetchUserUpload(db *pop.Connection, session *auth.Session, id uuid.UUID) (U // If there's a document, check permissions. Otherwise user must // have been the uploader if userUpload.DocumentID != nil { - _, docErr := FetchDocument(db, session, *userUpload.DocumentID, false) + _, docErr := FetchDocument(db, session, *userUpload.DocumentID) if docErr != nil { return UserUpload{}, docErr } @@ -129,7 +129,7 @@ func FetchUserUploadFromUploadID(db *pop.Connection, session *auth.Session, uplo // If there's a document, check permissions. Otherwise user must // have been the uploader if userUpload.DocumentID != nil { - _, docErr := FetchDocument(db, session, *userUpload.DocumentID, false) + _, docErr := FetchDocument(db, session, *userUpload.DocumentID) if docErr != nil { return UserUpload{}, docErr } diff --git a/pkg/notifications/move_counseled_test.go b/pkg/notifications/move_counseled_test.go index b2a5c736d48..e477302cb1a 100644 --- a/pkg/notifications/move_counseled_test.go +++ b/pkg/notifications/move_counseled_test.go @@ -47,9 +47,12 @@ func (suite *NotificationSuite) TestMoveCounseledHTMLTemplateRender() { } expectedHTMLContent := `

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

+

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

+

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

+

Next steps for a PPM:

+

Next steps for government arranged shipments:

  • Your move request will be reviewed by the responsible personal property shipping office and a move task order for services will be placed with HomeSafe Alliance.
  • @@ -71,6 +75,7 @@ If you are doing a Personally Procured Move (PPM), you can start moving your per

Thank you,
USTRANSCOM MilMove Team

+

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

` htmlContent, err := notification.RenderHTML(suite.AppContextWithSessionForTest(&auth.Session{ @@ -99,9 +104,12 @@ func (suite *NotificationSuite) TestMoveCounseledTextTemplateRender() { } expectedTextContent := `*** DO NOT REPLY directly to this email *** + This is a confirmation that your counselor has approved move details for the assigned move code abc123 from origDutyLocation to destDutyLocation in the MilMove system. + What this means to you: If you are doing a Personally Procured Move (PPM), you can start moving your personal property. + Next steps for a PPM: * Remember to get legible certified weight tickets for both the empty and full weights for every trip you perform. If you do not upload legible certified weight tickets, your PPM incentive (or Actual Expense Reimbursement for Civilians) could be affected. Failure to obtain weight tickets will result in losing eligibility to receive your incentive. Note: To receive allowance for Pro-Gear, you must identify allowable items and provide weight tickets separately for Pro-Gear. @@ -110,12 +118,15 @@ Note: To receive allowance for Pro-Gear, you must identify allowable items and p * Storage costs cannot be paid in advance. * If your counselor approved an Advance Operating Allowance (AOA, or cash advance) for a PPM, log into MilMove to download your AOA Packet, and submit it to finance according to the instructions provided by your counselor. If you have been directed to use your government travel charge card (GTCC) for expenses no further action is required. * Once you complete your PPM, log into MilMove , upload your receipts and weight tickets, and submit your PPM for review. + Next steps for government arranged shipments: * Your move request will be reviewed by the responsible personal property shipping office and a move task order for services will be placed with HomeSafe Alliance. * Once this order is placed, you will receive an e-mail invitation to create an account in HomeSafe Connect (check your spam or junk folder). This is the system you will use to schedule your pre-move survey. * HomeSafe is required to contact you within one Government Business Day. Once contact has been established, HomeSafe is your primary point of contact. If any information about your move changes at any point during the move, immediately notify your HomeSafe Customer Care Representative of the changes. Remember to keep your contact information updated in MilMove. + Thank you, USTRANSCOM MilMove Team + The information contained in this email may contain Privacy Act information and is therefore protected under the Privacy Act of 1974. Failure to protect Privacy Act information could result in a $5,000 fine.` textContent, err := notification.RenderText(suite.AppContextWithSessionForTest(&auth.Session{ diff --git a/pkg/notifications/notification_receiver.go b/pkg/notifications/notification_receiver.go new file mode 100644 index 00000000000..6dfab1b5d74 --- /dev/null +++ b/pkg/notifications/notification_receiver.go @@ -0,0 +1,334 @@ +package notifications + +import ( + "context" + "errors" + "fmt" + "strings" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/config" + "github.com/aws/aws-sdk-go-v2/service/sns" + "github.com/aws/aws-sdk-go-v2/service/sqs" + "github.com/gofrs/uuid" + "go.uber.org/zap" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/cli" +) + +// NotificationQueueParams stores the params for queue creation +type NotificationQueueParams struct { + SubscriptionTopicName string + NamePrefix QueuePrefixType + FilterPolicy string +} + +// NotificationReceiver is an interface for receiving notifications +type NotificationReceiver interface { + CreateQueueWithSubscription(appCtx appcontext.AppContext, params NotificationQueueParams) (string, error) + ReceiveMessages(appCtx appcontext.AppContext, queueUrl string, timerContext context.Context) ([]ReceivedMessage, error) + CloseoutQueue(appCtx appcontext.AppContext, queueUrl string) error + GetDefaultTopic() (string, error) +} + +// NotificationReceiverConext provides context to a notification Receiver. Maps use queueUrl for key +type NotificationReceiverContext struct { + viper ViperType + snsService SnsClient + sqsService SqsClient + awsRegion string + awsAccountId string + queueSubscriptionMap map[string]string + receiverCancelMap map[string]context.CancelFunc +} + +// QueuePrefixType represents a prefix identifier given to a name of dynamic notification queues +type QueuePrefixType string + +const ( + QueuePrefixObjectTagsAdded QueuePrefixType = "ObjectTagsAdded" +) + +//go:generate mockery --name SnsClient --output ./receiverMocks +type SnsClient interface { + Subscribe(ctx context.Context, params *sns.SubscribeInput, optFns ...func(*sns.Options)) (*sns.SubscribeOutput, error) + Unsubscribe(ctx context.Context, params *sns.UnsubscribeInput, optFns ...func(*sns.Options)) (*sns.UnsubscribeOutput, error) + ListSubscriptionsByTopic(context.Context, *sns.ListSubscriptionsByTopicInput, ...func(*sns.Options)) (*sns.ListSubscriptionsByTopicOutput, error) +} + +//go:generate mockery --name SqsClient --output ./receiverMocks +type SqsClient interface { + CreateQueue(ctx context.Context, params *sqs.CreateQueueInput, optFns ...func(*sqs.Options)) (*sqs.CreateQueueOutput, error) + ReceiveMessage(ctx context.Context, params *sqs.ReceiveMessageInput, optFns ...func(*sqs.Options)) (*sqs.ReceiveMessageOutput, error) + DeleteMessage(ctx context.Context, params *sqs.DeleteMessageInput, optFns ...func(*sqs.Options)) (*sqs.DeleteMessageOutput, error) + DeleteQueue(ctx context.Context, params *sqs.DeleteQueueInput, optFns ...func(*sqs.Options)) (*sqs.DeleteQueueOutput, error) + ListQueues(ctx context.Context, params *sqs.ListQueuesInput, optFns ...func(*sqs.Options)) (*sqs.ListQueuesOutput, error) +} + +//go:generate mockery --name ViperType --output ./receiverMocks +type ViperType interface { + GetString(string) string + SetEnvKeyReplacer(*strings.Replacer) +} + +// ReceivedMessage standardizes the format of the received message +type ReceivedMessage struct { + MessageId string + Body *string +} + +// NewNotificationReceiver returns a new NotificationReceiverContext +func NewNotificationReceiver(v ViperType, snsService SnsClient, sqsService SqsClient, awsRegion string, awsAccountId string) NotificationReceiverContext { + return NotificationReceiverContext{ + viper: v, + snsService: snsService, + sqsService: sqsService, + awsRegion: awsRegion, + awsAccountId: awsAccountId, + queueSubscriptionMap: make(map[string]string), + receiverCancelMap: make(map[string]context.CancelFunc), + } +} + +// CreateQueueWithSubscription first creates a new queue, then subscribes an AWS topic to it +func (n NotificationReceiverContext) CreateQueueWithSubscription(appCtx appcontext.AppContext, params NotificationQueueParams) (string, error) { + + queueUUID := uuid.Must(uuid.NewV4()) + + queueName := fmt.Sprintf("%s_%s", params.NamePrefix, queueUUID) + queueArn := n.constructArn("sqs", queueName) + topicArn := n.constructArn("sns", params.SubscriptionTopicName) + + accessPolicy := fmt.Sprintf(`{ + "Version": "2012-10-17", + "Statement": [{ + "Sid": "AllowSNSPublish", + "Effect": "Allow", + "Principal": { + "Service": "sns.amazonaws.com" + }, + "Action": ["sqs:SendMessage"], + "Resource": "%s", + "Condition": { + "ArnEquals": { + "aws:SourceArn": "%s" + } + } + }, { + "Sid": "DenyNonSSLAccess", + "Effect": "Deny", + "Principal": "*", + "Action": "sqs:*", + "Resource": "%s", + "Condition": { + "Bool": { + "aws:SecureTransport": "false" + } + } + }] + }`, queueArn, topicArn, queueArn) + + input := &sqs.CreateQueueInput{ + QueueName: &queueName, + Attributes: map[string]string{ + "MessageRetentionPeriod": "120", + "Policy": accessPolicy, + }, + } + + result, err := n.sqsService.CreateQueue(context.Background(), input) + if err != nil { + appCtx.Logger().Error("Failed to create SQS queue, %v", zap.Error(err)) + return "", err + } + + subscribeInput := &sns.SubscribeInput{ + TopicArn: &topicArn, + Protocol: aws.String("sqs"), + Endpoint: &queueArn, + Attributes: map[string]string{ + "FilterPolicy": params.FilterPolicy, + "FilterPolicyScope": "MessageBody", + }, + } + subscribeOutput, err := n.snsService.Subscribe(context.Background(), subscribeInput) + if err != nil { + appCtx.Logger().Error("Failed to create subscription, %v", zap.Error(err)) + return "", err + } + + n.queueSubscriptionMap[*result.QueueUrl] = *subscribeOutput.SubscriptionArn + + return *result.QueueUrl, nil +} + +// ReceiveMessages polls given queue continuously for messages for up to 20 seconds +func (n NotificationReceiverContext) ReceiveMessages(appCtx appcontext.AppContext, queueUrl string, timerContext context.Context) ([]ReceivedMessage, error) { + recCtx, cancelRecCtx := context.WithCancel(timerContext) + defer cancelRecCtx() + n.receiverCancelMap[queueUrl] = cancelRecCtx + + result, err := n.sqsService.ReceiveMessage(recCtx, &sqs.ReceiveMessageInput{ + QueueUrl: &queueUrl, + MaxNumberOfMessages: 1, + WaitTimeSeconds: 20, + }) + if errors.Is(recCtx.Err(), context.Canceled) || errors.Is(recCtx.Err(), context.DeadlineExceeded) { + return nil, recCtx.Err() + } + + if err != nil { + appCtx.Logger().Info("Couldn't get messages from queue. Error: %v\n", zap.Error(err)) + return nil, err + } + + receivedMessages := make([]ReceivedMessage, len(result.Messages)) + for index, value := range result.Messages { + receivedMessages[index] = ReceivedMessage{ + MessageId: *value.MessageId, + Body: value.Body, + } + + appCtx.Logger().Info("Message received.", zap.String("messageId", *value.MessageId)) + + _, err := n.sqsService.DeleteMessage(recCtx, &sqs.DeleteMessageInput{ + QueueUrl: &queueUrl, + ReceiptHandle: value.ReceiptHandle, + }) + if err != nil { + appCtx.Logger().Info("Couldn't delete message from queue. Error: %v\n", zap.Error(err)) + } + } + + return receivedMessages, recCtx.Err() +} + +// CloseoutQueue stops receiving messages and cleans up the queue and its subscriptions +func (n NotificationReceiverContext) CloseoutQueue(appCtx appcontext.AppContext, queueUrl string) error { + appCtx.Logger().Info("Closing out queue: ", zap.String("queueUrl", queueUrl)) + + if cancelFunc, exists := n.receiverCancelMap[queueUrl]; exists { + cancelFunc() + delete(n.receiverCancelMap, queueUrl) + } + + if subscriptionArn, exists := n.queueSubscriptionMap[queueUrl]; exists { + _, err := n.snsService.Unsubscribe(context.Background(), &sns.UnsubscribeInput{ + SubscriptionArn: &subscriptionArn, + }) + if err != nil { + return err + } + delete(n.queueSubscriptionMap, queueUrl) + } + + _, err := n.sqsService.DeleteQueue(context.Background(), &sqs.DeleteQueueInput{ + QueueUrl: &queueUrl, + }) + + return err +} + +// GetDefaultTopic returns the topic value set within the environment +func (n NotificationReceiverContext) GetDefaultTopic() (string, error) { + topicName := n.viper.GetString(cli.SNSTagsUpdatedTopicFlag) + receiverBackend := n.viper.GetString(cli.ReceiverBackendFlag) + if topicName == "" && receiverBackend == "sns_sqs" { + return "", errors.New("sns_tags_updated_topic key not available") + } + return topicName, nil +} + +// InitReceiver initializes the receiver backend, only call this once +func InitReceiver(v ViperType, logger *zap.Logger, wipeAllNotificationQueues bool) (NotificationReceiver, error) { + + if v.GetString(cli.ReceiverBackendFlag) == "sns_sqs" { + // Setup notification receiver service with SNS & SQS backend dependencies + awsSNSRegion := v.GetString(cli.SNSRegionFlag) + awsAccountId := v.GetString(cli.SNSAccountId) + + logger.Info("Using aws sns_sqs receiver backend", zap.String("region", awsSNSRegion)) + + cfg, err := config.LoadDefaultConfig(context.Background(), + config.WithRegion(awsSNSRegion), + ) + if err != nil { + logger.Fatal("error loading sns aws config", zap.Error(err)) + return nil, err + } + + snsService := sns.NewFromConfig(cfg) + sqsService := sqs.NewFromConfig(cfg) + + notificationReceiver := NewNotificationReceiver(v, snsService, sqsService, awsSNSRegion, awsAccountId) + + // Remove any remaining previous notification queues on server start + if wipeAllNotificationQueues { + err = notificationReceiver.wipeAllNotificationQueues(logger) + if err != nil { + return nil, err + } + } + + return notificationReceiver, nil + } + + logger.Info("Using local notification receiver backend", zap.String("receiver_backend", v.GetString(cli.ReceiverBackendFlag))) + + return NewStubNotificationReceiver(), nil +} + +func (n NotificationReceiverContext) constructArn(awsService string, endpointName string) string { + return fmt.Sprintf("arn:aws-us-gov:%s:%s:%s:%s", awsService, n.awsRegion, n.awsAccountId, endpointName) +} + +// Removes ALL previously created notification queues +func (n *NotificationReceiverContext) wipeAllNotificationQueues(logger *zap.Logger) error { + defaultTopic, err := n.GetDefaultTopic() + if err != nil { + return err + } + + logger.Info("Receiver cleanup - Removing previous subscriptions...") + paginator := sns.NewListSubscriptionsByTopicPaginator(n.snsService, &sns.ListSubscriptionsByTopicInput{ + TopicArn: aws.String(n.constructArn("sns", defaultTopic)), + }) + + for paginator.HasMorePages() { + output, err := paginator.NextPage(context.Background()) + if err != nil { + return err + } + for _, subscription := range output.Subscriptions { + if strings.Contains(*subscription.Endpoint, string(QueuePrefixObjectTagsAdded)) { + logger.Info("Subscription ARN: ", zap.String("subscription arn", *subscription.SubscriptionArn)) + logger.Info("Endpoint ARN: ", zap.String("endpoint arn", *subscription.Endpoint)) + _, err = n.snsService.Unsubscribe(context.Background(), &sns.UnsubscribeInput{ + SubscriptionArn: subscription.SubscriptionArn, + }) + if err != nil { + return err + } + } + } + } + + logger.Info("Receiver cleanup - Removing previous queues...") + result, err := n.sqsService.ListQueues(context.Background(), &sqs.ListQueuesInput{ + QueueNamePrefix: aws.String(string(QueuePrefixObjectTagsAdded)), + }) + if err != nil { + return err + } + + for _, url := range result.QueueUrls { + _, err = n.sqsService.DeleteQueue(context.Background(), &sqs.DeleteQueueInput{ + QueueUrl: &url, + }) + if err != nil { + return err + } + } + return nil +} diff --git a/pkg/notifications/notification_receiver_stub.go b/pkg/notifications/notification_receiver_stub.go new file mode 100644 index 00000000000..e98f0c8aa1e --- /dev/null +++ b/pkg/notifications/notification_receiver_stub.go @@ -0,0 +1,51 @@ +package notifications + +import ( + "context" + "time" + + "go.uber.org/zap" + + "github.com/transcom/mymove/pkg/appcontext" +) + +// StubNotificationReceiver mocks an SNS & SQS client for local usage +type StubNotificationReceiver NotificationReceiverContext + +// NewStubNotificationReceiver returns a new StubNotificationReceiver +func NewStubNotificationReceiver() StubNotificationReceiver { + return StubNotificationReceiver{ + snsService: nil, + sqsService: nil, + awsRegion: "", + awsAccountId: "", + queueSubscriptionMap: make(map[string]string), + receiverCancelMap: make(map[string]context.CancelFunc), + } +} + +func (n StubNotificationReceiver) CreateQueueWithSubscription(appCtx appcontext.AppContext, params NotificationQueueParams) (string, error) { + return "stubQueueName", nil +} + +func (n StubNotificationReceiver) ReceiveMessages(appCtx appcontext.AppContext, queueUrl string, timerContext context.Context) ([]ReceivedMessage, error) { + time.Sleep(3 * time.Second) + messageId := "stubMessageId" + body := queueUrl + ":stubMessageBody" + mockMessages := make([]ReceivedMessage, 1) + mockMessages[0] = ReceivedMessage{ + MessageId: messageId, + Body: &body, + } + appCtx.Logger().Debug("Receiving a stubbed message for queue: %v", zap.String("queueUrl", queueUrl)) + return mockMessages, nil +} + +func (n StubNotificationReceiver) CloseoutQueue(appCtx appcontext.AppContext, queueUrl string) error { + appCtx.Logger().Debug("Closing out the stubbed queue.") + return nil +} + +func (n StubNotificationReceiver) GetDefaultTopic() (string, error) { + return "stubDefaultTopic", nil +} diff --git a/pkg/notifications/notification_receiver_test.go b/pkg/notifications/notification_receiver_test.go new file mode 100644 index 00000000000..f7dab5a91b7 --- /dev/null +++ b/pkg/notifications/notification_receiver_test.go @@ -0,0 +1,146 @@ +package notifications + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/sns" + "github.com/aws/aws-sdk-go-v2/service/sqs" + "github.com/aws/aws-sdk-go-v2/service/sqs/types" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" + + "github.com/transcom/mymove/pkg/cli" + mocks "github.com/transcom/mymove/pkg/notifications/receiverMocks" + "github.com/transcom/mymove/pkg/testingsuite" +) + +type notificationReceiverSuite struct { + *testingsuite.PopTestSuite +} + +func TestNotificationReceiverSuite(t *testing.T) { + + hs := ¬ificationReceiverSuite{ + PopTestSuite: testingsuite.NewPopTestSuite(testingsuite.CurrentPackage(), + testingsuite.WithPerTestTransaction()), + } + suite.Run(t, hs) + hs.PopTestSuite.TearDown() +} + +func (suite *notificationReceiverSuite) TestSuccessPath() { + + suite.Run("local backend - notification receiver stub", func() { + // Setup mocks + mockedViper := mocks.ViperType{} + mockedViper.On("GetString", cli.ReceiverBackendFlag).Return("local") + mockedViper.On("GetString", cli.SNSRegionFlag).Return("us-gov-west-1") + mockedViper.On("GetString", cli.SNSAccountId).Return("12345") + mockedViper.On("GetString", cli.SNSTagsUpdatedTopicFlag).Return("fake_sns_topic") + localReceiver, err := InitReceiver(&mockedViper, suite.Logger(), true) + + suite.NoError(err) + suite.IsType(StubNotificationReceiver{}, localReceiver) + + defaultTopic, err := localReceiver.GetDefaultTopic() + suite.Equal("stubDefaultTopic", defaultTopic) + suite.NoError(err) + + queueParams := NotificationQueueParams{ + NamePrefix: "testPrefix", + } + createdQueueUrl, err := localReceiver.CreateQueueWithSubscription(suite.AppContextForTest(), queueParams) + suite.NoError(err) + suite.NotContains(createdQueueUrl, queueParams.NamePrefix) + suite.Equal(createdQueueUrl, "stubQueueName") + + timerContext, cancelTimerContext := context.WithTimeout(context.Background(), 2*time.Second) + defer cancelTimerContext() + + receivedMessages, err := localReceiver.ReceiveMessages(suite.AppContextForTest(), createdQueueUrl, timerContext) + suite.NoError(err) + suite.Len(receivedMessages, 1) + suite.Equal(receivedMessages[0].MessageId, "stubMessageId") + suite.Equal(*receivedMessages[0].Body, fmt.Sprintf("%s:stubMessageBody", createdQueueUrl)) + }) + + suite.Run("aws backend - notification receiver InitReceiver", func() { + // Setup mocks + mockedViper := mocks.ViperType{} + mockedViper.On("GetString", cli.ReceiverBackendFlag).Return("sns_sqs") + mockedViper.On("GetString", cli.SNSRegionFlag).Return("us-gov-west-1") + mockedViper.On("GetString", cli.SNSAccountId).Return("12345") + mockedViper.On("GetString", cli.SNSTagsUpdatedTopicFlag).Return("fake_sns_topic") + + receiver, err := InitReceiver(&mockedViper, suite.Logger(), false) + + suite.NoError(err) + suite.IsType(NotificationReceiverContext{}, receiver) + defaultTopic, err := receiver.GetDefaultTopic() + suite.Equal("fake_sns_topic", defaultTopic) + suite.NoError(err) + }) + + suite.Run("aws backend - notification receiver with mock services", func() { + // Setup mocks + mockedViper := mocks.ViperType{} + mockedViper.On("GetString", cli.ReceiverBackendFlag).Return("sns_sqs") + mockedViper.On("GetString", cli.SNSRegionFlag).Return("us-gov-west-1") + mockedViper.On("GetString", cli.SNSAccountId).Return("12345") + mockedViper.On("GetString", cli.SNSTagsUpdatedTopicFlag).Return("fake_sns_topic") + + mockedSns := mocks.SnsClient{} + mockedSns.On("Subscribe", mock.Anything, mock.AnythingOfType("*sns.SubscribeInput")).Return(&sns.SubscribeOutput{ + SubscriptionArn: aws.String("FakeSubscriptionArn"), + }, nil) + mockedSns.On("Unsubscribe", mock.Anything, mock.AnythingOfType("*sns.UnsubscribeInput")).Return(&sns.UnsubscribeOutput{}, nil) + mockedSns.On("ListSubscriptionsByTopic", mock.Anything, mock.AnythingOfType("*sns.ListSubscriptionsByTopicInput")).Return(&sns.ListSubscriptionsByTopicOutput{}, nil) + + mockedSqs := mocks.SqsClient{} + mockedSqs.On("CreateQueue", mock.Anything, mock.AnythingOfType("*sqs.CreateQueueInput")).Return(&sqs.CreateQueueOutput{ + QueueUrl: aws.String("fakeQueueUrl"), + }, nil) + mockedSqs.On("ReceiveMessage", mock.Anything, mock.AnythingOfType("*sqs.ReceiveMessageInput")).Return(&sqs.ReceiveMessageOutput{ + Messages: []types.Message{ + { + MessageId: aws.String("fakeMessageId"), + Body: aws.String("fakeQueueUrl:fakeMessageBody"), + }, + }, + }, nil) + mockedSqs.On("DeleteMessage", mock.Anything, mock.AnythingOfType("*sqs.DeleteMessageInput")).Return(&sqs.DeleteMessageOutput{}, nil) + mockedSqs.On("DeleteQueue", mock.Anything, mock.AnythingOfType("*sqs.DeleteQueueInput")).Return(&sqs.DeleteQueueOutput{}, nil) + mockedSqs.On("ListQueues", mock.Anything, mock.AnythingOfType("*sqs.ListQueuesInput")).Return(&sqs.ListQueuesOutput{}, nil) + + // Run test + receiver := NewNotificationReceiver(&mockedViper, &mockedSns, &mockedSqs, "", "") + suite.IsType(NotificationReceiverContext{}, receiver) + + defaultTopic, err := receiver.GetDefaultTopic() + suite.Equal("fake_sns_topic", defaultTopic) + suite.NoError(err) + + queueParams := NotificationQueueParams{ + NamePrefix: "testPrefix", + } + createdQueueUrl, err := receiver.CreateQueueWithSubscription(suite.AppContextForTest(), queueParams) + suite.NoError(err) + suite.Equal("fakeQueueUrl", createdQueueUrl) + + timerContext, cancelTimerContext := context.WithTimeout(context.Background(), 2*time.Second) + defer cancelTimerContext() + + receivedMessages, err := receiver.ReceiveMessages(suite.AppContextForTest(), createdQueueUrl, timerContext) + suite.NoError(err) + suite.Len(receivedMessages, 1) + suite.Equal(receivedMessages[0].MessageId, "fakeMessageId") + suite.Equal(*receivedMessages[0].Body, fmt.Sprintf("%s:fakeMessageBody", createdQueueUrl)) + + err = receiver.CloseoutQueue(suite.AppContextForTest(), createdQueueUrl) + suite.NoError(err) + }) +} diff --git a/pkg/notifications/notification_stub.go b/pkg/notifications/notification_sender_stub.go similarity index 100% rename from pkg/notifications/notification_stub.go rename to pkg/notifications/notification_sender_stub.go diff --git a/pkg/notifications/notification_test.go b/pkg/notifications/notification_sender_test.go similarity index 100% rename from pkg/notifications/notification_test.go rename to pkg/notifications/notification_sender_test.go diff --git a/pkg/notifications/receiverMocks/SnsClient.go b/pkg/notifications/receiverMocks/SnsClient.go new file mode 100644 index 00000000000..0c562896a0d --- /dev/null +++ b/pkg/notifications/receiverMocks/SnsClient.go @@ -0,0 +1,141 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + + sns "github.com/aws/aws-sdk-go-v2/service/sns" +) + +// SnsClient is an autogenerated mock type for the SnsClient type +type SnsClient struct { + mock.Mock +} + +// ListSubscriptionsByTopic provides a mock function with given fields: _a0, _a1, _a2 +func (_m *SnsClient) ListSubscriptionsByTopic(_a0 context.Context, _a1 *sns.ListSubscriptionsByTopicInput, _a2 ...func(*sns.Options)) (*sns.ListSubscriptionsByTopicOutput, error) { + _va := make([]interface{}, len(_a2)) + for _i := range _a2 { + _va[_i] = _a2[_i] + } + var _ca []interface{} + _ca = append(_ca, _a0, _a1) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for ListSubscriptionsByTopic") + } + + var r0 *sns.ListSubscriptionsByTopicOutput + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *sns.ListSubscriptionsByTopicInput, ...func(*sns.Options)) (*sns.ListSubscriptionsByTopicOutput, error)); ok { + return rf(_a0, _a1, _a2...) + } + if rf, ok := ret.Get(0).(func(context.Context, *sns.ListSubscriptionsByTopicInput, ...func(*sns.Options)) *sns.ListSubscriptionsByTopicOutput); ok { + r0 = rf(_a0, _a1, _a2...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*sns.ListSubscriptionsByTopicOutput) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *sns.ListSubscriptionsByTopicInput, ...func(*sns.Options)) error); ok { + r1 = rf(_a0, _a1, _a2...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Subscribe provides a mock function with given fields: ctx, params, optFns +func (_m *SnsClient) Subscribe(ctx context.Context, params *sns.SubscribeInput, optFns ...func(*sns.Options)) (*sns.SubscribeOutput, error) { + _va := make([]interface{}, len(optFns)) + for _i := range optFns { + _va[_i] = optFns[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, params) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Subscribe") + } + + var r0 *sns.SubscribeOutput + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *sns.SubscribeInput, ...func(*sns.Options)) (*sns.SubscribeOutput, error)); ok { + return rf(ctx, params, optFns...) + } + if rf, ok := ret.Get(0).(func(context.Context, *sns.SubscribeInput, ...func(*sns.Options)) *sns.SubscribeOutput); ok { + r0 = rf(ctx, params, optFns...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*sns.SubscribeOutput) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *sns.SubscribeInput, ...func(*sns.Options)) error); ok { + r1 = rf(ctx, params, optFns...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Unsubscribe provides a mock function with given fields: ctx, params, optFns +func (_m *SnsClient) Unsubscribe(ctx context.Context, params *sns.UnsubscribeInput, optFns ...func(*sns.Options)) (*sns.UnsubscribeOutput, error) { + _va := make([]interface{}, len(optFns)) + for _i := range optFns { + _va[_i] = optFns[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, params) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Unsubscribe") + } + + var r0 *sns.UnsubscribeOutput + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *sns.UnsubscribeInput, ...func(*sns.Options)) (*sns.UnsubscribeOutput, error)); ok { + return rf(ctx, params, optFns...) + } + if rf, ok := ret.Get(0).(func(context.Context, *sns.UnsubscribeInput, ...func(*sns.Options)) *sns.UnsubscribeOutput); ok { + r0 = rf(ctx, params, optFns...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*sns.UnsubscribeOutput) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *sns.UnsubscribeInput, ...func(*sns.Options)) error); ok { + r1 = rf(ctx, params, optFns...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewSnsClient creates a new instance of SnsClient. 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 NewSnsClient(t interface { + mock.TestingT + Cleanup(func()) +}) *SnsClient { + mock := &SnsClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/notifications/receiverMocks/SqsClient.go b/pkg/notifications/receiverMocks/SqsClient.go new file mode 100644 index 00000000000..c8e6e6aa284 --- /dev/null +++ b/pkg/notifications/receiverMocks/SqsClient.go @@ -0,0 +1,215 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + + sqs "github.com/aws/aws-sdk-go-v2/service/sqs" +) + +// SqsClient is an autogenerated mock type for the SqsClient type +type SqsClient struct { + mock.Mock +} + +// CreateQueue provides a mock function with given fields: ctx, params, optFns +func (_m *SqsClient) CreateQueue(ctx context.Context, params *sqs.CreateQueueInput, optFns ...func(*sqs.Options)) (*sqs.CreateQueueOutput, error) { + _va := make([]interface{}, len(optFns)) + for _i := range optFns { + _va[_i] = optFns[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, params) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for CreateQueue") + } + + var r0 *sqs.CreateQueueOutput + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *sqs.CreateQueueInput, ...func(*sqs.Options)) (*sqs.CreateQueueOutput, error)); ok { + return rf(ctx, params, optFns...) + } + if rf, ok := ret.Get(0).(func(context.Context, *sqs.CreateQueueInput, ...func(*sqs.Options)) *sqs.CreateQueueOutput); ok { + r0 = rf(ctx, params, optFns...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*sqs.CreateQueueOutput) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *sqs.CreateQueueInput, ...func(*sqs.Options)) error); ok { + r1 = rf(ctx, params, optFns...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DeleteMessage provides a mock function with given fields: ctx, params, optFns +func (_m *SqsClient) DeleteMessage(ctx context.Context, params *sqs.DeleteMessageInput, optFns ...func(*sqs.Options)) (*sqs.DeleteMessageOutput, error) { + _va := make([]interface{}, len(optFns)) + for _i := range optFns { + _va[_i] = optFns[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, params) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for DeleteMessage") + } + + var r0 *sqs.DeleteMessageOutput + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *sqs.DeleteMessageInput, ...func(*sqs.Options)) (*sqs.DeleteMessageOutput, error)); ok { + return rf(ctx, params, optFns...) + } + if rf, ok := ret.Get(0).(func(context.Context, *sqs.DeleteMessageInput, ...func(*sqs.Options)) *sqs.DeleteMessageOutput); ok { + r0 = rf(ctx, params, optFns...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*sqs.DeleteMessageOutput) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *sqs.DeleteMessageInput, ...func(*sqs.Options)) error); ok { + r1 = rf(ctx, params, optFns...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// DeleteQueue provides a mock function with given fields: ctx, params, optFns +func (_m *SqsClient) DeleteQueue(ctx context.Context, params *sqs.DeleteQueueInput, optFns ...func(*sqs.Options)) (*sqs.DeleteQueueOutput, error) { + _va := make([]interface{}, len(optFns)) + for _i := range optFns { + _va[_i] = optFns[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, params) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for DeleteQueue") + } + + var r0 *sqs.DeleteQueueOutput + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *sqs.DeleteQueueInput, ...func(*sqs.Options)) (*sqs.DeleteQueueOutput, error)); ok { + return rf(ctx, params, optFns...) + } + if rf, ok := ret.Get(0).(func(context.Context, *sqs.DeleteQueueInput, ...func(*sqs.Options)) *sqs.DeleteQueueOutput); ok { + r0 = rf(ctx, params, optFns...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*sqs.DeleteQueueOutput) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *sqs.DeleteQueueInput, ...func(*sqs.Options)) error); ok { + r1 = rf(ctx, params, optFns...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ListQueues provides a mock function with given fields: ctx, params, optFns +func (_m *SqsClient) ListQueues(ctx context.Context, params *sqs.ListQueuesInput, optFns ...func(*sqs.Options)) (*sqs.ListQueuesOutput, error) { + _va := make([]interface{}, len(optFns)) + for _i := range optFns { + _va[_i] = optFns[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, params) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for ListQueues") + } + + var r0 *sqs.ListQueuesOutput + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *sqs.ListQueuesInput, ...func(*sqs.Options)) (*sqs.ListQueuesOutput, error)); ok { + return rf(ctx, params, optFns...) + } + if rf, ok := ret.Get(0).(func(context.Context, *sqs.ListQueuesInput, ...func(*sqs.Options)) *sqs.ListQueuesOutput); ok { + r0 = rf(ctx, params, optFns...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*sqs.ListQueuesOutput) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *sqs.ListQueuesInput, ...func(*sqs.Options)) error); ok { + r1 = rf(ctx, params, optFns...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ReceiveMessage provides a mock function with given fields: ctx, params, optFns +func (_m *SqsClient) ReceiveMessage(ctx context.Context, params *sqs.ReceiveMessageInput, optFns ...func(*sqs.Options)) (*sqs.ReceiveMessageOutput, error) { + _va := make([]interface{}, len(optFns)) + for _i := range optFns { + _va[_i] = optFns[_i] + } + var _ca []interface{} + _ca = append(_ca, ctx, params) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for ReceiveMessage") + } + + var r0 *sqs.ReceiveMessageOutput + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, *sqs.ReceiveMessageInput, ...func(*sqs.Options)) (*sqs.ReceiveMessageOutput, error)); ok { + return rf(ctx, params, optFns...) + } + if rf, ok := ret.Get(0).(func(context.Context, *sqs.ReceiveMessageInput, ...func(*sqs.Options)) *sqs.ReceiveMessageOutput); ok { + r0 = rf(ctx, params, optFns...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*sqs.ReceiveMessageOutput) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, *sqs.ReceiveMessageInput, ...func(*sqs.Options)) error); ok { + r1 = rf(ctx, params, optFns...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewSqsClient creates a new instance of SqsClient. 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 NewSqsClient(t interface { + mock.TestingT + Cleanup(func()) +}) *SqsClient { + mock := &SqsClient{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/notifications/receiverMocks/ViperType.go b/pkg/notifications/receiverMocks/ViperType.go new file mode 100644 index 00000000000..bf5e6f84090 --- /dev/null +++ b/pkg/notifications/receiverMocks/ViperType.go @@ -0,0 +1,51 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + + strings "strings" +) + +// ViperType is an autogenerated mock type for the ViperType type +type ViperType struct { + mock.Mock +} + +// GetString provides a mock function with given fields: _a0 +func (_m *ViperType) GetString(_a0 string) string { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for GetString") + } + + var r0 string + if rf, ok := ret.Get(0).(func(string) string); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// SetEnvKeyReplacer provides a mock function with given fields: _a0 +func (_m *ViperType) SetEnvKeyReplacer(_a0 *strings.Replacer) { + _m.Called(_a0) +} + +// NewViperType creates a new instance of ViperType. 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 NewViperType(t interface { + mock.TestingT + Cleanup(func()) +}) *ViperType { + mock := &ViperType{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/payment_request/service_param_value_lookups/cubic_feet_billed_lookup.go b/pkg/payment_request/service_param_value_lookups/cubic_feet_billed_lookup.go index 547d900cfc8..80f26837b3f 100644 --- a/pkg/payment_request/service_param_value_lookups/cubic_feet_billed_lookup.go +++ b/pkg/payment_request/service_param_value_lookups/cubic_feet_billed_lookup.go @@ -13,17 +13,21 @@ const ( // CubicFeetBilledLookup does lookup for CubicFeetBilled type CubicFeetBilledLookup struct { - Dimensions models.MTOServiceItemDimensions + Dimensions models.MTOServiceItemDimensions + ServiceItem models.MTOServiceItem } func (c CubicFeetBilledLookup) lookup(_ appcontext.AppContext, keyData *ServiceItemParamKeyData) (string, error) { + isIntlCrateUncrate := c.ServiceItem.ReService.Code == models.ReServiceCodeICRT || c.ServiceItem.ReService.Code == models.ReServiceCodeIUCRT + isExternalCrate := c.ServiceItem.ExternalCrate != nil && *c.ServiceItem.ExternalCrate + // Each service item has an array of dimensions. There is a DB constraint preventing // more than one dimension of each type for a given service item, so we just have to // look for the first crating dimension. for _, dimension := range c.Dimensions { if dimension.Type == models.DimensionTypeCrate { volume := dimension.Volume().ToCubicFeet() - if volume < minCubicFeetBilled { + if (!isIntlCrateUncrate || isExternalCrate) && volume < minCubicFeetBilled { volume = minCubicFeetBilled } return volume.String(), nil diff --git a/pkg/payment_request/service_param_value_lookups/cubic_feet_billed_lookup_test.go b/pkg/payment_request/service_param_value_lookups/cubic_feet_billed_lookup_test.go index 29965d9f03a..48374649c74 100644 --- a/pkg/payment_request/service_param_value_lookups/cubic_feet_billed_lookup_test.go +++ b/pkg/payment_request/service_param_value_lookups/cubic_feet_billed_lookup_test.go @@ -74,7 +74,7 @@ func (suite *ServiceParamValueLookupsSuite) TestCubicFeetBilledLookup() { suite.Equal("1029.33", stringValue) }) - suite.Run("When crate volume is less than minimum, billed volume should be set to minimum", func() { + suite.Run("When domestic crate volume is less than minimum, billed volume should be set to minimum", func() { testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ ReContractYear: models.ReContractYear{ StartDate: time.Now().Add(-24 * time.Hour), @@ -134,6 +134,127 @@ func (suite *ServiceParamValueLookupsSuite) TestCubicFeetBilledLookup() { suite.Equal("4.00", stringValue) }) + suite.Run("When international external crate volume is less than minimum, billed volume should be set to minimum", func() { + testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: time.Now().Add(-24 * time.Hour), + EndDate: time.Now().Add(24 * time.Hour), + }, + }) + mtoServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeICRT, + }, + }, + }, []factory.Trait{ + factory.GetTraitAvailableToPrimeMove, + }) + + cratingDimension := factory.BuildMTOServiceItemDimension(suite.DB(), []factory.Customization{ + { + Model: models.MTOServiceItemDimension{ + Type: models.DimensionTypeCrate, + Length: 1000, + Height: 1000, + Width: 1000, + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + }, + }, + { + Model: mtoServiceItem, + LinkOnly: true, + }, + }, nil) + itemDimension := factory.BuildMTOServiceItemDimension(suite.DB(), []factory.Customization{ + { + Model: models.MTOServiceItemDimension{ + Type: models.DimensionTypeItem, + Length: 100, + Height: 100, + Width: 100, + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + }, + }, + { + Model: mtoServiceItem, + LinkOnly: true, + }, + }, nil) + mtoServiceItem.Dimensions = []models.MTOServiceItemDimension{itemDimension, cratingDimension} + mtoServiceItem.ExternalCrate = models.BoolPointer(true) + suite.MustSave(&mtoServiceItem) + paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), mtoServiceItem.MoveTaskOrderID, nil) + suite.FatalNoError(err) + + stringValue, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + suite.FatalNoError(err) + + suite.Equal("4.00", stringValue) + }) + + suite.Run("When international non-external crate volume is less than minimum, billed volume should NOT be set to minimum", func() { + testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: time.Now().Add(-24 * time.Hour), + EndDate: time.Now().Add(24 * time.Hour), + }, + }) + mtoServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeICRT, + }, + }, + }, []factory.Trait{ + factory.GetTraitAvailableToPrimeMove, + }) + + cratingDimension := factory.BuildMTOServiceItemDimension(suite.DB(), []factory.Customization{ + { + Model: models.MTOServiceItemDimension{ + Type: models.DimensionTypeCrate, + Length: 12000, + Height: 12000, + Width: 12000, + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + }, + }, + { + Model: mtoServiceItem, + LinkOnly: true, + }, + }, nil) + itemDimension := factory.BuildMTOServiceItemDimension(suite.DB(), []factory.Customization{ + { + Model: models.MTOServiceItemDimension{ + Type: models.DimensionTypeItem, + Length: 100, + Height: 100, + Width: 100, + CreatedAt: time.Time{}, + UpdatedAt: time.Time{}, + }, + }, + { + Model: mtoServiceItem, + LinkOnly: true, + }, + }, nil) + mtoServiceItem.Dimensions = []models.MTOServiceItemDimension{itemDimension, cratingDimension} + suite.MustSave(&mtoServiceItem) + paramLookup, err := ServiceParamLookupInitialize(suite.AppContextForTest(), suite.planner, mtoServiceItem, uuid.Must(uuid.NewV4()), mtoServiceItem.MoveTaskOrderID, nil) + suite.FatalNoError(err) + + stringValue, err := paramLookup.ServiceParamValue(suite.AppContextForTest(), key) + suite.FatalNoError(err) + + suite.Equal("1.00", stringValue) + }) + suite.Run("missing dimension should error", func() { testdatagen.MakeReContractYear(suite.DB(), testdatagen.Assertions{ ReContractYear: models.ReContractYear{ diff --git a/pkg/payment_request/service_param_value_lookups/external_crate_lookup.go b/pkg/payment_request/service_param_value_lookups/external_crate_lookup.go new file mode 100644 index 00000000000..64aef2da1d4 --- /dev/null +++ b/pkg/payment_request/service_param_value_lookups/external_crate_lookup.go @@ -0,0 +1,22 @@ +package serviceparamvaluelookups + +import ( + "strconv" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/models" +) + +// ExternalCrateLookup does lookup on externalCrate +type ExternalCrateLookup struct { + ServiceItem models.MTOServiceItem +} + +func (r ExternalCrateLookup) lookup(_ appcontext.AppContext, _ *ServiceItemParamKeyData) (string, error) { + externalCrate := r.ServiceItem.ExternalCrate + if externalCrate == nil { + return "false", nil + } + + return strconv.FormatBool(*externalCrate), nil +} diff --git a/pkg/payment_request/service_param_value_lookups/external_crate_lookup_test.go b/pkg/payment_request/service_param_value_lookups/external_crate_lookup_test.go new file mode 100644 index 00000000000..59606485500 --- /dev/null +++ b/pkg/payment_request/service_param_value_lookups/external_crate_lookup_test.go @@ -0,0 +1,50 @@ +package serviceparamvaluelookups + +import ( + "strconv" + + "github.com/transcom/mymove/pkg/models" +) + +func (suite *ServiceParamValueLookupsSuite) TestExternalCrateLookup() { + suite.Run("ExternalCrate is true", func() { + externalCrate := true + mtoServiceItem := models.MTOServiceItem{ + ExternalCrate: &externalCrate, + } + + paramLookup := ExternalCrateLookup{ServiceItem: mtoServiceItem} + valueStr, err := paramLookup.lookup(suite.AppContextForTest(), nil) + + suite.FatalNoError(err) + expected := strconv.FormatBool(externalCrate) + suite.Equal(expected, valueStr) + }) + + suite.Run("ExternalCrate is false", func() { + externalCrate := false + mtoServiceItem := models.MTOServiceItem{ + ExternalCrate: &externalCrate, + } + + paramLookup := ExternalCrateLookup{ServiceItem: mtoServiceItem} + valueStr, err := paramLookup.lookup(suite.AppContextForTest(), nil) + + suite.FatalNoError(err) + expected := strconv.FormatBool(externalCrate) + suite.Equal(expected, valueStr) + }) + + suite.Run("ExternalCrate is nil", func() { + mtoServiceItem := models.MTOServiceItem{ + ExternalCrate: nil, + } + + paramLookup := ExternalCrateLookup{ServiceItem: mtoServiceItem} + valueStr, err := paramLookup.lookup(suite.AppContextForTest(), nil) + + suite.FatalNoError(err) + expected := "false" + suite.Equal(expected, valueStr) + }) +} 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 6815d250f28..242340c3085 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 @@ -91,6 +91,7 @@ var ServiceItemParamsWithLookups = []models.ServiceItemParamName{ models.ServiceItemParamNamePortZip, models.ServiceItemParamNameMarketDest, models.ServiceItemParamNameMarketOrigin, + models.ServiceItemParamNameExternalCrate, } // ServiceParamLookupInitialize initializes service parameter lookup @@ -342,7 +343,8 @@ func InitializeLookups(appCtx appcontext.AppContext, shipment models.MTOShipment lookups[models.ServiceItemParamNameContractCode] = ContractCodeLookup{} lookups[models.ServiceItemParamNameCubicFeetBilled] = CubicFeetBilledLookup{ - Dimensions: serviceItem.Dimensions, + Dimensions: serviceItem.Dimensions, + ServiceItem: serviceItem, } lookups[models.ServiceItemParamNamePSILinehaulDom] = PSILinehaulDomLookup{ @@ -455,6 +457,10 @@ func InitializeLookups(appCtx appcontext.AppContext, shipment models.MTOShipment Address: *shipment.DestinationAddress, } + lookups[models.ServiceItemParamNameExternalCrate] = ExternalCrateLookup{ + ServiceItem: serviceItem, + } + return lookups } 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/services/address.go b/pkg/services/address.go index a1b25f17448..4537083bad3 100644 --- a/pkg/services/address.go +++ b/pkg/services/address.go @@ -15,5 +15,5 @@ type AddressUpdater interface { //go:generate mockery --name VLocation type VLocation interface { - GetLocationsByZipCityState(appCtx appcontext.AppContext, search string, exclusionStateFilters []string) (*models.VLocations, error) + GetLocationsByZipCityState(appCtx appcontext.AppContext, search string, exclusionStateFilters []string, exactMatch ...bool) (*models.VLocations, error) } diff --git a/pkg/services/address/address_creator_test.go b/pkg/services/address/address_creator_test.go index 0d691225e51..f99aaca7552 100644 --- a/pkg/services/address/address_creator_test.go +++ b/pkg/services/address/address_creator_test.go @@ -176,4 +176,22 @@ func (suite *AddressSuite) TestAddressCreator() { suite.Nil(err) suite.NotNil(address.Country) }) + + suite.Run("Successfully creates a CONUS address", func() { + country := &models.Country{} + country.Country = "US" + addressCreator := NewAddressCreator() + address, err := addressCreator.CreateAddress(suite.AppContextForTest(), &models.Address{ + StreetAddress1: "7645 Ballinshire N", + City: "Indianapolis", + State: "IN", + PostalCode: "46254", + Country: country, + }) + + suite.False(*address.IsOconus) + suite.NotNil(address.ID) + suite.Nil(err) + suite.NotNil(address.Country) + }) } diff --git a/pkg/services/address/address_lookup.go b/pkg/services/address/address_lookup.go index a258ab29dfb..1c12c4ed277 100644 --- a/pkg/services/address/address_lookup.go +++ b/pkg/services/address/address_lookup.go @@ -6,6 +6,7 @@ import ( "regexp" "strings" + "github.com/gobuffalo/pop/v6" "github.com/gofrs/uuid" "github.com/pkg/errors" @@ -22,8 +23,14 @@ func NewVLocation() services.VLocation { return &vLocation{} } -func (o vLocation) GetLocationsByZipCityState(appCtx appcontext.AppContext, search string, exclusionStateFilters []string) (*models.VLocations, error) { - locationList, err := FindLocationsByZipCity(appCtx, search, exclusionStateFilters) +func (o vLocation) GetLocationsByZipCityState(appCtx appcontext.AppContext, search string, exclusionStateFilters []string, exactMatch ...bool) (*models.VLocations, error) { + exact := false + + if len(exactMatch) > 0 { + exact = true + } + + locationList, err := FindLocationsByZipCity(appCtx, search, exclusionStateFilters, exact) if err != nil { switch err { @@ -42,7 +49,7 @@ func (o vLocation) GetLocationsByZipCityState(appCtx appcontext.AppContext, sear // to determine when the state and postal code need to be parsed from the search string // If there is only one result and no comma and the search string is all numbers we then search // using the entered postal code rather than city name -func FindLocationsByZipCity(appCtx appcontext.AppContext, search string, exclusionStateFilters []string) (models.VLocations, error) { +func FindLocationsByZipCity(appCtx appcontext.AppContext, search string, exclusionStateFilters []string, exactMatch bool) (models.VLocations, error) { var locationList []models.VLocation searchSlice := strings.Split(search, ",") city := "" @@ -67,8 +74,14 @@ func FindLocationsByZipCity(appCtx appcontext.AppContext, search string, exclusi } sqlQuery := `SELECT vl.city_name, vl.state, vl.usprc_county_nm, vl.uspr_zip_id, vl.uprc_id - FROM v_locations vl where vl.uspr_zip_id like ? AND - vl.city_name like upper(?) AND vl.state like upper(?)` + FROM v_locations vl where vl.uspr_zip_id like ? AND + vl.city_name like upper(?) AND vl.state like upper(?)` + + if exactMatch { + sqlQuery = `SELECT vl.city_name, vl.state, vl.usprc_county_nm, vl.uspr_zip_id, vl.uprc_id + FROM v_locations vl where vl.uspr_zip_id = ? AND + vl.city_name = upper(?) AND vl.state = upper(?)` + } // apply filter to exclude specific states if provided for _, value := range exclusionStateFilters { @@ -76,8 +89,15 @@ func FindLocationsByZipCity(appCtx appcontext.AppContext, search string, exclusi } sqlQuery += ` limit 30` + var query *pop.Query + + // we only want to add an extra % to the strings if we are using the LIKE in the query + if exactMatch { + query = appCtx.DB().RawQuery(sqlQuery, postalCode, city, state) + } else { + query = appCtx.DB().RawQuery(sqlQuery, fmt.Sprintf("%s%%", postalCode), fmt.Sprintf("%s%%", city), fmt.Sprintf("%s%%", state)) + } - query := appCtx.DB().RawQuery(sqlQuery, fmt.Sprintf("%s%%", postalCode), fmt.Sprintf("%s%%", city), fmt.Sprintf("%s%%", state)) if err := query.All(&locationList); err != nil { if errors.Cause(err).Error() != models.RecordNotFoundErrorString { return locationList, err 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 d1c7b923118..8944ad1b1bc 100644 --- a/pkg/services/ghc_rate_engine.go +++ b/pkg/services/ghc_rate_engine.go @@ -312,3 +312,19 @@ type IntlDestinationAdditionalDaySITPricer interface { Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, numberOfDaysInSIT int, weight unit.Pound, perUnitCents int) (unit.Cents, PricingDisplayParams, error) ParamsPricer } + +// IntlCratingPricer prices the international crating service for a Move +// +//go:generate mockery --name IntlCratingPricer +type IntlCratingPricer interface { + Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, billedCubicFeet unit.CubicFeet, standaloneCrate bool, standaloneCrateCap unit.Cents, externalCrate bool, market models.Market) (unit.Cents, PricingDisplayParams, error) + ParamsPricer +} + +// IntlUncratingPricer prices the international uncrating service for a Move +// +//go:generate mockery --name IntlUncratingPricer +type IntlUncratingPricer interface { + Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, billedCubicFeet unit.CubicFeet, market models.Market) (unit.Cents, PricingDisplayParams, error) + ParamsPricer +} diff --git a/pkg/services/ghcrateengine/intl_crating_pricer.go b/pkg/services/ghcrateengine/intl_crating_pricer.go new file mode 100644 index 00000000000..096045a07ce --- /dev/null +++ b/pkg/services/ghcrateengine/intl_crating_pricer.go @@ -0,0 +1,66 @@ +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 intlCratingPricer struct { +} + +// NewIntlCratingPricer creates a new pricer for international crating +func NewIntlCratingPricer() services.IntlCratingPricer { + return &intlCratingPricer{} +} + +// Price determines the price for international crating +func (p intlCratingPricer) Price(appCtx appcontext.AppContext, contractCode string, referenceDate time.Time, billedCubicFeet unit.CubicFeet, standaloneCrate bool, standaloneCrateCap unit.Cents, externalCrate bool, market models.Market) (unit.Cents, services.PricingDisplayParams, error) { + return priceIntlCratingUncrating(appCtx, models.ReServiceCodeICRT, contractCode, referenceDate, billedCubicFeet, standaloneCrate, standaloneCrateCap, externalCrate, market) +} + +// PriceUsingParams determines the price for international crating given PaymentServiceItemParams +func (p intlCratingPricer) 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 + } + + cubicFeetFloat, err := getParamFloat(params, models.ServiceItemParamNameCubicFeetBilled) + if err != nil { + return unit.Cents(0), nil, err + } + + cubicFeetBilled := unit.CubicFeet(cubicFeetFloat) + + referenceDate, err := getParamTime(params, models.ServiceItemParamNameReferenceDate) + if err != nil { + return unit.Cents(0), nil, err + } + + market, err := getParamMarket(params, models.ServiceItemParamNameMarketOrigin) + if err != nil { + return unit.Cents(0), nil, err + } + + standaloneCrate, err := getParamBool(params, models.ServiceItemParamNameStandaloneCrate) + if err != nil { + return unit.Cents(0), nil, err + } + + externalCrate, err := getParamBool(params, models.ServiceItemParamNameExternalCrate) + if err != nil { + return unit.Cents(0), nil, err + } + + standaloneCrateCapParam, err := getParamInt(params, models.ServiceItemParamNameStandaloneCrateCap) + if err != nil { + return unit.Cents(0), nil, err + } + standaloneCrateCap := unit.Cents(float64(standaloneCrateCapParam)) + + return p.Price(appCtx, contractCode, referenceDate, cubicFeetBilled, standaloneCrate, standaloneCrateCap, externalCrate, market) +} diff --git a/pkg/services/ghcrateengine/intl_crating_pricer_test.go b/pkg/services/ghcrateengine/intl_crating_pricer_test.go new file mode 100644 index 00000000000..d49951cb30c --- /dev/null +++ b/pkg/services/ghcrateengine/intl_crating_pricer_test.go @@ -0,0 +1,136 @@ +package ghcrateengine + +import ( + "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 ( + icrtTestMarket = models.Market("O") + icrtTestBasePriceCents = unit.Cents(2300) + icrtTestEscalationCompounded = 1.125 + icrtTestBilledCubicFeet = unit.CubicFeet(10) + icrtTestPriceCents = unit.Cents(25880) + icrtTestStandaloneCrate = false + icrtTestStandaloneCrateCap = unit.Cents(1000000) + icrtTestUncappedRequestTotal = unit.Cents(25880) + icrtTestExternalCrate = false +) + +var icrtTestRequestedPickupDate = time.Date(testdatagen.TestYear, time.June, 5, 7, 33, 11, 456, time.UTC) + +func (suite *GHCRateEngineServiceSuite) TestIntlCratingPricer() { + pricer := NewIntlCratingPricer() + + suite.Run("success using PaymentServiceItemParams", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeICRT, icrtTestMarket, icrtTestBasePriceCents, testdatagen.DefaultContractCode, icrtTestEscalationCompounded) + + paymentServiceItem := suite.setupIntlCratingServiceItem(icrtTestBilledCubicFeet) + priceCents, displayParams, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.NoError(err) + suite.Equal(icrtTestPriceCents, priceCents) + + expectedParams := services.PricingDisplayParams{ + {Key: models.ServiceItemParamNameContractYearName, Value: testdatagen.DefaultContractCode}, + {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(icrtTestEscalationCompounded)}, + {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(icrtTestBasePriceCents)}, + {Key: models.ServiceItemParamNameUncappedRequestTotal, Value: FormatCents(icrtTestUncappedRequestTotal)}, + } + suite.validatePricerCreatedParams(expectedParams, displayParams) + }) + suite.Run("success with truncating cubic feet", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeICRT, icrtTestMarket, icrtTestBasePriceCents, testdatagen.DefaultContractCode, icrtTestEscalationCompounded) + + paymentServiceItem := suite.setupIntlCratingServiceItem(unit.CubicFeet(10.005)) + priceCents, _, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.NoError(err) + suite.Equal(icrtTestPriceCents, priceCents) + }) + + suite.Run("success without PaymentServiceItemParams", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeICRT, icrtTestMarket, icrtTestBasePriceCents, testdatagen.DefaultContractCode, icrtTestEscalationCompounded) + + priceCents, _, err := pricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, icrtTestRequestedPickupDate, icrtTestBilledCubicFeet, icrtTestStandaloneCrate, icrtTestStandaloneCrateCap, icrtTestExternalCrate, icrtTestMarket) + suite.NoError(err) + suite.Equal(icrtTestPriceCents, priceCents) + }) + + suite.Run("PriceUsingParams but sending empty params", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeICRT, icrtTestMarket, icrtTestBasePriceCents, testdatagen.DefaultContractCode, icrtTestEscalationCompounded) + _, _, err := pricer.PriceUsingParams(suite.AppContextForTest(), models.PaymentServiceItemParams{}) + suite.Error(err) + }) + + suite.Run("invalid crating volume - external crate", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeICRT, icrtTestMarket, icrtTestBasePriceCents, testdatagen.DefaultContractCode, icrtTestEscalationCompounded) + badVolume := unit.CubicFeet(3.0) + _, _, err := pricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, icrtTestRequestedPickupDate, badVolume, icrtTestStandaloneCrate, icrtTestStandaloneCrateCap, true, icrtTestMarket) + suite.Error(err) + suite.Contains(err.Error(), "external crates must be billed for a minimum of 4.00 cubic feet") + }) + + suite.Run("not finding a rate record", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeICRT, icrtTestMarket, icrtTestBasePriceCents, testdatagen.DefaultContractCode, icrtTestEscalationCompounded) + _, _, err := pricer.Price(suite.AppContextForTest(), "BOGUS", icrtTestRequestedPickupDate, icrtTestBilledCubicFeet, icrtTestStandaloneCrate, icrtTestStandaloneCrateCap, icrtTestExternalCrate, icrtTestMarket) + suite.Error(err) + suite.Contains(err.Error(), "could not lookup International Accessorial Area Price") + }) + + suite.Run("not finding a contract year record", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeICRT, icrtTestMarket, icrtTestBasePriceCents, testdatagen.DefaultContractCode, icrtTestEscalationCompounded) + twoYearsLaterPickupDate := icrtTestRequestedPickupDate.AddDate(2, 0, 0) + _, _, err := pricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, twoYearsLaterPickupDate, icrtTestBilledCubicFeet, icrtTestStandaloneCrate, icrtTestStandaloneCrateCap, icrtTestExternalCrate, icrtTestMarket) + suite.Error(err) + suite.Contains(err.Error(), "could not lookup contract year") + }) +} + +func (suite *GHCRateEngineServiceSuite) setupIntlCratingServiceItem(cubicFeet unit.CubicFeet) models.PaymentServiceItem { + return factory.BuildPaymentServiceItemWithParams( + suite.DB(), + models.ReServiceCodeICRT, + []factory.CreatePaymentServiceItemParams{ + { + Key: models.ServiceItemParamNameContractCode, + KeyType: models.ServiceItemParamTypeString, + Value: factory.DefaultContractCode, + }, + { + Key: models.ServiceItemParamNameCubicFeetBilled, + KeyType: models.ServiceItemParamTypeDecimal, + Value: cubicFeet.String(), + }, + { + Key: models.ServiceItemParamNameReferenceDate, + KeyType: models.ServiceItemParamTypeDate, + Value: icrtTestRequestedPickupDate.Format(DateParamFormat), + }, + { + Key: models.ServiceItemParamNameStandaloneCrate, + KeyType: models.ServiceItemParamTypeBoolean, + Value: strconv.FormatBool(false), + }, + { + Key: models.ServiceItemParamNameStandaloneCrateCap, + KeyType: models.ServiceItemParamTypeInteger, + Value: strconv.FormatInt(100000, 10), + }, + { + Key: models.ServiceItemParamNameMarketOrigin, + KeyType: models.ServiceItemParamTypeString, + Value: icrtTestMarket.String(), + }, + { + Key: models.ServiceItemParamNameExternalCrate, + KeyType: models.ServiceItemParamTypeBoolean, + Value: strconv.FormatBool(false), + }, + }, nil, nil, + ) +} diff --git a/pkg/services/ghcrateengine/intl_uncrating_pricer.go b/pkg/services/ghcrateengine/intl_uncrating_pricer.go new file mode 100644 index 00000000000..23c3470f102 --- /dev/null +++ b/pkg/services/ghcrateengine/intl_uncrating_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 intlUncratingPricer struct { +} + +// NewIntlUncratingPricer creates a new pricer for international uncrating +func NewIntlUncratingPricer() services.IntlUncratingPricer { + return &intlUncratingPricer{} +} + +// Price determines the price for international uncrating +func (p intlUncratingPricer) Price(appCtx appcontext.AppContext, contractCode string, referenceDate time.Time, billedCubicFeet unit.CubicFeet, market models.Market) (unit.Cents, services.PricingDisplayParams, error) { + return priceIntlCratingUncrating(appCtx, models.ReServiceCodeIUCRT, contractCode, referenceDate, billedCubicFeet, false, 0, false, market) +} + +// PriceUsingParams determines the price for international uncrating given PaymentServiceItemParams +func (p intlUncratingPricer) 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 + } + + cubicFeetFloat, err := getParamFloat(params, models.ServiceItemParamNameCubicFeetBilled) + if err != nil { + return unit.Cents(0), nil, err + } + + cubicFeetBilled := unit.CubicFeet(cubicFeetFloat) + + referenceDate, err := getParamTime(params, models.ServiceItemParamNameReferenceDate) + if err != nil { + return unit.Cents(0), nil, err + } + + market, err := getParamMarket(params, models.ServiceItemParamNameMarketDest) + if err != nil { + return unit.Cents(0), nil, err + } + + return p.Price(appCtx, contractCode, referenceDate, cubicFeetBilled, market) +} diff --git a/pkg/services/ghcrateengine/intl_uncrating_pricer_test.go b/pkg/services/ghcrateengine/intl_uncrating_pricer_test.go new file mode 100644 index 00000000000..5369172b039 --- /dev/null +++ b/pkg/services/ghcrateengine/intl_uncrating_pricer_test.go @@ -0,0 +1,102 @@ +package ghcrateengine + +import ( + "fmt" + "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 ( + iucrtTestMarket = models.Market("O") + iucrtTestBasePriceCents = unit.Cents(595) + iucrtTestEscalationCompounded = 1.125 + iucrtTestBilledCubicFeet = 10 + iucrtTestPriceCents = unit.Cents(6690) + iucrtTestUncappedRequestTotal = unit.Cents(6690) +) + +var iucrtTestRequestedPickupDate = time.Date(testdatagen.TestYear, time.June, 5, 7, 33, 11, 456, time.UTC) + +func (suite *GHCRateEngineServiceSuite) TestIntlUncratingPricer() { + pricer := NewIntlUncratingPricer() + + suite.Run("success using PaymentServiceItemParams", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeIUCRT, iucrtTestMarket, iucrtTestBasePriceCents, testdatagen.DefaultContractCode, iucrtTestEscalationCompounded) + + paymentServiceItem := suite.setupIntlUncratingServiceItem() + priceCents, displayParams, err := pricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) + suite.NoError(err) + suite.Equal(iucrtTestPriceCents, priceCents) + + expectedParams := services.PricingDisplayParams{ + {Key: models.ServiceItemParamNameContractYearName, Value: testdatagen.DefaultContractCode}, + {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(iucrtTestEscalationCompounded)}, + {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(iucrtTestBasePriceCents)}, + {Key: models.ServiceItemParamNameUncappedRequestTotal, Value: FormatCents(iucrtTestUncappedRequestTotal)}, + } + suite.validatePricerCreatedParams(expectedParams, displayParams) + }) + + suite.Run("success without PaymentServiceItemParams", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeIUCRT, iucrtTestMarket, iucrtTestBasePriceCents, testdatagen.DefaultContractCode, iucrtTestEscalationCompounded) + + priceCents, _, err := pricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, iucrtTestRequestedPickupDate, iucrtTestBilledCubicFeet, iucrtTestMarket) + suite.NoError(err) + suite.Equal(iucrtTestPriceCents, priceCents) + }) + + suite.Run("PriceUsingParams but sending empty params", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeIUCRT, iucrtTestMarket, iucrtTestBasePriceCents, testdatagen.DefaultContractCode, iucrtTestEscalationCompounded) + _, _, err := pricer.PriceUsingParams(suite.AppContextForTest(), models.PaymentServiceItemParams{}) + suite.Error(err) + }) + + suite.Run("not finding a rate record", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeIUCRT, iucrtTestMarket, iucrtTestBasePriceCents, testdatagen.DefaultContractCode, iucrtTestEscalationCompounded) + _, _, err := pricer.Price(suite.AppContextForTest(), "BOGUS", iucrtTestRequestedPickupDate, iucrtTestBilledCubicFeet, iucrtTestMarket) + suite.Error(err) + suite.Contains(err.Error(), "could not lookup International Accessorial Area Price") + }) + + suite.Run("not finding a contract year record", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeIUCRT, iucrtTestMarket, iucrtTestBasePriceCents, testdatagen.DefaultContractCode, iucrtTestEscalationCompounded) + twoYearsLaterPickupDate := iucrtTestRequestedPickupDate.AddDate(2, 0, 0) + _, _, err := pricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, twoYearsLaterPickupDate, iucrtTestBilledCubicFeet, iucrtTestMarket) + suite.Error(err) + suite.Contains(err.Error(), "could not lookup contract year") + }) +} + +func (suite *GHCRateEngineServiceSuite) setupIntlUncratingServiceItem() models.PaymentServiceItem { + return factory.BuildPaymentServiceItemWithParams( + suite.DB(), + models.ReServiceCodeIUCRT, + []factory.CreatePaymentServiceItemParams{ + { + Key: models.ServiceItemParamNameContractCode, + KeyType: models.ServiceItemParamTypeString, + Value: factory.DefaultContractCode, + }, + { + Key: models.ServiceItemParamNameCubicFeetBilled, + KeyType: models.ServiceItemParamTypeDecimal, + Value: fmt.Sprintf("%d", int(iucrtTestBilledCubicFeet)), + }, + { + Key: models.ServiceItemParamNameReferenceDate, + KeyType: models.ServiceItemParamTypeDate, + Value: iucrtTestRequestedPickupDate.Format(DateParamFormat), + }, + { + Key: models.ServiceItemParamNameMarketDest, + KeyType: models.ServiceItemParamTypeString, + Value: iucrtTestMarket.String(), + }, + }, nil, nil, + ) +} diff --git a/pkg/services/ghcrateengine/pricer_helpers_intl.go b/pkg/services/ghcrateengine/pricer_helpers_intl.go index 9754f552b3d..195dc117b1c 100644 --- a/pkg/services/ghcrateengine/pricer_helpers_intl.go +++ b/pkg/services/ghcrateengine/pricer_helpers_intl.go @@ -222,3 +222,63 @@ func priceIntlAdditionalDaySIT(appCtx appcontext.AppContext, additionalDaySITCod return totalCost, displayParams, nil } + +func priceIntlCratingUncrating(appCtx appcontext.AppContext, cratingUncratingCode models.ReServiceCode, contractCode string, referenceDate time.Time, billedCubicFeet unit.CubicFeet, standaloneCrate bool, standaloneCrateCap unit.Cents, externalCrate bool, market models.Market) (unit.Cents, services.PricingDisplayParams, error) { + if cratingUncratingCode != models.ReServiceCodeICRT && cratingUncratingCode != models.ReServiceCodeIUCRT { + return 0, nil, fmt.Errorf("unsupported international crating/uncrating code of %s", cratingUncratingCode) + } + + // Validate parameters + if len(contractCode) == 0 { + return 0, nil, errors.New("ContractCode is required") + } + if referenceDate.IsZero() { + return 0, nil, errors.New("ReferenceDate is required") + } + if market == "" { + return 0, nil, errors.New("Market is required") + } + + if externalCrate && billedCubicFeet < minIntlExternalCrateBilledCubicFeet { + return 0, nil, fmt.Errorf("external crates must be billed for a minimum of %.2f cubic feet", minIntlExternalCrateBilledCubicFeet) + } + + internationalAccessorialPrice, err := fetchInternationalAccessorialPrice(appCtx, contractCode, cratingUncratingCode, market) + if err != nil { + return 0, nil, fmt.Errorf("could not lookup International Accessorial Area Price: %w", err) + } + + basePrice := internationalAccessorialPrice.PerUnitCents.Float64() + escalatedPrice, contractYear, err := escalatePriceForContractYear(appCtx, internationalAccessorialPrice.ContractID, referenceDate, false, basePrice) + if err != nil { + return 0, nil, fmt.Errorf("could not calculate escalated price: %w", err) + } + + escalatedPrice = escalatedPrice * float64(billedCubicFeet) + totalCost := unit.Cents(math.Round(escalatedPrice)) + + displayParams := services.PricingDisplayParams{ + { + Key: models.ServiceItemParamNamePriceRateOrFactor, + Value: FormatCents(internationalAccessorialPrice.PerUnitCents), + }, + { + Key: models.ServiceItemParamNameContractYearName, + Value: contractYear.Name, + }, + { + Key: models.ServiceItemParamNameEscalationCompounded, + Value: FormatEscalation(contractYear.EscalationCompounded), + }, + { + Key: models.ServiceItemParamNameUncappedRequestTotal, + Value: FormatCents(totalCost), + }, + } + + if (standaloneCrate) && (totalCost > standaloneCrateCap) { + totalCost = standaloneCrateCap + } + + 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 14e3d6c8618..a0721e001fb 100644 --- a/pkg/services/ghcrateengine/pricer_helpers_intl_test.go +++ b/pkg/services/ghcrateengine/pricer_helpers_intl_test.go @@ -205,3 +205,58 @@ func (suite *GHCRateEngineServiceSuite) TestPriceIntlAdditionalDaySIT() { suite.Contains(err.Error(), "NumberDaysSIT is required") }) } + +func (suite *GHCRateEngineServiceSuite) TestPriceIntlCratingUncrating() { + suite.Run("crating golden path", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeICRT, icrtTestMarket, icrtTestBasePriceCents, testdatagen.DefaultContractCode, icrtTestEscalationCompounded) + + priceCents, displayParams, err := priceIntlCratingUncrating(suite.AppContextForTest(), models.ReServiceCodeICRT, testdatagen.DefaultContractCode, icrtTestRequestedPickupDate, icrtTestBilledCubicFeet, icrtTestStandaloneCrate, icrtTestStandaloneCrateCap, icrtTestExternalCrate, icrtTestMarket) + suite.NoError(err) + suite.Equal(icrtTestPriceCents, priceCents) + + expectedParams := services.PricingDisplayParams{ + {Key: models.ServiceItemParamNameContractYearName, Value: testdatagen.DefaultContractCode}, + {Key: models.ServiceItemParamNameEscalationCompounded, Value: FormatEscalation(icrtTestEscalationCompounded)}, + {Key: models.ServiceItemParamNamePriceRateOrFactor, Value: FormatCents(icrtTestBasePriceCents)}, + {Key: models.ServiceItemParamNameUncappedRequestTotal, Value: FormatCents(dcrtTestUncappedRequestTotal)}, + } + suite.validatePricerCreatedParams(expectedParams, displayParams) + }) + + suite.Run("invalid service code", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeICRT, icrtTestMarket, icrtTestBasePriceCents, testdatagen.DefaultContractCode, icrtTestEscalationCompounded) + _, _, err := priceIntlCratingUncrating(suite.AppContextForTest(), models.ReServiceCodeCS, testdatagen.DefaultContractCode, icrtTestRequestedPickupDate, icrtTestBilledCubicFeet, icrtTestStandaloneCrate, icrtTestStandaloneCrateCap, icrtTestExternalCrate, icrtTestMarket) + + suite.Error(err) + suite.Contains(err.Error(), "unsupported international crating/uncrating code") + }) + + suite.Run("invalid crate size - external crate", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeICRT, icrtTestMarket, icrtTestBasePriceCents, testdatagen.DefaultContractCode, icrtTestEscalationCompounded) + + badSize := unit.CubicFeet(1.0) + _, _, err := priceIntlCratingUncrating(suite.AppContextForTest(), models.ReServiceCodeICRT, testdatagen.DefaultContractCode, icrtTestRequestedPickupDate, badSize, icrtTestStandaloneCrate, icrtTestStandaloneCrateCap, true, icrtTestMarket) + + suite.Error(err) + suite.Contains(err.Error(), "external crates must be billed for a minimum of 4.00 cubic feet") + }) + + suite.Run("not finding a rate record", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeICRT, icrtTestMarket, icrtTestBasePriceCents, testdatagen.DefaultContractCode, icrtTestEscalationCompounded) + + _, _, err := priceIntlCratingUncrating(suite.AppContextForTest(), models.ReServiceCodeICRT, "BOGUS", icrtTestRequestedPickupDate, icrtTestBilledCubicFeet, icrtTestStandaloneCrate, icrtTestStandaloneCrateCap, icrtTestExternalCrate, icrtTestMarket) + + suite.Error(err) + suite.Contains(err.Error(), "could not lookup International Accessorial Area Price") + }) + + suite.Run("not finding a contract year record", func() { + suite.setupInternationalAccessorialPrice(models.ReServiceCodeICRT, icrtTestMarket, icrtTestBasePriceCents, testdatagen.DefaultContractCode, icrtTestEscalationCompounded) + + twoYearsLaterPickupDate := ioshutTestRequestedPickupDate.AddDate(2, 0, 0) + _, _, err := priceIntlCratingUncrating(suite.AppContextForTest(), models.ReServiceCodeICRT, testdatagen.DefaultContractCode, twoYearsLaterPickupDate, icrtTestBilledCubicFeet, icrtTestStandaloneCrate, icrtTestStandaloneCrateCap, icrtTestExternalCrate, icrtTestMarket) + + suite.Error(err) + suite.Contains(err.Error(), "could not calculate escalated price: could not lookup contract year") + }) +} diff --git a/pkg/services/ghcrateengine/service_item_pricer.go b/pkg/services/ghcrateengine/service_item_pricer.go index 777ca2283bf..e48f3cdc749 100644 --- a/pkg/services/ghcrateengine/service_item_pricer.go +++ b/pkg/services/ghcrateengine/service_item_pricer.go @@ -115,6 +115,10 @@ func PricerForServiceItem(serviceCode models.ReServiceCode) (services.ParamsPric return NewIntlDestinationFirstDaySITPricer(), nil case models.ReServiceCodeIDASIT: return NewIntlDestinationAdditionalDaySITPricer(), nil + case models.ReServiceCodeICRT: + return NewIntlCratingPricer(), nil + case models.ReServiceCodeIUCRT: + return NewIntlUncratingPricer(), 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/ghcrateengine/service_item_pricer_test.go b/pkg/services/ghcrateengine/service_item_pricer_test.go index c27652cf90d..cbaf02ba2e2 100644 --- a/pkg/services/ghcrateengine/service_item_pricer_test.go +++ b/pkg/services/ghcrateengine/service_item_pricer_test.go @@ -55,6 +55,8 @@ func (suite *GHCRateEngineServiceSuite) TestGetPricer() { {models.ReServiceCodeIOSHUT, &internationalOriginShuttlingPricer{}}, {models.ReServiceCodeDCRT, &domesticCratingPricer{}}, {models.ReServiceCodeDUCRT, &domesticUncratingPricer{}}, + {models.ReServiceCodeICRT, &intlCratingPricer{}}, + {models.ReServiceCodeIUCRT, &intlUncratingPricer{}}, {models.ReServiceCodeDPK, &domesticPackPricer{}}, {models.ReServiceCodeDNPK, &domesticNTSPackPricer{}}, {models.ReServiceCodeDUPK, &domesticUnpackPricer{}}, diff --git a/pkg/services/ghcrateengine/shared.go b/pkg/services/ghcrateengine/shared.go index 1a76f817734..44eb2100124 100644 --- a/pkg/services/ghcrateengine/shared.go +++ b/pkg/services/ghcrateengine/shared.go @@ -15,6 +15,9 @@ const minIntlWeightHHG = unit.Pound(500) // minInternationalWeight is the minimum weight used in international calculations (weights below this are upgraded to the min) const minInternationalWeight = unit.Pound(500) +// minIntlExternalCrateBilledCubicFeet is the minimum billed cubic feet used in international external crate +const minIntlExternalCrateBilledCubicFeet = 4.00 + // dateInYear represents a specific date in a year (without caring what year it is) type dateInYear struct { month time.Month diff --git a/pkg/services/mocks/IntlCratingPricer.go b/pkg/services/mocks/IntlCratingPricer.go new file mode 100644 index 00000000000..0ada84deb77 --- /dev/null +++ b/pkg/services/mocks/IntlCratingPricer.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" +) + +// IntlCratingPricer is an autogenerated mock type for the IntlCratingPricer type +type IntlCratingPricer struct { + mock.Mock +} + +// Price provides a mock function with given fields: appCtx, contractCode, requestedPickupDate, billedCubicFeet, standaloneCrate, standaloneCrateCap, externalCrate, market +func (_m *IntlCratingPricer) Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, billedCubicFeet unit.CubicFeet, standaloneCrate bool, standaloneCrateCap unit.Cents, externalCrate bool, market models.Market) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, contractCode, requestedPickupDate, billedCubicFeet, standaloneCrate, standaloneCrateCap, externalCrate, market) + + 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.CubicFeet, bool, unit.Cents, bool, models.Market) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, contractCode, requestedPickupDate, billedCubicFeet, standaloneCrate, standaloneCrateCap, externalCrate, market) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, unit.CubicFeet, bool, unit.Cents, bool, models.Market) unit.Cents); ok { + r0 = rf(appCtx, contractCode, requestedPickupDate, billedCubicFeet, standaloneCrate, standaloneCrateCap, externalCrate, market) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, time.Time, unit.CubicFeet, bool, unit.Cents, bool, models.Market) services.PricingDisplayParams); ok { + r1 = rf(appCtx, contractCode, requestedPickupDate, billedCubicFeet, standaloneCrate, standaloneCrateCap, externalCrate, market) + } 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.CubicFeet, bool, unit.Cents, bool, models.Market) error); ok { + r2 = rf(appCtx, contractCode, requestedPickupDate, billedCubicFeet, standaloneCrate, standaloneCrateCap, externalCrate, market) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// PriceUsingParams provides a mock function with given fields: appCtx, params +func (_m *IntlCratingPricer) 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 +} + +// NewIntlCratingPricer creates a new instance of IntlCratingPricer. 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 NewIntlCratingPricer(t interface { + mock.TestingT + Cleanup(func()) +}) *IntlCratingPricer { + mock := &IntlCratingPricer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/services/mocks/IntlUncratingPricer.go b/pkg/services/mocks/IntlUncratingPricer.go new file mode 100644 index 00000000000..c54879fbce8 --- /dev/null +++ b/pkg/services/mocks/IntlUncratingPricer.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" +) + +// IntlUncratingPricer is an autogenerated mock type for the IntlUncratingPricer type +type IntlUncratingPricer struct { + mock.Mock +} + +// Price provides a mock function with given fields: appCtx, contractCode, requestedPickupDate, billedCubicFeet, market +func (_m *IntlUncratingPricer) Price(appCtx appcontext.AppContext, contractCode string, requestedPickupDate time.Time, billedCubicFeet unit.CubicFeet, market models.Market) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, contractCode, requestedPickupDate, billedCubicFeet, market) + + 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.CubicFeet, models.Market) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, contractCode, requestedPickupDate, billedCubicFeet, market) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time, unit.CubicFeet, models.Market) unit.Cents); ok { + r0 = rf(appCtx, contractCode, requestedPickupDate, billedCubicFeet, market) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, time.Time, unit.CubicFeet, models.Market) services.PricingDisplayParams); ok { + r1 = rf(appCtx, contractCode, requestedPickupDate, billedCubicFeet, market) + } 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.CubicFeet, models.Market) error); ok { + r2 = rf(appCtx, contractCode, requestedPickupDate, billedCubicFeet, market) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// PriceUsingParams provides a mock function with given fields: appCtx, params +func (_m *IntlUncratingPricer) 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 +} + +// NewIntlUncratingPricer creates a new instance of IntlUncratingPricer. 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 NewIntlUncratingPricer(t interface { + mock.TestingT + Cleanup(func()) +}) *IntlUncratingPricer { + mock := &IntlUncratingPricer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/pkg/services/mocks/MTOServiceItemCreator.go b/pkg/services/mocks/MTOServiceItemCreator.go index ae6e7d230e7..ab7cd5f1deb 100644 --- a/pkg/services/mocks/MTOServiceItemCreator.go +++ b/pkg/services/mocks/MTOServiceItemCreator.go @@ -8,6 +8,8 @@ import ( models "github.com/transcom/mymove/pkg/models" + unit "github.com/transcom/mymove/pkg/unit" + validate "github.com/gobuffalo/validate/v3" ) @@ -55,6 +57,34 @@ func (_m *MTOServiceItemCreator) CreateMTOServiceItem(appCtx appcontext.AppConte return r0, r1, r2 } +// FindEstimatedPrice provides a mock function with given fields: appCtx, serviceItem, mtoShipment +func (_m *MTOServiceItemCreator) FindEstimatedPrice(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, mtoShipment models.MTOShipment) (unit.Cents, error) { + ret := _m.Called(appCtx, serviceItem, mtoShipment) + + if len(ret) == 0 { + panic("no return value specified for FindEstimatedPrice") + } + + var r0 unit.Cents + var r1 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, *models.MTOServiceItem, models.MTOShipment) (unit.Cents, error)); ok { + return rf(appCtx, serviceItem, mtoShipment) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, *models.MTOServiceItem, models.MTOShipment) unit.Cents); ok { + r0 = rf(appCtx, serviceItem, mtoShipment) + } else { + r0 = ret.Get(0).(unit.Cents) + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, *models.MTOServiceItem, models.MTOShipment) error); ok { + r1 = rf(appCtx, serviceItem, mtoShipment) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // NewMTOServiceItemCreator creates a new instance of MTOServiceItemCreator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewMTOServiceItemCreator(t interface { diff --git a/pkg/services/mocks/MTOServiceItemUpdater.go b/pkg/services/mocks/MTOServiceItemUpdater.go index ed356fd7699..bbb6bd86828 100644 --- a/pkg/services/mocks/MTOServiceItemUpdater.go +++ b/pkg/services/mocks/MTOServiceItemUpdater.go @@ -138,6 +138,36 @@ func (_m *MTOServiceItemUpdater) UpdateMTOServiceItemBasic(appCtx appcontext.App return r0, r1 } +// UpdateMTOServiceItemPricingEstimate provides a mock function with given fields: appCtx, serviceItem, shipment, eTag +func (_m *MTOServiceItemUpdater) UpdateMTOServiceItemPricingEstimate(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, shipment models.MTOShipment, eTag string) (*models.MTOServiceItem, error) { + ret := _m.Called(appCtx, serviceItem, shipment, eTag) + + if len(ret) == 0 { + panic("no return value specified for UpdateMTOServiceItemPricingEstimate") + } + + var r0 *models.MTOServiceItem + var r1 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, *models.MTOServiceItem, models.MTOShipment, string) (*models.MTOServiceItem, error)); ok { + return rf(appCtx, serviceItem, shipment, eTag) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, *models.MTOServiceItem, models.MTOShipment, string) *models.MTOServiceItem); ok { + r0 = rf(appCtx, serviceItem, shipment, eTag) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.MTOServiceItem) + } + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, *models.MTOServiceItem, models.MTOShipment, string) error); ok { + r1 = rf(appCtx, serviceItem, shipment, eTag) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // UpdateMTOServiceItemPrime provides a mock function with given fields: appCtx, serviceItem, planner, shipment, eTag func (_m *MTOServiceItemUpdater) UpdateMTOServiceItemPrime(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, planner route.Planner, shipment models.MTOShipment, eTag string) (*models.MTOServiceItem, error) { ret := _m.Called(appCtx, serviceItem, planner, shipment, eTag) diff --git a/pkg/services/mocks/OrderFetcher.go b/pkg/services/mocks/OrderFetcher.go index c7eb39d01ce..012df5ccb1b 100644 --- a/pkg/services/mocks/OrderFetcher.go +++ b/pkg/services/mocks/OrderFetcher.go @@ -80,6 +80,43 @@ func (_m *OrderFetcher) ListAllOrderLocations(appCtx appcontext.AppContext, offi return r0, r1 } +// ListDestinationRequestsOrders provides a mock function with given fields: appCtx, officeUserID, role, params +func (_m *OrderFetcher) ListDestinationRequestsOrders(appCtx appcontext.AppContext, officeUserID uuid.UUID, role roles.RoleType, params *services.ListOrderParams) ([]models.Move, int, error) { + ret := _m.Called(appCtx, officeUserID, role, params) + + if len(ret) == 0 { + panic("no return value specified for ListDestinationRequestsOrders") + } + + var r0 []models.Move + var r1 int + var r2 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, uuid.UUID, roles.RoleType, *services.ListOrderParams) ([]models.Move, int, error)); ok { + return rf(appCtx, officeUserID, role, params) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, uuid.UUID, roles.RoleType, *services.ListOrderParams) []models.Move); ok { + r0 = rf(appCtx, officeUserID, role, params) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]models.Move) + } + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, uuid.UUID, roles.RoleType, *services.ListOrderParams) int); ok { + r1 = rf(appCtx, officeUserID, role, params) + } else { + r1 = ret.Get(1).(int) + } + + if rf, ok := ret.Get(2).(func(appcontext.AppContext, uuid.UUID, roles.RoleType, *services.ListOrderParams) error); ok { + r2 = rf(appCtx, officeUserID, role, params) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + // ListOrders provides a mock function with given fields: appCtx, officeUserID, role, params func (_m *OrderFetcher) ListOrders(appCtx appcontext.AppContext, officeUserID uuid.UUID, role roles.RoleType, params *services.ListOrderParams) ([]models.Move, int, error) { ret := _m.Called(appCtx, officeUserID, role, params) 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/TransportationOfficesFetcher.go b/pkg/services/mocks/TransportationOfficesFetcher.go index ad777d72b0a..d0c017b3e19 100644 --- a/pkg/services/mocks/TransportationOfficesFetcher.go +++ b/pkg/services/mocks/TransportationOfficesFetcher.go @@ -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/mocks/VLocation.go b/pkg/services/mocks/VLocation.go index 162924e8464..7c932ff7910 100644 --- a/pkg/services/mocks/VLocation.go +++ b/pkg/services/mocks/VLocation.go @@ -14,9 +14,16 @@ type VLocation struct { mock.Mock } -// GetLocationsByZipCityState provides a mock function with given fields: appCtx, search, exclusionStateFilters -func (_m *VLocation) GetLocationsByZipCityState(appCtx appcontext.AppContext, search string, exclusionStateFilters []string) (*models.VLocations, error) { - ret := _m.Called(appCtx, search, exclusionStateFilters) +// GetLocationsByZipCityState provides a mock function with given fields: appCtx, search, exclusionStateFilters, exactMatch +func (_m *VLocation) GetLocationsByZipCityState(appCtx appcontext.AppContext, search string, exclusionStateFilters []string, exactMatch ...bool) (*models.VLocations, error) { + _va := make([]interface{}, len(exactMatch)) + for _i := range exactMatch { + _va[_i] = exactMatch[_i] + } + var _ca []interface{} + _ca = append(_ca, appCtx, search, exclusionStateFilters) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) if len(ret) == 0 { panic("no return value specified for GetLocationsByZipCityState") @@ -24,19 +31,19 @@ func (_m *VLocation) GetLocationsByZipCityState(appCtx appcontext.AppContext, se var r0 *models.VLocations var r1 error - if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, []string) (*models.VLocations, error)); ok { - return rf(appCtx, search, exclusionStateFilters) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, []string, ...bool) (*models.VLocations, error)); ok { + return rf(appCtx, search, exclusionStateFilters, exactMatch...) } - if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, []string) *models.VLocations); ok { - r0 = rf(appCtx, search, exclusionStateFilters) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, []string, ...bool) *models.VLocations); ok { + r0 = rf(appCtx, search, exclusionStateFilters, exactMatch...) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*models.VLocations) } } - if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, []string) error); ok { - r1 = rf(appCtx, search, exclusionStateFilters) + if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, []string, ...bool) error); ok { + r1 = rf(appCtx, search, exclusionStateFilters, exactMatch...) } else { r1 = ret.Error(1) } diff --git a/pkg/services/move/move_fetcher.go b/pkg/services/move/move_fetcher.go index e3c4e0ad370..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) diff --git a/pkg/services/move/move_fetcher_test.go b/pkg/services/move/move_fetcher_test.go index 62b6551add5..6a6db7f28be 100644 --- a/pkg/services/move/move_fetcher_test.go +++ b/pkg/services/move/move_fetcher_test.go @@ -533,6 +533,7 @@ func (suite *MoveServiceSuite) TestMoveFetcherBulkAssignmentTOO() { Type: &factory.TransportationOffices.CounselingOffice, }, }, []roles.RoleType{roles.RoleTypeTOO}) + factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ { Model: models.Move{ @@ -545,6 +546,7 @@ func (suite *MoveServiceSuite) TestMoveFetcherBulkAssignmentTOO() { Type: &factory.TransportationOffices.CounselingOffice, }, }, nil) + factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ { Model: models.Move{ @@ -697,6 +699,7 @@ func (suite *MoveServiceSuite) TestMoveFetcherBulkAssignmentTOO() { }, }, }, nil) + moves, err := moveFetcher.FetchMovesForBulkAssignmentTaskOrder(suite.AppContextForTest(), "KKFA", officeUser.TransportationOffice.ID) suite.FatalNoError(err) suite.Equal(2, len(moves)) diff --git a/pkg/services/move/move_router.go b/pkg/services/move/move_router.go index 21dbd759282..930a3a46006 100644 --- a/pkg/services/move/move_router.go +++ b/pkg/services/move/move_router.go @@ -163,6 +163,10 @@ func (router moveRouter) needsServiceCounseling(appCtx appcontext.AppContext, mo return false, nil } + if move.IsPPMOnly() { + return true, nil + } + return originDutyLocation.ProvidesServicesCounseling, nil } diff --git a/pkg/services/move/move_router_test.go b/pkg/services/move/move_router_test.go index ee51c174859..a7d5489f931 100644 --- a/pkg/services/move/move_router_test.go +++ b/pkg/services/move/move_router_test.go @@ -283,6 +283,69 @@ func (suite *MoveServiceSuite) TestMoveSubmission() { }, }, nil) + shipment := factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusDraft, + ShipmentType: models.MTOShipmentTypeHHG, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + move.MTOShipments = models.MTOShipments{shipment} + + newSignedCertification := factory.BuildSignedCertification(nil, []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + }, nil) + err := moveRouter.Submit(suite.AppContextForTest(), &move, &newSignedCertification) + suite.NoError(err) + err = suite.DB().Where("move_id = $1", move.ID).First(&newSignedCertification) + suite.NoError(err) + suite.NotNil(newSignedCertification) + + err = suite.DB().Find(&move, move.ID) + suite.NoError(err) + suite.Equal(tt.moveStatus, move.Status) + }) + } + }) + + suite.Run("PPM moves are routed correctly and SignedCertification is created", func() { + // Under test: MoveRouter.Submit (Full PPM should always route to service counselor, never to office user) + // Set up: Create moves and SignedCertification + // Expected outcome: signed cert is created + // Expected outcome: Move status is set to needs service counseling for both true and false on origin providing service counseling + tests := []struct { + desc string + ProvidesServicesCounseling bool + moveStatus models.MoveStatus + }{ + {"Routes to Service Counseling", true, models.MoveStatusNeedsServiceCounseling}, + {"Routes to Service Counseling", false, models.MoveStatusNeedsServiceCounseling}, + } + for _, tt := range tests { + suite.Run(tt.desc, func() { + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + ProvidesServicesCounseling: tt.ProvidesServicesCounseling, + }, + Type: &factory.DutyLocations.OriginDutyLocation, + }, + { + Model: models.Move{ + Status: models.MoveStatusDRAFT, + }, + }, + }, nil) + shipment := factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{ { Model: models.MTOShipment{ @@ -408,7 +471,7 @@ func (suite *MoveServiceSuite) TestMoveSubmission() { err := moveRouter.Submit(suite.AppContextForTest(), &move, &newSignedCertification) suite.NoError(err) - suite.Equal(models.MoveStatusSUBMITTED, move.Status, "expected Submitted") + suite.Equal(models.MoveStatusNeedsServiceCounseling, move.Status, "expected Needs Service Counseling") suite.Equal(models.MTOShipmentStatusSubmitted, move.MTOShipments[0].Status, "expected Submitted") suite.Equal(models.PPMShipmentStatusSubmitted, move.MTOShipments[0].PPMShipment.Status, "expected Submitted") }) diff --git a/pkg/services/move/move_weights.go b/pkg/services/move/move_weights.go index 0c88fddefba..27e845be379 100644 --- a/pkg/services/move/move_weights.go +++ b/pkg/services/move/move_weights.go @@ -3,6 +3,7 @@ package move import ( "database/sql" "errors" + "math" "time" "github.com/gobuffalo/validate/v3" @@ -62,7 +63,7 @@ func shipmentHasReweighWeight(shipment models.MTOShipment) bool { } // return the lower weight of a shipment's actual weight and the reweighed weight -func lowerShipmentWeight(shipment models.MTOShipment) int { +func lowerShipmentActualWeight(shipment models.MTOShipment) int { actualWeight := 0 if shipment.PrimeActualWeight != nil { actualWeight = shipment.PrimeActualWeight.Int() @@ -78,6 +79,23 @@ func lowerShipmentWeight(shipment models.MTOShipment) int { return actualWeight } +// return the lower weight of a shipment's estimated weight and the reweighed weight +func lowerShipmentEstimatedWeight(shipment models.MTOShipment) int { + estimatedWeight := 0 + if shipment.PrimeEstimatedWeight != nil { + estimatedWeight = shipment.PrimeEstimatedWeight.Int() + } + + if shipmentHasReweighWeight(shipment) { + reweighWeight := shipment.Reweigh.Weight.Int() + if reweighWeight < estimatedWeight { + return reweighWeight + } + } + + return estimatedWeight +} + func (w moveWeights) CheckExcessWeight(appCtx appcontext.AppContext, moveID uuid.UUID, updatedShipment models.MTOShipment) (*models.Move, *validate.Errors, error) { db := appCtx.DB() var move models.Move @@ -244,10 +262,68 @@ func calculateSumOfWeights(move models.Move, updatedShipment *models.MTOShipment return sumOfWeights } +// GetAutoReweighShipments returns all shipments that need to be reweighed +func (w moveWeights) GetAutoReweighShipments(appCtx appcontext.AppContext, move *models.Move, updatedShipment *models.MTOShipment) (models.MTOShipments, error) { + if move == nil { + return nil, apperror.NewBadDataError("received a nil move, a move must be supplied for checking reweighs") + } + if updatedShipment == nil { + return nil, apperror.NewBadDataError("received a nil MTO shipment, an MTO shipment must be supplied for checking reweighs") + } + results := models.MTOShipments{} + + totalWeightAllowance, err := w.WeightAllotmentFetcher.GetWeightAllotment(appCtx, string(*move.Orders.Grade), move.Orders.OrdersType) + if err != nil { + return nil, err + } + + maxWeight := int(math.Round(float64(totalWeightAllowance.TotalWeightSelfPlusDependents) * 0.9)) + + totalActualWeight := 0 + totalEstimatedWeight := 0 + for i := range move.MTOShipments { + if move.MTOShipments[i].ShipmentType != models.MTOShipmentTypePPM && + availableShipmentStatus(move.MTOShipments[i].Status) && + move.MTOShipments[i].DeletedAt == nil && + updatedShipment.ID != move.MTOShipments[i].ID { + if move.MTOShipments[i].PrimeActualWeight != nil { + totalActualWeight += lowerShipmentActualWeight(move.MTOShipments[i]) + } + if move.MTOShipments[i].PrimeEstimatedWeight != nil { + totalEstimatedWeight += lowerShipmentEstimatedWeight(move.MTOShipments[i]) + } + results = append(results, move.MTOShipments[i]) + } else if move.MTOShipments[i].ID == updatedShipment.ID { + if updatedShipment.PrimeActualWeight != nil { + totalActualWeight += lowerShipmentActualWeight(*updatedShipment) + } + if updatedShipment.PrimeEstimatedWeight != nil { + totalEstimatedWeight += lowerShipmentEstimatedWeight(*updatedShipment) + } + results = append(results, *updatedShipment) + } + } + + // Check actual weight first + if int(totalActualWeight) >= maxWeight { + return results, nil + } + + // Check estimated weight second + if int(totalEstimatedWeight) >= maxWeight { + return results, nil + } + + return models.MTOShipments{}, nil +} + func (w moveWeights) CheckAutoReweigh(appCtx appcontext.AppContext, moveID uuid.UUID, updatedShipment *models.MTOShipment) (models.MTOShipments, error) { - db := appCtx.DB() + if updatedShipment == nil { + return nil, apperror.NewBadDataError("received a nil MTO shipment, an MTO shipment must be supplied for checking reweighs") + } + var move models.Move - err := db.Eager("MTOShipments", "MTOShipments.Reweigh", "Orders.Entitlement").Find(&move, moveID) + err := appCtx.DB().Eager("MTOShipments", "Orders", "Orders.Entitlement", "MTOShipments.ShipmentType", "MTOShipments.Status", "MTOShipments.DeletedAt", "MTOShipments.PrimeActualWeight", "MTOShipments.PrimeEstimatedWeight").Find(&move, moveID) if err != nil { switch err { case sql.ErrNoRows: @@ -265,48 +341,19 @@ func (w moveWeights) CheckAutoReweigh(appCtx appcontext.AppContext, moveID uuid. return nil, errors.New("could not determine excess weight entitlement without dependents authorization value") } - totalWeightAllowance, err := w.WeightAllotmentFetcher.GetWeightAllotment(appCtx, string(*move.Orders.Grade), move.Orders.OrdersType) + autoReweighShipments, err := w.GetAutoReweighShipments(appCtx, &move, updatedShipment) if err != nil { return nil, err } - overallWeightAllowance := totalWeightAllowance.TotalWeightSelf - if *move.Orders.Entitlement.DependentsAuthorized { - overallWeightAllowance = totalWeightAllowance.TotalWeightSelfPlusDependents - } - - moveWeightTotal := 0 - for _, shipment := range move.MTOShipments { - // We should avoid counting shipments that haven't been approved yet and will need to account for diversions - // and cancellations factoring into the weight total. - if availableShipmentStatus(shipment.Status) { - if shipment.ID != updatedShipment.ID { - moveWeightTotal += lowerShipmentWeight(shipment) - } else { - // the shipment being updated might have a reweigh that wasn't loaded - updatedShipment.Reweigh = shipment.Reweigh - moveWeightTotal += lowerShipmentWeight(*updatedShipment) + if len(autoReweighShipments) > 0 { + for _, shipment := range autoReweighShipments { + reweigh, err := w.ReweighRequestor.RequestShipmentReweigh(appCtx, shipment.ID, models.ReweighRequesterSystem) + if err != nil { + return nil, err } - } - } - autoReweighShipments := models.MTOShipments{} - // may need to take into account floating point precision here but should be dealing with whole numbers - if int(float32(overallWeightAllowance)*AutoReweighRequestThreshold) <= moveWeightTotal { - for _, shipment := range move.MTOShipments { - // We should avoid counting shipments that haven't been approved yet and will need to account for diversions - // and cancellations factoring into the weight total. - if availableShipmentStatus(shipment.Status) && (shipment.Reweigh == nil || shipment.Reweigh.ID == uuid.Nil) { - reweigh, err := w.ReweighRequestor.RequestShipmentReweigh(appCtx, shipment.ID, models.ReweighRequesterSystem) - if err != nil { - return nil, err - } - autoReweighShipments = append(autoReweighShipments, shipment) - // this may not be necessary depending on how the shipment is being updated/refetched elsewhere - if shipment.ID == updatedShipment.ID { - updatedShipment.Reweigh = reweigh - } - } + shipment.Reweigh = reweigh } } diff --git a/pkg/services/move/move_weights_test.go b/pkg/services/move/move_weights_test.go index 60d716c1241..59b1b14ab2b 100644 --- a/pkg/services/move/move_weights_test.go +++ b/pkg/services/move/move_weights_test.go @@ -5,6 +5,7 @@ import ( "github.com/gofrs/uuid" + "github.com/transcom/mymove/pkg/apperror" "github.com/transcom/mymove/pkg/auth" "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" @@ -515,8 +516,10 @@ func (suite *MoveServiceSuite) TestAutoReweigh() { ApplicationName: auth.OfficeApp, OfficeUserID: uuid.Must(uuid.NewV4()), }) - autoReweighShipments, err := moveWeights.CheckAutoReweigh(session, approvedMove.ID, &approvedShipment) + autoReweighShipments, err := moveWeights.CheckAutoReweigh(session, approvedMove.ID, &approvedShipment) + suite.NoError(err) + err = suite.DB().Eager("Reweigh").Reload(&approvedShipment) suite.NoError(err) suite.NotNil(approvedShipment.Reweigh) @@ -548,11 +551,14 @@ func (suite *MoveServiceSuite) TestAutoReweigh() { }, }, nil) - actualWeight := unit.Pound(7199) - approvedShipment.PrimeEstimatedWeight = &actualWeight + weight := unit.Pound(7199) + approvedShipment.PrimeActualWeight = &weight + approvedShipment.PrimeEstimatedWeight = &weight _, err := mockedWeightService.CheckAutoReweigh(suite.AppContextForTest(), approvedMove.ID, &approvedShipment) - suite.NoError(err) + err = suite.DB().Eager("Reweigh").Reload(&approvedShipment) + suite.NoError(err) + suite.Equal(uuid.Nil, approvedShipment.Reweigh.ID) mockedReweighRequestor.AssertNotCalled(suite.T(), "RequestShipmentReweigh") }) @@ -599,7 +605,8 @@ func (suite *MoveServiceSuite) TestAutoReweigh() { OfficeUserID: uuid.Must(uuid.NewV4()), }) autoReweighShipments, err := moveWeights.CheckAutoReweigh(session, approvedMove.ID, &approvedShipment) - + suite.NoError(err) + err = suite.DB().Eager("Reweigh").Reload(&approvedShipment) suite.NoError(err) suite.NotNil(approvedShipment.Reweigh) @@ -624,15 +631,16 @@ func (suite *MoveServiceSuite) TestAutoReweigh() { approvedMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) now := time.Now() pickupDate := now.AddDate(0, 0, 10) - actualWeight := unit.Pound(3600) + weight := unit.Pound(3600) existingShipment := factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{ { Model: models.MTOShipment{ - Status: models.MTOShipmentStatusCanceled, - ApprovedDate: &now, - ScheduledPickupDate: &pickupDate, - PrimeActualWeight: &actualWeight, + Status: models.MTOShipmentStatusCanceled, + ApprovedDate: &now, + ScheduledPickupDate: &pickupDate, + PrimeEstimatedWeight: &weight, + PrimeActualWeight: &weight, }, }, { @@ -654,9 +662,11 @@ func (suite *MoveServiceSuite) TestAutoReweigh() { }, }, nil) - approvedShipment.PrimeActualWeight = &actualWeight + approvedShipment.PrimeEstimatedWeight = &weight + approvedShipment.PrimeActualWeight = &weight _, err := mockedWeightService.CheckAutoReweigh(suite.AppContextForTest(), approvedMove.ID, &approvedShipment) - + suite.NoError(err) + err = suite.DB().Eager("Reweigh").Reload(&approvedShipment) suite.NoError(err) err = suite.DB().Eager("Reweigh").Reload(&existingShipment) @@ -666,7 +676,7 @@ func (suite *MoveServiceSuite) TestAutoReweigh() { mockedReweighRequestor.AssertNotCalled(suite.T(), "RequestShipmentReweigh") }) - suite.Run("uses lower reweigh weight on shipments that already have reweighs", func() { + suite.Run("uses lower reweigh weight (based on actual weight) on shipments that already have reweighs", func() { mockedReweighRequestor := mocks.ShipmentReweighRequester{} mockedWeightService := NewMoveWeights(&mockedReweighRequestor, waf) approvedMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) @@ -726,12 +736,89 @@ func (suite *MoveServiceSuite) TestAutoReweigh() { }, nil) approvedShipment.PrimeActualWeight = &actualWeight - _, err := mockedWeightService.CheckAutoReweigh(suite.AppContextForTest(), approvedMove.ID, &approvedShipment) + err := suite.DB().Eager("Reweigh").Reload(&approvedShipment) suite.NoError(err) err = suite.DB().Eager("Reweigh").Reload(&existingShipment) suite.NoError(err) + + _, err = mockedWeightService.CheckAutoReweigh(suite.AppContextForTest(), approvedMove.ID, &approvedShipment) + suite.NoError(err) + suite.Equal(uuid.Nil, existingShipment.Reweigh.ID) + suite.Equal(uuid.Nil, approvedShipment.Reweigh.ID) + mockedReweighRequestor.AssertNotCalled(suite.T(), "RequestShipmentReweigh") + }) + + suite.Run("uses lower reweigh weight (based on estimated weight) on shipments that already have reweighs", func() { + mockedReweighRequestor := mocks.ShipmentReweighRequester{} + mockedWeightService := NewMoveWeights(&mockedReweighRequestor, waf) + approvedMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + + now := time.Now() + pickupDate := now.AddDate(0, 0, 10) + estimatedWeight := unit.Pound(2400) + existingShipment := factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusApproved, + ApprovedDate: &now, + ScheduledPickupDate: &pickupDate, + PrimeEstimatedWeight: &estimatedWeight, + }, + }, + { + Model: approvedMove, + LinkOnly: true, + }, + }, nil) + + reweighedShipment := factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusApproved, + ApprovedDate: &now, + ScheduledPickupDate: &pickupDate, + PrimeEstimatedWeight: &estimatedWeight, + }, + }, + { + Model: approvedMove, + LinkOnly: true, + }, + }, nil) + reweighWeight := unit.Pound(2399) + testdatagen.MakeReweigh(suite.DB(), testdatagen.Assertions{ + Reweigh: models.Reweigh{ + Weight: &reweighWeight, + }, + MTOShipment: reweighedShipment, + }) + + approvedShipment := factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusApproved, + ApprovedDate: &now, + ScheduledPickupDate: &pickupDate, + }, + }, + { + Model: approvedMove, + LinkOnly: true, + }, + }, nil) + + approvedShipment.PrimeEstimatedWeight = &estimatedWeight + + err := suite.DB().Eager("Reweigh").Reload(&approvedShipment) + suite.NoError(err) + + err = suite.DB().Eager("Reweigh").Reload(&existingShipment) + suite.NoError(err) + + _, err = mockedWeightService.CheckAutoReweigh(suite.AppContextForTest(), approvedMove.ID, &approvedShipment) + suite.NoError(err) suite.Equal(uuid.Nil, existingShipment.Reweigh.ID) suite.Equal(uuid.Nil, approvedShipment.Reweigh.ID) mockedReweighRequestor.AssertNotCalled(suite.T(), "RequestShipmentReweigh") @@ -758,4 +845,21 @@ func (suite *MoveServiceSuite) TestAutoReweigh() { _, err = moveWeights.CheckAutoReweigh(suite.AppContextForTest(), approvedMove.ID, &models.MTOShipment{}) suite.EqualError(err, "could not determine excess weight entitlement without dependents authorization value") }) + + suite.Run("returns error if can't find move when checking for auto-reweigh", func() { + randomID, err := uuid.NewV4() + suite.NoError(err) + _, err = moveWeights.CheckAutoReweigh(suite.AppContextForTest(), randomID, &models.MTOShipment{}) + suite.EqualError(err, apperror.NewNotFoundError(randomID, "looking for Move").Error()) + }) + + suite.Run("returns error if shipment returns nil when checking for auto-reweigh", func() { + approvedMove := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + + err := suite.DB().Save(approvedMove.Orders.Entitlement) + suite.NoError(err) + + _, err = moveWeights.CheckAutoReweigh(suite.AppContextForTest(), approvedMove.ID, nil) + suite.EqualError(err, apperror.NewBadDataError("received a nil MTO shipment, an MTO shipment must be supplied for checking reweighs").Error()) + }) } diff --git a/pkg/services/move_history/move_history_fetcher_test.go b/pkg/services/move_history/move_history_fetcher_test.go index eac2db729fc..9900d97cd1a 100644 --- a/pkg/services/move_history/move_history_fetcher_test.go +++ b/pkg/services/move_history/move_history_fetcher_test.go @@ -379,7 +379,7 @@ func (suite *MoveHistoryServiceSuite) TestMoveHistoryFetcherScenarios() { mock.Anything, false, ).Return(400, nil) - updater := mtoserviceitem.NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher) + updater := mtoserviceitem.NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) move := factory.BuildApprovalsRequestedMove(suite.DB(), nil, nil) serviceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ { 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 b53b8351420..ff4266005ac 100644 --- a/pkg/services/move_task_order/move_task_order_fetcher.go +++ b/pkg/services/move_task_order/move_task_order_fetcher.go @@ -199,6 +199,16 @@ func (f moveTaskOrderFetcher) FetchMoveTaskOrder(appCtx appcontext.AppContext, s } } + // Now that we have the move and order, construct the allotment (hhg allowance) + // Only fetch if grade is not nil + if mto.Orders.Grade != nil { + allotment, err := f.waf.GetWeightAllotment(appCtx, string(*mto.Orders.Grade), mto.Orders.OrdersType) + if err != nil { + return nil, err + } + mto.Orders.Entitlement.WeightAllotted = &allotment + } + for i := range mto.MTOShipments { var nonDeletedAgents models.MTOAgents loadErr := appCtx.DB(). @@ -213,16 +223,6 @@ func (f moveTaskOrderFetcher) FetchMoveTaskOrder(appCtx appcontext.AppContext, s 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 { - allotment, err := f.waf.GetWeightAllotment(appCtx, string(*mto.Orders.Grade), mto.Orders.OrdersType) - if err != nil { - return nil, err - } - mto.Orders.Entitlement.WeightAllotted = &allotment - } - // Due to a bug in Pop for EagerPreload the New Address of the DeliveryAddressUpdate and the PortLocation (City, Country, UsPostRegionCity.UsPostRegion.State") must be loaded manually. // The bug occurs in EagerPreload when there are two or more eager paths with 3+ levels // where the first 2 levels match. For example: @@ -352,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/mto_service_item.go b/pkg/services/mto_service_item.go index 25926ae3fca..0931fcbefc7 100644 --- a/pkg/services/mto_service_item.go +++ b/pkg/services/mto_service_item.go @@ -9,6 +9,7 @@ import ( "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/route" + "github.com/transcom/mymove/pkg/unit" ) // MTOServiceItemFetcher is the exported interface for fetching a mto service item @@ -23,6 +24,7 @@ type MTOServiceItemFetcher interface { //go:generate mockery --name MTOServiceItemCreator type MTOServiceItemCreator interface { CreateMTOServiceItem(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem) (*models.MTOServiceItems, *validate.Errors, error) + FindEstimatedPrice(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, mtoShipment models.MTOShipment) (unit.Cents, error) } // MTOServiceItemUpdater is the exported interface for updating an mto service item @@ -32,6 +34,7 @@ type MTOServiceItemUpdater interface { ApproveOrRejectServiceItem(appCtx appcontext.AppContext, mtoServiceItemID uuid.UUID, status models.MTOServiceItemStatus, rejectionReason *string, eTag string) (*models.MTOServiceItem, error) UpdateMTOServiceItem(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, eTag string, validator string) (*models.MTOServiceItem, error) UpdateMTOServiceItemBasic(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, eTag string) (*models.MTOServiceItem, error) + UpdateMTOServiceItemPricingEstimate(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, shipment models.MTOShipment, eTag string) (*models.MTOServiceItem, error) UpdateMTOServiceItemPrime(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, planner route.Planner, shipment models.MTOShipment, eTag string) (*models.MTOServiceItem, error) ConvertItemToCustomerExpense(appCtx appcontext.AppContext, shipment *models.MTOShipment, customerExpenseReason *string, convertToCustomerExpense bool) (*models.MTOServiceItem, error) } diff --git a/pkg/services/mto_service_item/mto_service_item_creator.go b/pkg/services/mto_service_item/mto_service_item_creator.go index e71a4d5249d..71be9917e5b 100644 --- a/pkg/services/mto_service_item/mto_service_item_creator.go +++ b/pkg/services/mto_service_item/mto_service_item_creator.go @@ -39,7 +39,8 @@ type mtoServiceItemCreator struct { fuelSurchargePricer services.FuelSurchargePricer } -func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, mtoShipment models.MTOShipment) (unit.Cents, error) { +// FindEstimatedPrice finds the estimated price for a service item +func (o *mtoServiceItemCreator) FindEstimatedPrice(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, mtoShipment models.MTOShipment) (unit.Cents, error) { if serviceItem.ReService.Code == models.ReServiceCodeDOP || serviceItem.ReService.Code == models.ReServiceCodeDPK || serviceItem.ReService.Code == models.ReServiceCodeDDP || @@ -55,7 +56,8 @@ func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, requestedPickupDate := *mtoShipment.RequestedPickupDate currTime := time.Now() var distance int - primeEstimatedWeight := *mtoShipment.PrimeEstimatedWeight + + adjustedWeight := GetAdjustedWeight(*mtoShipment.PrimeEstimatedWeight, mtoShipment.ShipmentType == models.MTOShipmentTypeUnaccompaniedBaggage) contractCode, err := FetchContractCode(appCtx, currTime) if err != nil { @@ -74,7 +76,7 @@ func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, return 0, err } - price, _, err = o.originPricer.Price(appCtx, contractCode, requestedPickupDate, *mtoShipment.PrimeEstimatedWeight, domesticServiceArea.ServiceArea, isPPM) + price, _, err = o.originPricer.Price(appCtx, contractCode, requestedPickupDate, *adjustedWeight, domesticServiceArea.ServiceArea, isPPM) if err != nil { return 0, err } @@ -87,7 +89,7 @@ func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, servicesScheduleOrigin := domesticServiceArea.ServicesSchedule - price, _, err = o.packPricer.Price(appCtx, contractCode, requestedPickupDate, *mtoShipment.PrimeEstimatedWeight, servicesScheduleOrigin, isPPM) + price, _, err = o.packPricer.Price(appCtx, contractCode, requestedPickupDate, *adjustedWeight, servicesScheduleOrigin, isPPM) if err != nil { return 0, err } @@ -102,7 +104,7 @@ func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, } } - price, _, err = o.destinationPricer.Price(appCtx, contractCode, requestedPickupDate, *mtoShipment.PrimeEstimatedWeight, domesticServiceArea.ServiceArea, isPPM) + price, _, err = o.destinationPricer.Price(appCtx, contractCode, requestedPickupDate, *adjustedWeight, domesticServiceArea.ServiceArea, isPPM) if err != nil { return 0, err } @@ -115,7 +117,7 @@ func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, serviceScheduleDestination := domesticServiceArea.ServicesSchedule - price, _, err = o.unpackPricer.Price(appCtx, contractCode, requestedPickupDate, *mtoShipment.PrimeEstimatedWeight, serviceScheduleDestination, isPPM) + price, _, err = o.unpackPricer.Price(appCtx, contractCode, requestedPickupDate, *adjustedWeight, serviceScheduleDestination, isPPM) if err != nil { return 0, err } @@ -133,7 +135,7 @@ func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, return 0, err } } - price, _, err = o.linehaulPricer.Price(appCtx, contractCode, requestedPickupDate, unit.Miles(distance), *mtoShipment.PrimeEstimatedWeight, domesticServiceArea.ServiceArea, isPPM) + price, _, err = o.linehaulPricer.Price(appCtx, contractCode, requestedPickupDate, unit.Miles(distance), *adjustedWeight, domesticServiceArea.ServiceArea, isPPM) if err != nil { return 0, err } @@ -149,7 +151,7 @@ func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, return 0, err } } - price, _, err = o.shorthaulPricer.Price(appCtx, contractCode, requestedPickupDate, unit.Miles(distance), *mtoShipment.PrimeEstimatedWeight, domesticServiceArea.ServiceArea) + price, _, err = o.shorthaulPricer.Price(appCtx, contractCode, requestedPickupDate, unit.Miles(distance), *adjustedWeight, domesticServiceArea.ServiceArea) if err != nil { return 0, err } @@ -173,7 +175,7 @@ func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, } } - fscWeightBasedDistanceMultiplier, err := LookupFSCWeightBasedDistanceMultiplier(appCtx, primeEstimatedWeight) + fscWeightBasedDistanceMultiplier, err := LookupFSCWeightBasedDistanceMultiplier(appCtx, *adjustedWeight) if err != nil { return 0, err } @@ -185,7 +187,7 @@ func (o *mtoServiceItemCreator) findEstimatedPrice(appCtx appcontext.AppContext, if err != nil { return 0, err } - price, _, err = o.fuelSurchargePricer.Price(appCtx, pickupDateForFSC, unit.Miles(distance), primeEstimatedWeight, fscWeightBasedDistanceMultiplierFloat, eiaFuelPrice, isPPM) + price, _, err = o.fuelSurchargePricer.Price(appCtx, pickupDateForFSC, unit.Miles(distance), *adjustedWeight, fscWeightBasedDistanceMultiplierFloat, eiaFuelPrice, isPPM) if err != nil { return 0, err } @@ -448,22 +450,35 @@ func (o *mtoServiceItemCreator) CreateMTOServiceItem(appCtx appcontext.AppContex // checking to see if the service item being created is a destination SIT // if so, we want the destination address to be the same as the shipment's // which will later populate the additional dest SIT service items as well - if serviceItem.ReService.Code == models.ReServiceCodeDDFSIT && mtoShipment.DestinationAddressID != nil { + if (serviceItem.ReService.Code == models.ReServiceCodeDDFSIT || serviceItem.ReService.Code == models.ReServiceCodeIDFSIT) && + mtoShipment.DestinationAddressID != nil { serviceItem.SITDestinationFinalAddress = mtoShipment.DestinationAddress serviceItem.SITDestinationFinalAddressID = mtoShipment.DestinationAddressID } - if serviceItem.ReService.Code == models.ReServiceCodeDOASIT { + if serviceItem.ReService.Code == models.ReServiceCodeDOASIT || serviceItem.ReService.Code == models.ReServiceCodeIOASIT { + // validation mappings // DOASIT must be associated with shipment that has DOFSIT - serviceItem, err = o.validateSITStandaloneServiceItem(appCtx, serviceItem, models.ReServiceCodeDOFSIT) + // IOASIT must be associated with shipment that has IOFSIT + m := make(map[models.ReServiceCode]models.ReServiceCode) + m[models.ReServiceCodeDOASIT] = models.ReServiceCodeDOFSIT + m[models.ReServiceCodeIOASIT] = models.ReServiceCodeIOFSIT + + serviceItem, err = o.validateSITStandaloneServiceItem(appCtx, serviceItem, m[serviceItem.ReService.Code]) if err != nil { return nil, nil, err } } - if serviceItem.ReService.Code == models.ReServiceCodeDDASIT { + if serviceItem.ReService.Code == models.ReServiceCodeDDASIT || serviceItem.ReService.Code == models.ReServiceCodeIDASIT { + // validation mappings // DDASIT must be associated with shipment that has DDFSIT - serviceItem, err = o.validateSITStandaloneServiceItem(appCtx, serviceItem, models.ReServiceCodeDDFSIT) + // IDASIT must be associated with shipment that has IDFSIT + m := make(map[models.ReServiceCode]models.ReServiceCode) + m[models.ReServiceCodeDDASIT] = models.ReServiceCodeDDFSIT + m[models.ReServiceCodeIDASIT] = models.ReServiceCodeIDFSIT + + serviceItem, err = o.validateSITStandaloneServiceItem(appCtx, serviceItem, m[serviceItem.ReService.Code]) if err != nil { return nil, nil, err } @@ -478,7 +493,9 @@ func (o *mtoServiceItemCreator) CreateMTOServiceItem(appCtx appcontext.AppContex } if serviceItem.ReService.Code == models.ReServiceCodeDDDSIT || serviceItem.ReService.Code == models.ReServiceCodeDOPSIT || - serviceItem.ReService.Code == models.ReServiceCodeDDSFSC || serviceItem.ReService.Code == models.ReServiceCodeDOSFSC { + serviceItem.ReService.Code == models.ReServiceCodeDDSFSC || serviceItem.ReService.Code == models.ReServiceCodeDOSFSC || + serviceItem.ReService.Code == models.ReServiceCodeIDDSIT || serviceItem.ReService.Code == models.ReServiceCodeIOPSIT || + serviceItem.ReService.Code == models.ReServiceCodeIDSFSC || serviceItem.ReService.Code == models.ReServiceCodeIOSFSC { verrs = validate.NewErrors() verrs.Add("reServiceCode", fmt.Sprintf("%s cannot be created", serviceItem.ReService.Code)) return nil, nil, apperror.NewInvalidInputError(serviceItem.ID, nil, verrs, @@ -486,15 +503,19 @@ func (o *mtoServiceItemCreator) CreateMTOServiceItem(appCtx appcontext.AppContex } updateShipmentPickupAddress := false - if serviceItem.ReService.Code == models.ReServiceCodeDDFSIT || serviceItem.ReService.Code == models.ReServiceCodeDOFSIT { + if serviceItem.ReService.Code == models.ReServiceCodeDDFSIT || + serviceItem.ReService.Code == models.ReServiceCodeDOFSIT || + serviceItem.ReService.Code == models.ReServiceCodeIDFSIT || + serviceItem.ReService.Code == models.ReServiceCodeIOFSIT { extraServiceItems, errSIT := o.validateFirstDaySITServiceItem(appCtx, serviceItem) if errSIT != nil { return nil, nil, errSIT } - // update HHG origin address for ReServiceCodeDOFSIT service item - if serviceItem.ReService.Code == models.ReServiceCodeDOFSIT { - // When creating a DOFSIT, the prime must provide an HHG actual address for the move/shift in origin (pickup address) + // update HHG origin address for ReServiceCodeDOFSIT/ReServiceCodeIOFSIT service item + if serviceItem.ReService.Code == models.ReServiceCodeDOFSIT || + serviceItem.ReService.Code == models.ReServiceCodeIOFSIT { + // When creating a DOFSIT/IOFSIT, the prime must provide an HHG actual address for the move/shift in origin (pickup address) if serviceItem.SITOriginHHGActualAddress == nil { verrs = validate.NewErrors() verrs.Add("reServiceCode", fmt.Sprintf("%s cannot be created", serviceItem.ReService.Code)) @@ -543,13 +564,16 @@ func (o *mtoServiceItemCreator) CreateMTOServiceItem(appCtx appcontext.AppContex // changes were made to the shipment, needs to be saved to the database updateShipmentPickupAddress = true - // Find the DOPSIT service item and update the SIT related address fields. These fields - // will be used for pricing when a payment request is created for DOPSIT + // Find the DOPSIT/IOPSIT service item and update the SIT related address fields. These fields + // will be used for pricing when a payment request is created for DOPSIT/IOPSIT for itemIndex := range *extraServiceItems { extraServiceItem := &(*extraServiceItems)[itemIndex] if extraServiceItem.ReService.Code == models.ReServiceCodeDOPSIT || extraServiceItem.ReService.Code == models.ReServiceCodeDOASIT || - extraServiceItem.ReService.Code == models.ReServiceCodeDOSFSC { + extraServiceItem.ReService.Code == models.ReServiceCodeDOSFSC || + extraServiceItem.ReService.Code == models.ReServiceCodeIOPSIT || + extraServiceItem.ReService.Code == models.ReServiceCodeIOASIT || + extraServiceItem.ReService.Code == models.ReServiceCodeIOSFSC { extraServiceItem.SITOriginHHGActualAddress = serviceItem.SITOriginHHGActualAddress extraServiceItem.SITOriginHHGActualAddressID = serviceItem.SITOriginHHGActualAddressID extraServiceItem.SITOriginHHGOriginalAddress = serviceItem.SITOriginHHGOriginalAddress @@ -559,12 +583,17 @@ func (o *mtoServiceItemCreator) CreateMTOServiceItem(appCtx appcontext.AppContex } // make sure SITDestinationFinalAddress is the same for all destination SIT related service item - if serviceItem.ReService.Code == models.ReServiceCodeDDFSIT && serviceItem.SITDestinationFinalAddress != nil { + if (serviceItem.ReService.Code == models.ReServiceCodeDDFSIT || serviceItem.ReService.Code == models.ReServiceCodeIDFSIT) && + serviceItem.SITDestinationFinalAddress != nil { for itemIndex := range *extraServiceItems { extraServiceItem := &(*extraServiceItems)[itemIndex] + // handle both domestic and internationl(OCONUS) if extraServiceItem.ReService.Code == models.ReServiceCodeDDDSIT || extraServiceItem.ReService.Code == models.ReServiceCodeDDASIT || - extraServiceItem.ReService.Code == models.ReServiceCodeDDSFSC { + extraServiceItem.ReService.Code == models.ReServiceCodeDDSFSC || + extraServiceItem.ReService.Code == models.ReServiceCodeIDDSIT || + extraServiceItem.ReService.Code == models.ReServiceCodeIDASIT || + extraServiceItem.ReService.Code == models.ReServiceCodeIDSFSC { extraServiceItem.SITDestinationFinalAddress = serviceItem.SITDestinationFinalAddress extraServiceItem.SITDestinationFinalAddressID = serviceItem.SITDestinationFinalAddressID } @@ -574,11 +603,13 @@ func (o *mtoServiceItemCreator) CreateMTOServiceItem(appCtx appcontext.AppContex milesCalculated, errCalcSITDelivery := o.calculateSITDeliveryMiles(appCtx, serviceItem, mtoShipment) // only calculate SITDeliveryMiles for DOPSIT and DOSFSC origin service items - if serviceItem.ReService.Code == models.ReServiceCodeDOFSIT && milesCalculated != 0 { + if (serviceItem.ReService.Code == models.ReServiceCodeDOFSIT || serviceItem.ReService.Code == models.ReServiceCodeIOFSIT) && + milesCalculated != 0 { for itemIndex := range *extraServiceItems { extraServiceItem := &(*extraServiceItems)[itemIndex] - if extraServiceItem.ReService.Code == models.ReServiceCodeDOPSIT || - extraServiceItem.ReService.Code == models.ReServiceCodeDOSFSC { + if extraServiceItem.ReService.Code == models.ReServiceCodeDOPSIT || extraServiceItem.ReService.Code == models.ReServiceCodeIOPSIT || + extraServiceItem.ReService.Code == models.ReServiceCodeDOSFSC || + extraServiceItem.ReService.Code == models.ReServiceCodeIOSFSC { if milesCalculated > 0 && errCalcSITDelivery == nil { extraServiceItem.SITDeliveryMiles = &milesCalculated } @@ -587,11 +618,12 @@ func (o *mtoServiceItemCreator) CreateMTOServiceItem(appCtx appcontext.AppContex } // only calculate SITDeliveryMiles for DDDSIT and DDSFSC destination service items - if serviceItem.ReService.Code == models.ReServiceCodeDDFSIT && milesCalculated != 0 { + if (serviceItem.ReService.Code == models.ReServiceCodeDDFSIT || serviceItem.ReService.Code == models.ReServiceCodeIDFSIT) && milesCalculated != 0 { for itemIndex := range *extraServiceItems { extraServiceItem := &(*extraServiceItems)[itemIndex] - if extraServiceItem.ReService.Code == models.ReServiceCodeDDDSIT || - extraServiceItem.ReService.Code == models.ReServiceCodeDDSFSC { + if extraServiceItem.ReService.Code == models.ReServiceCodeDDDSIT || extraServiceItem.ReService.Code == models.ReServiceCodeIDDSIT || + extraServiceItem.ReService.Code == models.ReServiceCodeDDSFSC || + extraServiceItem.ReService.Code == models.ReServiceCodeIDSFSC { if milesCalculated > 0 && errCalcSITDelivery == nil { extraServiceItem.SITDeliveryMiles = &milesCalculated } @@ -606,8 +638,8 @@ func (o *mtoServiceItemCreator) CreateMTOServiceItem(appCtx appcontext.AppContex // DLH, DPK, DOP, DDP, DUPK // NTS-release requested pickup dates are for handle out, their pricing is handled differently as their locations are based on storage facilities, not pickup locations - if mtoShipment.PrimeEstimatedWeight != nil && mtoShipment.RequestedPickupDate != nil && mtoShipment.ShipmentType != models.MTOShipmentTypeHHGOutOfNTS { - serviceItemEstimatedPrice, err := o.findEstimatedPrice(appCtx, serviceItem, mtoShipment) + if mtoShipment.PrimeEstimatedWeight != nil && mtoShipment.RequestedPickupDate != nil { + serviceItemEstimatedPrice, err := o.FindEstimatedPrice(appCtx, serviceItem, mtoShipment) if serviceItemEstimatedPrice != 0 && err == nil { serviceItem.PricingEstimate = &serviceItemEstimatedPrice } @@ -673,7 +705,7 @@ func (o *mtoServiceItemCreator) CreateMTOServiceItem(appCtx appcontext.AppContex } if mtoShipment.MarketCode == models.MarketCodeInternational { - createdInternationalServiceItemIds, err = models.CreateInternationalAccessorialServiceItemsForShipment(appCtx.DB(), *serviceItem.MTOShipmentID, models.MTOServiceItems{*serviceItem}) + createdInternationalServiceItemIds, err = models.CreateInternationalAccessorialServiceItemsForShipment(appCtx.DB(), *requestedServiceItem.MTOShipmentID, models.MTOServiceItems{*requestedServiceItem}) if err != nil { return err } @@ -908,6 +940,12 @@ func (o *mtoServiceItemCreator) validateFirstDaySITServiceItem(appCtx appcontext return nil, err } + //SIT Entry Date must be before SIT Departure Date + err = o.checkSITEntryDateBeforeDepartureDate(serviceItem) + if err != nil { + return nil, err + } + verrs := validate.NewErrors() // check if the address IDs are nil @@ -932,6 +970,10 @@ func (o *mtoServiceItemCreator) validateFirstDaySITServiceItem(appCtx appcontext reServiceCodes = append(reServiceCodes, models.ReServiceCodeDDASIT, models.ReServiceCodeDDDSIT, models.ReServiceCodeDDSFSC) case models.ReServiceCodeDOFSIT: reServiceCodes = append(reServiceCodes, models.ReServiceCodeDOASIT, models.ReServiceCodeDOPSIT, models.ReServiceCodeDOSFSC) + case models.ReServiceCodeIDFSIT: + reServiceCodes = append(reServiceCodes, models.ReServiceCodeIDASIT, models.ReServiceCodeIDDSIT, models.ReServiceCodeIDSFSC) + case models.ReServiceCodeIOFSIT: + reServiceCodes = append(reServiceCodes, models.ReServiceCodeIOASIT, models.ReServiceCodeIOPSIT, models.ReServiceCodeIOSFSC) default: verrs := validate.NewErrors() verrs.Add("reServiceCode", fmt.Sprintf("%s invalid code", serviceItem.ReService.Code)) @@ -952,3 +994,22 @@ func (o *mtoServiceItemCreator) validateFirstDaySITServiceItem(appCtx appcontext return &extraServiceItems, nil } + +// Get Adjusted weight for pricing. Returns the weight at 110% or the minimum billable weight whichever is higher, unless it's 0 +func GetAdjustedWeight(incomingWeight unit.Pound, isUB bool) *unit.Pound { + // minimum weight billed by GHC is 500 lbs unless it's Unaccompanied Baggage (UB) + minimumBilledWeight := unit.Pound(500) + if isUB { + minimumBilledWeight = unit.Pound(300) + } + + // add 110% modifier to billable weight + newWeight := (int(incomingWeight.Float64() * 1.1)) + adjustedWeight := (*unit.Pound)(&newWeight) + + // if the adjusted weight is less than the minimum billable weight but is nonzero, set it to the minimum weight billed + if *adjustedWeight < minimumBilledWeight && *adjustedWeight > 0 { + *adjustedWeight = minimumBilledWeight + } + return adjustedWeight +} 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 d754d1aa00f..7e560cd4ea5 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 @@ -112,6 +112,45 @@ func (suite *MTOServiceItemServiceSuite) buildValidDDFSITServiceItemWithValidMov return serviceItem } +func (suite *MTOServiceItemServiceSuite) buildValidIDFSITServiceItemWithValidMove() models.MTOServiceItem { + move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + dimension := models.MTOServiceItemDimension{ + Type: models.DimensionTypeItem, + Length: 12000, + Height: 12000, + Width: 12000, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + reServiceIDFSIT := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeIDFSIT) + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + }, + }, + }, nil) + destAddress := factory.BuildDefaultAddress(suite.DB()) + + serviceItem := models.MTOServiceItem{ + MoveTaskOrderID: move.ID, + MoveTaskOrder: move, + ReService: reServiceIDFSIT, + MTOShipmentID: &shipment.ID, + MTOShipment: shipment, + Dimensions: models.MTOServiceItemDimensions{dimension}, + Status: models.MTOServiceItemStatusSubmitted, + SITDestinationFinalAddressID: &destAddress.ID, + SITDestinationFinalAddress: &destAddress, + } + + return serviceItem +} + func (suite *MTOServiceItemServiceSuite) buildValidDOSHUTServiceItemWithValidMove() models.MTOServiceItem { move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) reServiceDOSHUT := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDOSHUT) @@ -281,6 +320,60 @@ func (suite *MTOServiceItemServiceSuite) TestCreateMTOServiceItem() { suite.Equal(numDDSFSCFound, 1) }) + suite.Run("200 Success - International Destination SIT Service Item Creation", func() { + + // TESTCASE SCENARIO + // Under test: CreateMTOServiceItem function + // Set up: We create an approved move and attempt to create IDFSIT service item on it. Includes Dimensions + // and a SITDestinationFinalAddress + // Expected outcome: + // 4 SIT items are created, status of move is APPROVALS_REQUESTED + + sitServiceItem := suite.buildValidIDFSITServiceItemWithValidMove() + sitMove := sitServiceItem.MoveTaskOrder + sitShipment := sitServiceItem.MTOShipment + + createdServiceItems, verrs, err := creator.CreateMTOServiceItem(suite.AppContextForTest(), &sitServiceItem) + suite.NoError(err) + suite.Nil(verrs) + suite.NotNil(createdServiceItems) + + var foundMove models.Move + err = suite.DB().Find(&foundMove, sitMove.ID) + suite.NoError(err) + + createdServiceItemList := *createdServiceItems + suite.Equal(len(createdServiceItemList), 4) + suite.Equal(models.MoveStatusAPPROVALSREQUESTED, foundMove.Status) + + numIDFSITFound := 0 + numIDASITFound := 0 + numIDDSITFound := 0 + numIDSFSCFound := 0 + + for _, createdServiceItem := range createdServiceItemList { + // checking that the service item final destination address equals the shipment's final destination address + suite.Equal(sitShipment.DestinationAddress.StreetAddress1, createdServiceItem.SITDestinationFinalAddress.StreetAddress1) + suite.Equal(sitShipment.DestinationAddressID, createdServiceItem.SITDestinationFinalAddressID) + + switch createdServiceItem.ReService.Code { + case models.ReServiceCodeIDFSIT: + suite.NotEmpty(createdServiceItem.Dimensions) + numIDFSITFound++ + case models.ReServiceCodeIDASIT: + numIDASITFound++ + case models.ReServiceCodeIDDSIT: + numIDDSITFound++ + case models.ReServiceCodeIDSFSC: + numIDSFSCFound++ + } + } + suite.Equal(numIDASITFound, 1) + suite.Equal(numIDDSITFound, 1) + suite.Equal(numIDFSITFound, 1) + suite.Equal(numIDSFSCFound, 1) + }) + // Happy path: If the service item is created successfully it should be returned suite.Run("200 Success - SHUT Service Item Creation", func() { @@ -819,7 +912,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 @@ -1261,6 +1354,99 @@ func (suite *MTOServiceItemServiceSuite) TestCreateOriginSITServiceItem() { suite.IsType(apperror.ConflictError{}, err) }) + suite.Run("Do not create DOFSIT if departure date is after entry date", func() { + shipment := setupTestData() + originAddress := factory.BuildAddress(suite.DB(), nil, nil) + reServiceDOFSIT := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDOFSIT) + serviceItemDOFSIT := factory.BuildMTOServiceItem(nil, []factory.Customization{ + { + Model: models.MTOServiceItem{ + SITEntryDate: models.TimePointer(time.Now().AddDate(0, 0, 1)), + SITDepartureDate: models.TimePointer(time.Now()), + }, + }, + { + Model: reServiceDOFSIT, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + { + Model: originAddress, + LinkOnly: true, + Type: &factory.Addresses.SITOriginHHGOriginalAddress, + }, + }, nil) + builder := query.NewQueryBuilder() + moveRouter := moverouter.NewMoveRouter() + planner := &mocks.Planner{} + planner.On("ZipTransitDistance", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + false, + false, + ).Return(400, nil) + creator := NewMTOServiceItemCreator(planner, builder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) + _, _, err := creator.CreateMTOServiceItem(suite.AppContextForTest(), &serviceItemDOFSIT) + suite.Error(err) + expectedError := fmt.Sprintf( + "the SIT Departure Date (%s) must be after the SIT Entry Date (%s)", + serviceItemDOFSIT.SITDepartureDate.Format("2006-01-02"), + serviceItemDOFSIT.SITEntryDate.Format("2006-01-02"), + ) + suite.Contains(err.Error(), expectedError) + }) + + suite.Run("Do not create DOFSIT if departure date is the same as entry date", func() { + today := models.TimePointer(time.Now()) + shipment := setupTestData() + originAddress := factory.BuildAddress(suite.DB(), nil, nil) + reServiceDOFSIT := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDOFSIT) + serviceItemDOFSIT := factory.BuildMTOServiceItem(nil, []factory.Customization{ + { + Model: models.MTOServiceItem{ + SITEntryDate: today, + SITDepartureDate: today, + }, + }, + { + Model: reServiceDOFSIT, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + { + Model: originAddress, + LinkOnly: true, + Type: &factory.Addresses.SITOriginHHGOriginalAddress, + }, + }, nil) + builder := query.NewQueryBuilder() + moveRouter := moverouter.NewMoveRouter() + planner := &mocks.Planner{} + planner.On("ZipTransitDistance", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + false, + false, + ).Return(400, nil) + creator := NewMTOServiceItemCreator(planner, builder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) + _, _, err := creator.CreateMTOServiceItem(suite.AppContextForTest(), &serviceItemDOFSIT) + suite.Error(err) + expectedError := fmt.Sprintf( + "the SIT Departure Date (%s) must be after the SIT Entry Date (%s)", + serviceItemDOFSIT.SITDepartureDate.Format("2006-01-02"), + serviceItemDOFSIT.SITEntryDate.Format("2006-01-02"), + ) + suite.Contains(err.Error(), expectedError) + }) + suite.Run("Do not create standalone DOPSIT service item", func() { // TESTCASE SCENARIO // Under test: CreateMTOServiceItem function @@ -1686,6 +1872,63 @@ func (suite *MTOServiceItemServiceSuite) TestCreateDestSITServiceItem() { suite.Contains(err.Error(), expectedError) }) + suite.Run("Do not create DDFSIT if departure date is after entry date", func() { + shipment, creator, reServiceDDFSIT := setupTestData() + serviceItemDDFSIT := factory.BuildMTOServiceItem(nil, []factory.Customization{ + { + Model: models.MTOServiceItem{ + SITEntryDate: models.TimePointer(time.Now().AddDate(0, 0, 1)), + SITDepartureDate: models.TimePointer(time.Now()), + }, + }, + { + Model: reServiceDDFSIT, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + }, nil) + _, _, err := creator.CreateMTOServiceItem(suite.AppContextForTest(), &serviceItemDDFSIT) + suite.Error(err) + expectedError := fmt.Sprintf( + "the SIT Departure Date (%s) must be after the SIT Entry Date (%s)", + serviceItemDDFSIT.SITDepartureDate.Format("2006-01-02"), + serviceItemDDFSIT.SITEntryDate.Format("2006-01-02"), + ) + suite.Contains(err.Error(), expectedError) + }) + + suite.Run("Do not create DDFSIT if departure date is the same as entry date", func() { + today := models.TimePointer(time.Now()) + shipment, creator, reServiceDDFSIT := setupTestData() + serviceItemDDFSIT := factory.BuildMTOServiceItem(nil, []factory.Customization{ + { + Model: models.MTOServiceItem{ + SITEntryDate: today, + SITDepartureDate: today, + }, + }, + { + Model: reServiceDDFSIT, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + }, nil) + _, _, err := creator.CreateMTOServiceItem(suite.AppContextForTest(), &serviceItemDDFSIT) + suite.Error(err) + expectedError := fmt.Sprintf( + "the SIT Departure Date (%s) must be after the SIT Entry Date (%s)", + serviceItemDDFSIT.SITDepartureDate.Format("2006-01-02"), + serviceItemDDFSIT.SITEntryDate.Format("2006-01-02"), + ) + suite.Contains(err.Error(), expectedError) + }) + // Successful creation of DDASIT service item suite.Run("Success - DDASIT creation approved", func() { shipment, creator, reServiceDDFSIT := setupTestData() @@ -1828,3 +2071,666 @@ func (suite *MTOServiceItemServiceSuite) TestCreateDestSITServiceItem() { suite.Contains(invalidInputError.ValidationErrors.Keys(), "reServiceCode") }) } + +func (suite *MTOServiceItemServiceSuite) TestPriceEstimator() { + suite.Run("Calcuating price estimated on creation for HHG ", func() { + setupTestData := func() models.MTOShipment { + // Set up data to use for all Origin SIT Service Item tests + + move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + estimatedPrimeWeight := unit.Pound(6000) + pickupDate := time.Date(2024, time.July, 31, 12, 0, 0, 0, time.UTC) + pickupAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress2}) + deliveryAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress3}) + + mtoShipment := factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: pickupAddress, + LinkOnly: true, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: deliveryAddress, + LinkOnly: true, + Type: &factory.Addresses.DeliveryAddress, + }, + { + Model: models.MTOShipment{ + PrimeEstimatedWeight: &estimatedPrimeWeight, + RequestedPickupDate: &pickupDate, + }, + }, + }, nil) + + return mtoShipment + } + + reServiceCodeDOP := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDOP) + reServiceCodeDPK := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDPK) + reServiceCodeDDP := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDDP) + reServiceCodeDUPK := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDUPK) + reServiceCodeDLH := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDLH) + reServiceCodeDSH := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDSH) + reServiceCodeFSC := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeFSC) + + startDate := time.Now().AddDate(-1, 0, 0) + endDate := startDate.AddDate(1, 1, 1) + sitEntryDate := time.Date(2020, time.October, 24, 0, 0, 0, 0, time.UTC) + sitPostalCode := "99999" + reason := "lorem ipsum" + + contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + contractYear := testdatagen.MakeReContractYear(suite.DB(), + testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + Name: "Test Contract Year", + EscalationCompounded: 1.125, + StartDate: startDate, + EndDate: endDate, + }, + }) + + serviceArea := testdatagen.MakeReDomesticServiceArea(suite.DB(), + testdatagen.Assertions{ + ReDomesticServiceArea: models.ReDomesticServiceArea{ + Contract: contractYear.Contract, + ServiceArea: "945", + ServicesSchedule: 1, + }, + }) + + serviceAreaDest := testdatagen.MakeReDomesticServiceArea(suite.DB(), + testdatagen.Assertions{ + ReDomesticServiceArea: models.ReDomesticServiceArea{ + Contract: contractYear.Contract, + ServiceArea: "503", + ServicesSchedule: 1, + }, + }) + + serviceAreaPriceDOP := models.ReDomesticServiceAreaPrice{ + ContractID: contractYear.Contract.ID, + ServiceID: reServiceCodeDOP.ID, + IsPeakPeriod: true, + DomesticServiceAreaID: serviceArea.ID, + PriceCents: unit.Cents(1234), + } + + serviceAreaPriceDPK := factory.FetchOrMakeDomesticOtherPrice(suite.DB(), []factory.Customization{ + { + Model: models.ReDomesticOtherPrice{ + ContractID: contractYear.Contract.ID, + ServiceID: reServiceCodeDPK.ID, + IsPeakPeriod: true, + Schedule: 1, + PriceCents: unit.Cents(121), + }, + }, + }, nil) + + serviceAreaPriceDDP := models.ReDomesticServiceAreaPrice{ + ContractID: contractYear.Contract.ID, + ServiceID: reServiceCodeDDP.ID, + IsPeakPeriod: true, + DomesticServiceAreaID: serviceAreaDest.ID, + PriceCents: unit.Cents(482), + } + + serviceAreaPriceDUPK := factory.FetchOrMakeDomesticOtherPrice(suite.DB(), []factory.Customization{ + { + Model: models.ReDomesticOtherPrice{ + ContractID: contractYear.Contract.ID, + ServiceID: reServiceCodeDUPK.ID, + IsPeakPeriod: true, + Schedule: 1, + PriceCents: unit.Cents(945), + }, + }, + }, nil) + + serviceAreaPriceDLH := models.ReDomesticLinehaulPrice{ + ContractID: contractYear.Contract.ID, + WeightLower: 500, + WeightUpper: 10000, + MilesLower: 1, + MilesUpper: 10000, + IsPeakPeriod: true, + DomesticServiceAreaID: serviceArea.ID, + PriceMillicents: unit.Millicents(482), + } + + serviceAreaPriceDSH := models.ReDomesticServiceAreaPrice{ + ContractID: contractYear.Contract.ID, + ServiceID: reServiceCodeDSH.ID, + IsPeakPeriod: true, + DomesticServiceAreaID: serviceArea.ID, + PriceCents: unit.Cents(999), + } + + testdatagen.FetchOrMakeGHCDieselFuelPrice(suite.DB(), testdatagen.Assertions{ + GHCDieselFuelPrice: models.GHCDieselFuelPrice{ + FuelPriceInMillicents: unit.Millicents(281400), + PublicationDate: time.Date(2020, time.March, 9, 0, 0, 0, 0, time.UTC), + EffectiveDate: time.Date(2020, time.March, 10, 0, 0, 0, 0, time.UTC), + EndDate: time.Date(2025, time.March, 17, 0, 0, 0, 0, time.UTC), + }, + }) + + suite.MustSave(&serviceAreaPriceDOP) + suite.MustSave(&serviceAreaPriceDPK) + suite.MustSave(&serviceAreaPriceDDP) + suite.MustSave(&serviceAreaPriceDUPK) + suite.MustSave(&serviceAreaPriceDLH) + suite.MustSave(&serviceAreaPriceDSH) + + testdatagen.FetchOrMakeReZip3(suite.DB(), testdatagen.Assertions{ + ReZip3: models.ReZip3{ + Contract: contract, + ContractID: contract.ID, + DomesticServiceArea: serviceArea, + Zip3: "945", + }, + }) + + testdatagen.FetchOrMakeReZip3(suite.DB(), testdatagen.Assertions{ + ReZip3: models.ReZip3{ + Contract: contract, + ContractID: contract.ID, + DomesticServiceArea: serviceAreaDest, + Zip3: "503", + }, + }) + + shipment := setupTestData() + actualPickupAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress2}) + serviceItemDOP := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDOP, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemDPK := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDPK, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemDDP := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDDP, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemDUPK := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDUPK, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemDLH := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDLH, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemDSH := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDSH, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemFSC := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeFSC, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + builder := query.NewQueryBuilder() + moveRouter := moverouter.NewMoveRouter() + planner := &mocks.Planner{} + planner.On("ZipTransitDistance", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + false, + ).Return(400, nil) + creator := NewMTOServiceItemCreator(planner, builder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) + + dopEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDOP, shipment) + suite.Equal(unit.Cents(67188), dopEstimatedPriceInCents) + + dpkEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDPK, shipment) + suite.Equal(unit.Cents(594000), dpkEstimatedPriceInCents) + + ddpEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDDP, shipment) + suite.Equal(unit.Cents(46464), ddpEstimatedPriceInCents) + + dupkEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDUPK, shipment) + suite.Equal(unit.Cents(48246), dupkEstimatedPriceInCents) + + dlhEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDLH, shipment) + suite.Equal(unit.Cents(13619760), dlhEstimatedPriceInCents) + + dshEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDSH, shipment) + suite.Equal(unit.Cents(11088000), dshEstimatedPriceInCents) + + fscEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemFSC, shipment) + suite.Equal(unit.Cents(-168), fscEstimatedPriceInCents) + }) + + suite.Run("Calcuating price estimated on creation for NTS shipment ", func() { + setupTestData := func() models.MTOShipment { + // Set up data to use for all Origin SIT Service Item tests + + move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + estimatedPrimeWeight := unit.Pound(6000) + pickupDate := time.Date(2024, time.July, 31, 12, 0, 0, 0, time.UTC) + pickupAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress2}) + deliveryAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress3}) + + mtoShipment := factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: pickupAddress, + LinkOnly: true, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: deliveryAddress, + LinkOnly: true, + Type: &factory.Addresses.DeliveryAddress, + }, + { + Model: models.MTOShipment{ + PrimeEstimatedWeight: &estimatedPrimeWeight, + RequestedPickupDate: &pickupDate, + ShipmentType: models.MTOShipmentTypeHHGOutOfNTS, + }, + }, + }, nil) + + return mtoShipment + } + + reServiceCodeDOP := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDOP) + reServiceCodeDPK := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDPK) + reServiceCodeDDP := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDDP) + reServiceCodeDUPK := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDUPK) + reServiceCodeDLH := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDLH) + reServiceCodeDSH := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeDSH) + reServiceCodeFSC := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeFSC) + + startDate := time.Now().AddDate(-1, 0, 0) + endDate := startDate.AddDate(1, 1, 1) + sitEntryDate := time.Date(2020, time.October, 24, 0, 0, 0, 0, time.UTC) + sitPostalCode := "99999" + reason := "lorem ipsum" + + contract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + contractYear := testdatagen.FetchOrMakeReContractYear(suite.DB(), + testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + Name: "Test Contract Year", + EscalationCompounded: 1.125, + StartDate: startDate, + EndDate: endDate, + }, + }) + + serviceArea := testdatagen.FetchOrMakeReDomesticServiceArea(suite.DB(), + testdatagen.Assertions{ + ReDomesticServiceArea: models.ReDomesticServiceArea{ + Contract: contractYear.Contract, + ServiceArea: "945", + ServicesSchedule: 1, + }, + }) + + serviceAreaDest := testdatagen.MakeReDomesticServiceArea(suite.DB(), + testdatagen.Assertions{ + ReDomesticServiceArea: models.ReDomesticServiceArea{ + Contract: contractYear.Contract, + ServiceArea: "503", + ServicesSchedule: 1, + }, + }) + + serviceAreaPriceDOP := models.ReDomesticServiceAreaPrice{ + ContractID: contractYear.Contract.ID, + ServiceID: reServiceCodeDOP.ID, + IsPeakPeriod: true, + DomesticServiceAreaID: serviceArea.ID, + PriceCents: unit.Cents(1234), + } + + serviceAreaPriceDPK := factory.FetchOrMakeDomesticOtherPrice(suite.DB(), []factory.Customization{ + { + Model: models.ReDomesticOtherPrice{ + ContractID: contractYear.Contract.ID, + ServiceID: reServiceCodeDPK.ID, + IsPeakPeriod: true, + Schedule: 1, + PriceCents: unit.Cents(121), + }, + }, + }, nil) + + serviceAreaPriceDDP := models.ReDomesticServiceAreaPrice{ + ContractID: contractYear.Contract.ID, + ServiceID: reServiceCodeDDP.ID, + IsPeakPeriod: true, + DomesticServiceAreaID: serviceAreaDest.ID, + PriceCents: unit.Cents(482), + } + + serviceAreaPriceDUPK := factory.FetchOrMakeDomesticOtherPrice(suite.DB(), []factory.Customization{ + { + Model: models.ReDomesticOtherPrice{ + ContractID: contractYear.Contract.ID, + ServiceID: reServiceCodeDUPK.ID, + IsPeakPeriod: true, + Schedule: 1, + PriceCents: unit.Cents(945), + }, + }, + }, nil) + + serviceAreaPriceDLH := models.ReDomesticLinehaulPrice{ + ContractID: contractYear.Contract.ID, + WeightLower: 500, + WeightUpper: 10000, + MilesLower: 1, + MilesUpper: 10000, + IsPeakPeriod: true, + DomesticServiceAreaID: serviceArea.ID, + PriceMillicents: unit.Millicents(482), + } + + serviceAreaPriceDSH := models.ReDomesticServiceAreaPrice{ + ContractID: contractYear.Contract.ID, + ServiceID: reServiceCodeDSH.ID, + IsPeakPeriod: true, + DomesticServiceAreaID: serviceArea.ID, + PriceCents: unit.Cents(999), + } + + testdatagen.FetchOrMakeGHCDieselFuelPrice(suite.DB(), testdatagen.Assertions{ + GHCDieselFuelPrice: models.GHCDieselFuelPrice{ + FuelPriceInMillicents: unit.Millicents(281400), + PublicationDate: time.Date(2020, time.March, 9, 0, 0, 0, 0, time.UTC), + EffectiveDate: time.Date(2020, time.March, 10, 0, 0, 0, 0, time.UTC), + EndDate: time.Date(2025, time.March, 17, 0, 0, 0, 0, time.UTC), + }, + }) + + suite.MustSave(&serviceAreaPriceDOP) + suite.MustSave(&serviceAreaPriceDPK) + suite.MustSave(&serviceAreaPriceDDP) + suite.MustSave(&serviceAreaPriceDUPK) + suite.MustSave(&serviceAreaPriceDLH) + suite.MustSave(&serviceAreaPriceDSH) + + testdatagen.FetchOrMakeReZip3(suite.DB(), testdatagen.Assertions{ + ReZip3: models.ReZip3{ + Contract: contract, + ContractID: contract.ID, + DomesticServiceArea: serviceArea, + Zip3: "945", + }, + }) + + testdatagen.FetchOrMakeReZip3(suite.DB(), testdatagen.Assertions{ + ReZip3: models.ReZip3{ + Contract: contract, + ContractID: contract.ID, + DomesticServiceArea: serviceAreaDest, + Zip3: "503", + }, + }) + + shipment := setupTestData() + actualPickupAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress2}) + serviceItemDOP := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDOP, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemDPK := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDPK, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemDDP := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDDP, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemDUPK := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDUPK, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemDLH := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDLH, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemDSH := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeDSH, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + serviceItemFSC := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeFSC, + SITEntryDate: &sitEntryDate, + SITPostalCode: &sitPostalCode, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + builder := query.NewQueryBuilder() + moveRouter := moverouter.NewMoveRouter() + planner := &mocks.Planner{} + planner.On("ZipTransitDistance", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + false, + ).Return(800, nil) + creator := NewMTOServiceItemCreator(planner, builder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) + + dopEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDOP, shipment) + suite.Equal(unit.Cents(67188), dopEstimatedPriceInCents) + + dpkEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDPK, shipment) + suite.Equal(unit.Cents(594000), dpkEstimatedPriceInCents) + + ddpEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDDP, shipment) + suite.Equal(unit.Cents(46464), ddpEstimatedPriceInCents) + + dupkEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDUPK, shipment) + suite.Equal(unit.Cents(48246), dupkEstimatedPriceInCents) + + dlhEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDLH, shipment) + suite.Equal(unit.Cents(29990400), dlhEstimatedPriceInCents) + + dshEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemDSH, shipment) + suite.Equal(unit.Cents(22176000), dshEstimatedPriceInCents) + + fscEstimatedPriceInCents, _ := creator.FindEstimatedPrice(suite.AppContextForTest(), &serviceItemFSC, shipment) + suite.Equal(unit.Cents(-335), fscEstimatedPriceInCents) + }) + +} +func (suite *MTOServiceItemServiceSuite) TestGetAdjustedWeight() { + suite.Run("If no weight is provided", func() { + var incomingWeight unit.Pound + adjustedWeight := GetAdjustedWeight(incomingWeight, false) + suite.Equal(unit.Pound(0), *adjustedWeight) + }) + suite.Run("If a weight of 0 is provided", func() { + incomingWeight := unit.Pound(0) + adjustedWeight := GetAdjustedWeight(incomingWeight, false) + suite.Equal(unit.Pound(0), *adjustedWeight) + }) + suite.Run("If weight of 100 is provided", func() { + incomingWeight := unit.Pound(100) + adjustedWeight := GetAdjustedWeight(incomingWeight, false) + suite.Equal(unit.Pound(500), *adjustedWeight) + }) + suite.Run("If weight of 454 is provided", func() { + incomingWeight := unit.Pound(454) + adjustedWeight := GetAdjustedWeight(incomingWeight, false) + suite.Equal(unit.Pound(500), *adjustedWeight) + }) + suite.Run("If weight of 456 is provided", func() { + incomingWeight := unit.Pound(456) + adjustedWeight := GetAdjustedWeight(incomingWeight, false) + suite.Equal(unit.Pound(501), *adjustedWeight) + }) + suite.Run("If weight of 1000 is provided", func() { + incomingWeight := unit.Pound(1000) + adjustedWeight := GetAdjustedWeight(incomingWeight, false) + suite.Equal(unit.Pound(1100), *adjustedWeight) + }) + + suite.Run("If no weight is provided UB", func() { + var incomingWeight unit.Pound + adjustedWeight := GetAdjustedWeight(incomingWeight, true) + suite.Equal(unit.Pound(0), *adjustedWeight) + }) + suite.Run("If a weight of 0 is provided UB", func() { + incomingWeight := unit.Pound(0) + adjustedWeight := GetAdjustedWeight(incomingWeight, true) + suite.Equal(unit.Pound(0), *adjustedWeight) + }) + suite.Run("If weight of 100 is provided UB", func() { + incomingWeight := unit.Pound(100) + adjustedWeight := GetAdjustedWeight(incomingWeight, true) + suite.Equal(unit.Pound(300), *adjustedWeight) + }) + suite.Run("If weight of 272 is provided UB", func() { + incomingWeight := unit.Pound(272) + adjustedWeight := GetAdjustedWeight(incomingWeight, true) + suite.Equal(unit.Pound(300), *adjustedWeight) + }) + suite.Run("If weight of 274 is provided UB", func() { + incomingWeight := unit.Pound(274) + adjustedWeight := GetAdjustedWeight(incomingWeight, true) + suite.Equal(unit.Pound(301), *adjustedWeight) + }) + suite.Run("If weight of 1000 is provided UB", func() { + incomingWeight := unit.Pound(1000) + adjustedWeight := GetAdjustedWeight(incomingWeight, true) + suite.Equal(unit.Pound(1100), *adjustedWeight) + }) +} diff --git a/pkg/services/mto_service_item/mto_service_item_updater.go b/pkg/services/mto_service_item/mto_service_item_updater.go index 3c17ceff042..b5eaeae3caa 100644 --- a/pkg/services/mto_service_item/mto_service_item_updater.go +++ b/pkg/services/mto_service_item/mto_service_item_updater.go @@ -3,6 +3,7 @@ package mtoserviceitem import ( "database/sql" "fmt" + "strconv" "time" "github.com/gobuffalo/validate/v3" @@ -19,6 +20,7 @@ import ( movetaskorder "github.com/transcom/mymove/pkg/services/move_task_order" "github.com/transcom/mymove/pkg/services/query" sitstatus "github.com/transcom/mymove/pkg/services/sit_status" + "github.com/transcom/mymove/pkg/unit" ) // OriginSITLocation is the constant representing when the shipment in storage occurs at the origin @@ -43,17 +45,21 @@ type mtoServiceItemUpdater struct { moveRouter services.MoveRouter shipmentFetcher services.MTOShipmentFetcher addressCreator services.AddressCreator + unpackPricer services.DomesticUnpackPricer + linehaulPricer services.DomesticLinehaulPricer + destinationPricer services.DomesticDestinationPricer + fuelSurchargePricer services.FuelSurchargePricer portLocationFetcher services.PortLocationFetcher } // NewMTOServiceItemUpdater returns a new mto service item updater -func NewMTOServiceItemUpdater(planner route.Planner, builder mtoServiceItemQueryBuilder, moveRouter services.MoveRouter, shipmentFetcher services.MTOShipmentFetcher, addressCreator services.AddressCreator, portLocationFetcher services.PortLocationFetcher) services.MTOServiceItemUpdater { +func NewMTOServiceItemUpdater(planner route.Planner, builder mtoServiceItemQueryBuilder, moveRouter services.MoveRouter, shipmentFetcher services.MTOShipmentFetcher, addressCreator services.AddressCreator, portLocationFetcher services.PortLocationFetcher, unpackPricer services.DomesticUnpackPricer, linehaulPricer services.DomesticLinehaulPricer, destinationPricer services.DomesticDestinationPricer, fuelSurchargePricer services.FuelSurchargePricer) services.MTOServiceItemUpdater { // used inside a transaction and mocking return &mtoServiceItemUpdater{builder: builder} createNewBuilder := func() mtoServiceItemQueryBuilder { return query.NewQueryBuilder() } - return &mtoServiceItemUpdater{planner, builder, createNewBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher} + return &mtoServiceItemUpdater{planner, builder, createNewBuilder, moveRouter, shipmentFetcher, addressCreator, unpackPricer, linehaulPricer, destinationPricer, fuelSurchargePricer, portLocationFetcher} } func (p *mtoServiceItemUpdater) ApproveOrRejectServiceItem( @@ -119,6 +125,134 @@ func (p *mtoServiceItemUpdater) ConvertItemToCustomerExpense( return p.convertItemToCustomerExpense(appCtx, *mtoServiceItem, customerExpenseReason, convertToCustomerExpense, eTag, checkETag()) } +func (p *mtoServiceItemUpdater) findEstimatedPrice(appCtx appcontext.AppContext, serviceItem *models.MTOServiceItem, mtoShipment models.MTOShipment) (unit.Cents, error) { + if serviceItem.ReService.Code == models.ReServiceCodeDDP || + serviceItem.ReService.Code == models.ReServiceCodeDUPK || + serviceItem.ReService.Code == models.ReServiceCodeDLH || + serviceItem.ReService.Code == models.ReServiceCodeFSC || + serviceItem.ReService.Code == models.ReServiceCodeDDDSIT || + serviceItem.ReService.Code == models.ReServiceCodeDDSFSC { + + isPPM := false + if mtoShipment.ShipmentType == models.MTOShipmentTypePPM { + isPPM = true + } + + var pickupDate *time.Time + if mtoShipment.ActualPickupDate != nil { + pickupDate = mtoShipment.ActualPickupDate + } else { + if mtoShipment.RequestedPickupDate != nil { + pickupDate = mtoShipment.RequestedPickupDate + } + } + + currTime := time.Now() + var distance int + + var shipmentWeight unit.Pound + if mtoShipment.PrimeActualWeight != nil { + shipmentWeight = *mtoShipment.PrimeActualWeight + } else { + if mtoShipment.PrimeEstimatedWeight != nil { + shipmentWeight = *mtoShipment.PrimeEstimatedWeight + } else { + return 0, apperror.NewInvalidInputError(serviceItem.ID, nil, nil, "No estimated or actual weight exists for this service item.") + } + } + + contractCode, err := FetchContractCode(appCtx, currTime) + if err != nil && pickupDate != nil { + contractCode, err = FetchContractCode(appCtx, *pickupDate) + if err != nil { + return 0, err + } + } + + var price unit.Cents + + // destination + if serviceItem.ReService.Code == models.ReServiceCodeDDP { + var domesticServiceArea models.ReDomesticServiceArea + if mtoShipment.DestinationAddress != nil { + domesticServiceArea, err = fetchDomesticServiceArea(appCtx, contractCode, mtoShipment.DestinationAddress.PostalCode) + if err != nil { + return 0, err + } + } + + adjustedWeight := GetAdjustedWeight(shipmentWeight, mtoShipment.ShipmentType == models.MTOShipmentTypeUnaccompaniedBaggage) + price, _, err = p.destinationPricer.Price(appCtx, contractCode, *pickupDate, *adjustedWeight, domesticServiceArea.ServiceArea, isPPM) + if err != nil { + return 0, err + } + } + // linehaul + if serviceItem.ReService.Code == models.ReServiceCodeDLH { + domesticServiceArea, err := fetchDomesticServiceArea(appCtx, contractCode, mtoShipment.PickupAddress.PostalCode) + if err != nil { + return 0, err + } + if mtoShipment.PickupAddress != nil && mtoShipment.DestinationAddress != nil { + distance, err = p.planner.ZipTransitDistance(appCtx, mtoShipment.PickupAddress.PostalCode, mtoShipment.DestinationAddress.PostalCode, false) + if err != nil { + return 0, err + } + } + + adjustedWeight := GetAdjustedWeight(shipmentWeight, mtoShipment.ShipmentType == models.MTOShipmentTypeUnaccompaniedBaggage) + price, _, err = p.linehaulPricer.Price(appCtx, contractCode, *pickupDate, unit.Miles(distance), *adjustedWeight, domesticServiceArea.ServiceArea, isPPM) + if err != nil { + return 0, err + } + } + // unpacking + if serviceItem.ReService.Code == models.ReServiceCodeDUPK { + domesticServiceArea, err := fetchDomesticServiceArea(appCtx, contractCode, mtoShipment.DestinationAddress.PostalCode) + if err != nil { + return 0, err + } + + adjustedWeight := GetAdjustedWeight(shipmentWeight, mtoShipment.ShipmentType == models.MTOShipmentTypeUnaccompaniedBaggage) + price, _, err = p.unpackPricer.Price(appCtx, contractCode, *pickupDate, *adjustedWeight, domesticServiceArea.ServicesSchedule, isPPM) + if err != nil { + return 0, err + } + } + // fuel surcharge + if serviceItem.ReService.Code == models.ReServiceCodeFSC { + if mtoShipment.PickupAddress != nil && mtoShipment.DestinationAddress != nil { + distance, err = p.planner.ZipTransitDistance(appCtx, mtoShipment.PickupAddress.PostalCode, mtoShipment.DestinationAddress.PostalCode, false) + if err != nil { + return 0, err + } + } + + fscWeightBasedDistanceMultiplier, err := LookupFSCWeightBasedDistanceMultiplier(appCtx, shipmentWeight) + if err != nil { + return 0, err + } + fscWeightBasedDistanceMultiplierFloat, err := strconv.ParseFloat(fscWeightBasedDistanceMultiplier, 64) + if err != nil { + return 0, err + } + eiaFuelPrice, err := LookupEIAFuelPrice(appCtx, *pickupDate) + if err != nil { + return 0, err + } + + adjustedWeight := GetAdjustedWeight(shipmentWeight, mtoShipment.ShipmentType == models.MTOShipmentTypeUnaccompaniedBaggage) + price, _, err = p.fuelSurchargePricer.Price(appCtx, *pickupDate, unit.Miles(distance), *adjustedWeight, fscWeightBasedDistanceMultiplierFloat, eiaFuelPrice, isPPM) + if err != nil { + return 0, err + } + + } + return price, nil + } + return 0, nil +} + func (p *mtoServiceItemUpdater) findServiceItem(appCtx appcontext.AppContext, serviceItemID uuid.UUID) (*models.MTOServiceItem, error) { var serviceItem models.MTOServiceItem err := appCtx.DB().Q().EagerPreload( @@ -327,6 +461,21 @@ func (p *mtoServiceItemUpdater) convertItemToCustomerExpense( return &serviceItem, nil } +// UpdateMTOServiceItemPricingEstimate updates the MTO Service Item pricing estimate +func (p *mtoServiceItemUpdater) UpdateMTOServiceItemPricingEstimate( + appCtx appcontext.AppContext, + mtoServiceItem *models.MTOServiceItem, + shipment models.MTOShipment, + eTag string, +) (*models.MTOServiceItem, error) { + estimatedPrice, err := p.findEstimatedPrice(appCtx, mtoServiceItem, shipment) + if estimatedPrice != 0 && err == nil { + mtoServiceItem.PricingEstimate = &estimatedPrice + return p.UpdateMTOServiceItem(appCtx, mtoServiceItem, eTag, UpdateMTOServiceItemBasicValidator) + } + return mtoServiceItem, err +} + // UpdateMTOServiceItemBasic updates the MTO Service Item using base validators func (p *mtoServiceItemUpdater) UpdateMTOServiceItemBasic( appCtx appcontext.AppContext, diff --git a/pkg/services/mto_service_item/mto_service_item_updater_test.go b/pkg/services/mto_service_item/mto_service_item_updater_test.go index 95f5191681a..26802c5eb9b 100644 --- a/pkg/services/mto_service_item/mto_service_item_updater_test.go +++ b/pkg/services/mto_service_item/mto_service_item_updater_test.go @@ -24,6 +24,7 @@ import ( "github.com/transcom/mymove/pkg/models/roles" mocks "github.com/transcom/mymove/pkg/route/mocks" "github.com/transcom/mymove/pkg/services/address" + "github.com/transcom/mymove/pkg/services/ghcrateengine" moverouter "github.com/transcom/mymove/pkg/services/move" movetaskorder "github.com/transcom/mymove/pkg/services/move_task_order" mtoshipment "github.com/transcom/mymove/pkg/services/mto_shipment" @@ -52,7 +53,7 @@ func (suite *MTOServiceItemServiceSuite) TestMTOServiceItemUpdater() { mock.Anything, false, ).Return(400, nil) - updater := NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher) + updater := NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) setupServiceItem := func() (models.MTOServiceItem, string) { serviceItem := testdatagen.MakeDefaultMTOServiceItem(suite.DB()) @@ -2169,7 +2170,7 @@ func (suite *MTOServiceItemServiceSuite) TestUpdateMTOServiceItemStatus() { mock.Anything, false, ).Return(400, nil) - updater := NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher) + updater := NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) rejectionReason := models.StringPointer("") @@ -2801,6 +2802,107 @@ func (suite *MTOServiceItemServiceSuite) TestUpdateMTOServiceItemStatus() { }) } +func (suite *MTOServiceItemServiceSuite) setupServiceItemData() { + startDate := time.Date(2020, time.January, 1, 12, 0, 0, 0, time.UTC) + endDate := time.Date(2020, time.December, 31, 12, 0, 0, 0, time.UTC) + + testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: startDate, + EndDate: endDate, + }, + }) + + originalDomesticServiceArea := testdatagen.FetchOrMakeReDomesticServiceArea(suite.DB(), testdatagen.Assertions{ + ReDomesticServiceArea: models.ReDomesticServiceArea{ + ServiceArea: "004", + ServicesSchedule: 2, + }, + ReContract: testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}), + }) + + testdatagen.FetchOrMakeReZip3(suite.DB(), testdatagen.Assertions{ + ReZip3: models.ReZip3{ + Contract: originalDomesticServiceArea.Contract, + ContractID: originalDomesticServiceArea.ContractID, + DomesticServiceArea: originalDomesticServiceArea, + Zip3: "902", + }, + }) + + testdatagen.FetchOrMakeReDomesticLinehaulPrice(suite.DB(), testdatagen.Assertions{ + ReDomesticLinehaulPrice: models.ReDomesticLinehaulPrice{ + Contract: originalDomesticServiceArea.Contract, + ContractID: originalDomesticServiceArea.ContractID, + DomesticServiceArea: originalDomesticServiceArea, + DomesticServiceAreaID: originalDomesticServiceArea.ID, + WeightLower: unit.Pound(500), + WeightUpper: unit.Pound(9999), + MilesLower: 250, + MilesUpper: 9999, + PriceMillicents: unit.Millicents(606800), + IsPeakPeriod: false, + }, + }) +} + +func (suite *MTOServiceItemServiceSuite) TestUpdateMTOServiceItemPricingEstimate() { + builder := query.NewQueryBuilder() + moveRouter := moverouter.NewMoveRouter() + shipmentFetcher := mtoshipment.NewMTOShipmentFetcher() + addressCreator := address.NewAddressCreator() + portLocationFetcher := portlocation.NewPortLocationFetcher() + planner := &mocks.Planner{} + planner.On("ZipTransitDistance", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + false, + ).Return(400, nil) + updater := NewMTOServiceItemUpdater(planner, builder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) + + setupServiceItem := func() (models.MTOServiceItem, string) { + serviceItem := testdatagen.MakeDefaultMTOServiceItem(suite.DB()) + eTag := etag.GenerateEtag(serviceItem.UpdatedAt) + return serviceItem, eTag + } + + setupServiceItems := func() models.MTOServiceItems { + serviceItems := testdatagen.MakeMTOServiceItems(suite.DB()) + return serviceItems + } + + suite.Run("Validation Error", func() { + suite.setupServiceItemData() + serviceItem, eTag := setupServiceItem() + invalidServiceItem := serviceItem + invalidServiceItem.MoveTaskOrderID = serviceItem.ID // invalid Move ID + + updatedServiceItem, err := updater.UpdateMTOServiceItemPricingEstimate(suite.AppContextForTest(), &invalidServiceItem, serviceItem.MTOShipment, eTag) + + suite.Nil(updatedServiceItem) + suite.Error(err) + suite.IsType(apperror.InvalidInputError{}, err) + + invalidInputError := err.(apperror.InvalidInputError) + suite.True(invalidInputError.ValidationErrors.HasAny()) + suite.Contains(invalidInputError.ValidationErrors.Keys(), "moveTaskOrderID") + }) + + suite.Run("Returns updated service item on success wihtout error", func() { + suite.setupServiceItemData() + serviceItems := setupServiceItems() + + for _, serviceItem := range serviceItems { + eTag := etag.GenerateEtag(serviceItem.UpdatedAt) + updatedServiceItem, err := updater.UpdateMTOServiceItemPricingEstimate(suite.AppContextForTest(), &serviceItem, serviceItem.MTOShipment, eTag) + + suite.NotNil(updatedServiceItem) + suite.Nil(err) + } + }) +} + // Helper function to create a rejected service item func buildRejectedServiceItem(suite *MTOServiceItemServiceSuite, reServiceCode models.ReServiceCode, reason string, contactDatePlusGracePeriod, aMonthAgo, now, sitRequestedDelivery time.Time, requestApprovalsRequestedStatus bool) models.MTOServiceItem { return factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ diff --git a/pkg/services/mto_service_item/mto_service_item_validators.go b/pkg/services/mto_service_item/mto_service_item_validators.go index 25a6768c335..786f18d9944 100644 --- a/pkg/services/mto_service_item/mto_service_item_validators.go +++ b/pkg/services/mto_service_item/mto_service_item_validators.go @@ -43,11 +43,21 @@ var allSITServiceItemsToCheck = []models.ReServiceCode{ models.ReServiceCodeDOFSIT, models.ReServiceCodeDOASIT, models.ReServiceCodeDOSFSC, + models.ReServiceCodeIDDSIT, + models.ReServiceCodeIDASIT, + models.ReServiceCodeIDFSIT, + models.ReServiceCodeIDSFSC, + models.ReServiceCodeIOPSIT, + models.ReServiceCodeIOFSIT, + models.ReServiceCodeIOASIT, + models.ReServiceCodeIOSFSC, } var allAccessorialServiceItemsToCheck = []models.ReServiceCode{ models.ReServiceCodeIDSHUT, models.ReServiceCodeIOSHUT, + models.ReServiceCodeICRT, + models.ReServiceCodeIUCRT, } var destSITServiceItems = []models.ReServiceCode{ @@ -135,7 +145,7 @@ func (v *primeUpdateMTOServiceItemValidator) validate(appCtx appcontext.AppConte } // Checks that the SITDepartureDate - // - is not later than the authorized end date + // - is not earlier than the SIT entry date err = serviceItemData.checkSITDepartureDate(appCtx) if err != nil { return err @@ -344,13 +354,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 +366,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 +377,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.") @@ -503,10 +514,10 @@ func (v *updateMTOServiceItemData) checkSITEntryDateAndFADD(_ appcontext.AppCont } // checkSITDepartureDate checks that the SITDepartureDate: -// - is not later than the authorized end date +// - is not earlier than the SIT entry date func (v *updateMTOServiceItemData) checkSITDepartureDate(_ appcontext.AppContext) error { - if v.updatedServiceItem.SITDepartureDate == nil || v.updatedServiceItem.SITDepartureDate == v.oldServiceItem.SITDepartureDate { - return nil // the SITDepartureDate isn't being updated, so we're fine here + if (v.updatedServiceItem.SITDepartureDate == nil || v.updatedServiceItem.SITDepartureDate == v.oldServiceItem.SITDepartureDate) && (v.updatedServiceItem.SITEntryDate == nil || v.updatedServiceItem.SITEntryDate == v.oldServiceItem.SITEntryDate) { + return nil // the SITDepartureDate or SITEntryDate isn't being updated, so we're fine here } if v.updatedServiceItem.SITDepartureDate != nil { @@ -523,9 +534,9 @@ func (v *updateMTOServiceItemData) checkSITDepartureDate(_ appcontext.AppContext if v.updatedServiceItem.SITEntryDate != nil { SITEntryDate = v.updatedServiceItem.SITEntryDate } - // Check that departure date is not before the current entry date - if v.updatedServiceItem.SITDepartureDate.Before(*SITEntryDate) { - v.verrs.Add("SITDepartureDate", "SIT departure date cannot be set before the SIT entry date.") + // Check that departure date is not before or equal to the current entry date + if !v.updatedServiceItem.SITDepartureDate.After(*SITEntryDate) { + v.verrs.Add("SITDepartureDate", "SIT departure date cannot be set before or equal to the SIT entry date.") } } return nil @@ -686,6 +697,9 @@ func (v *updateMTOServiceItemData) setNewMTOServiceItem() *models.MTOServiceItem newMTOServiceItem.ActualWeight = services.SetOptionalPoundField( v.updatedServiceItem.ActualWeight, newMTOServiceItem.ActualWeight) + newMTOServiceItem.PricingEstimate = services.SetNoNilOptionalCentField( + v.updatedServiceItem.PricingEstimate, newMTOServiceItem.PricingEstimate) + // Set POD Location if v.updatedServiceItem.PODLocationID != nil { newMTOServiceItem.PODLocationID = v.updatedServiceItem.PODLocationID @@ -824,3 +838,16 @@ func (o *mtoServiceItemCreator) checkSITEntryDateAndFADD(serviceItem *models.MTO return nil } + +func (o *mtoServiceItemCreator) checkSITEntryDateBeforeDepartureDate(serviceItem *models.MTOServiceItem) error { + if serviceItem.SITEntryDate == nil || serviceItem.SITDepartureDate == nil { + return nil + } + + //Departure Date has to be after the Entry Date + if !serviceItem.SITDepartureDate.After(*serviceItem.SITEntryDate) { + return apperror.NewUnprocessableEntityError(fmt.Sprintf("the SIT Departure Date (%s) must be after the SIT Entry Date (%s)", + serviceItem.SITDepartureDate.Format("2006-01-02"), serviceItem.SITEntryDate.Format("2006-01-02"))) + } + return nil +} diff --git a/pkg/services/mto_service_item/mto_service_item_validators_test.go b/pkg/services/mto_service_item/mto_service_item_validators_test.go index 888c094becd..4b74be7b68f 100644 --- a/pkg/services/mto_service_item/mto_service_item_validators_test.go +++ b/pkg/services/mto_service_item/mto_service_item_validators_test.go @@ -181,6 +181,29 @@ func (suite *MTOServiceItemServiceSuite) TestUpdateMTOServiceItemData() { suite.NoError(err) }) + suite.Run("checkForSITItemChanges - should not throw error when SIT Item is changed - international", func() { + + // Update the non-updateable fields: + oldServiceItem, newServiceItem := setupTestData() // Create old and new service item + + // Make both sthe newServiceItem of type DOFSIT because this type of service item will be checked by checkForSITItemChanges + newServiceItem.ReService.Code = models.ReServiceCodeIOFSIT + + // Sit Entry Date change. Need to make the newServiceItem different than the old. + newSitEntryDate := time.Date(2023, time.October, 10, 10, 10, 0, 0, time.UTC) + newServiceItem.SITEntryDate = &newSitEntryDate + + serviceItemData := updateMTOServiceItemData{ + updatedServiceItem: newServiceItem, + oldServiceItem: oldServiceItem, + verrs: validate.NewErrors(), + } + + err := serviceItemData.checkForSITItemChanges(&serviceItemData) + + suite.NoError(err) + }) + suite.Run("checkForSITItemChanges - should throw error when SIT Item is not changed", func() { oldServiceItem, newServiceItem := setupTestData() // Create old and new service item @@ -204,6 +227,29 @@ func (suite *MTOServiceItemServiceSuite) TestUpdateMTOServiceItemData() { }) + suite.Run("checkForSITItemChanges - should throw error when SIT Item is not changed - international", func() { + + oldServiceItem, newServiceItem := setupTestData() // Create old and new service item + + // Make both service items of type IOFSIT because this type of service item will be checked by checkForSITItemChanges + oldServiceItem.ReService.Code = models.ReServiceCodeIOFSIT + newServiceItem.ReService.Code = models.ReServiceCodeIOFSIT + oldServiceItem.SITDepartureDate, newServiceItem.SITDepartureDate = &now, &now + + serviceItemData := updateMTOServiceItemData{ + updatedServiceItem: newServiceItem, + oldServiceItem: oldServiceItem, + verrs: validate.NewErrors(), + } + + err := serviceItemData.checkForSITItemChanges(&serviceItemData) + + // Should error with message if nothing has changed between the new service item and the old one + suite.Error(err) + suite.Contains(err.Error(), "To re-submit a SIT sevice item the new SIT service item must be different than the previous one.") + + }) + // Test successful check for SIT departure service item - not updating SITDepartureDate suite.Run("checkSITDeparture w/ no SITDepartureDate update - success", func() { oldServiceItem, newServiceItem := setupTestData() // These @@ -247,6 +293,62 @@ func (suite *MTOServiceItemServiceSuite) TestUpdateMTOServiceItemData() { suite.NoVerrs(serviceItemData.verrs) }) + // Test successful check for SIT departure service item - IDDSIT + suite.Run("checkSITDeparture w/ IDDSIT - success", func() { + // Under test: checkSITDeparture checks that the service item is a + // IDDSIT or IOPSIT if the user is trying to update the + // SITDepartureDate + // Set up: Create an old and new IDDSIT, with a new date and try to update. + // Expected outcome: Success if both are IDDSIT + oldIDDSIT := factory.BuildMTOServiceItem(nil, []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeIDDSIT, + }, + }, + }, nil) + newIDDSIT := oldIDDSIT + newIDDSIT.SITDepartureDate = &now + + serviceItemData := updateMTOServiceItemData{ + updatedServiceItem: newIDDSIT, + oldServiceItem: oldIDDSIT, + verrs: validate.NewErrors(), + } + err := serviceItemData.checkSITDeparture(suite.AppContextForTest()) + + suite.NoError(err) + suite.NoVerrs(serviceItemData.verrs) + }) + + // Test successful check for SIT departure service item - IDDSIT + suite.Run("checkSITDeparture w/ IDDSIT - success", func() { + // Under test: checkSITDeparture checks that the service item is a + // IDDSIT or IOPSIT if the user is trying to update the + // SITDepartureDate + // Set up: Create an old and new IDDSIT, with a new date and try to update. + // Expected outcome: Success if both are IDDSIT + oldIDDSIT := factory.BuildMTOServiceItem(nil, []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeIDDSIT, + }, + }, + }, nil) + newIDDSIT := oldIDDSIT + newIDDSIT.SITDepartureDate = &now + + serviceItemData := updateMTOServiceItemData{ + updatedServiceItem: newIDDSIT, + oldServiceItem: oldIDDSIT, + verrs: validate.NewErrors(), + } + err := serviceItemData.checkSITDeparture(suite.AppContextForTest()) + + suite.NoError(err) + suite.NoVerrs(serviceItemData.verrs) + }) + // Test unsuccessful check for SIT departure service item - not a departure SIT item suite.Run("checkSITDeparture w/ non-departure SIT - failure", func() { // Under test: checkSITDeparture checks that the service item is a @@ -397,6 +499,37 @@ func (suite *MTOServiceItemServiceSuite) TestUpdateMTOServiceItemData() { suite.Contains(err.Error(), "- reason cannot be empty when resubmitting a previously rejected SIT service item") }) + // Test unsuccessful check service item when the reason isn't being updated + suite.Run("checkReasonWasUpdatedOnRejectedSIT - failure when empty string - international", func() { + // Under test: checkReasonWasUpdatedOnRejectedSIT ensures that the reason value is being updated + // Set up: Create any SIT service item + // Expected outcome: ConflictError + oldServiceItem, newServiceItem := setupTestData() + + // only checks rejected SIT service items + newServiceItem.Status = models.MTOServiceItemStatusSubmitted + oldServiceItem.Status = models.MTOServiceItemStatusRejected + + // This only checks SIT service items + newServiceItem.ReService.Code = models.ReServiceCodeIDFSIT + oldServiceItem.ReService.Code = models.ReServiceCodeIDFSIT + + newServiceItem.Reason = models.StringPointer("") + oldServiceItem.Reason = models.StringPointer("a reason") + + serviceItemData := updateMTOServiceItemData{ + updatedServiceItem: newServiceItem, + oldServiceItem: oldServiceItem, + verrs: validate.NewErrors(), + } + err := serviceItemData.checkReasonWasUpdatedOnRejectedSIT(suite.AppContextForTest()) + + suite.Error(err) + suite.IsType(apperror.ConflictError{}, err) + suite.NoVerrs(serviceItemData.verrs) + suite.Contains(err.Error(), "- reason cannot be empty when resubmitting a previously rejected SIT service item") + }) + // Test unsuccessful check service item when the reason isn't being updated suite.Run("checkReasonWasUpdatedOnRejectedSIT - failure when no reason is provided", func() { // Under test: checkReasonWasUpdatedOnRejectedSIT ensures that the reason value is being updated @@ -428,6 +561,37 @@ func (suite *MTOServiceItemServiceSuite) TestUpdateMTOServiceItemData() { suite.Contains(err.Error(), "- you must provide a new reason when resubmitting a previously rejected SIT service item") }) + // Test unsuccessful check service item when the reason isn't being updated + suite.Run("checkReasonWasUpdatedOnRejectedSIT - failure when no reason is provided - international", func() { + // Under test: checkReasonWasUpdatedOnRejectedSIT ensures that the reason value is being updated + // Set up: Create any SIT service item + // Expected outcome: ConflictError + oldServiceItem, newServiceItem := setupTestData() + + // only checks rejected SIT service items + newServiceItem.Status = models.MTOServiceItemStatusSubmitted + oldServiceItem.Status = models.MTOServiceItemStatusRejected + + // This only checks SIT service items + newServiceItem.ReService.Code = models.ReServiceCodeIDFSIT + oldServiceItem.ReService.Code = models.ReServiceCodeIDFSIT + + newServiceItem.Reason = nil + oldServiceItem.Reason = models.StringPointer("a reason") + + serviceItemData := updateMTOServiceItemData{ + updatedServiceItem: newServiceItem, + oldServiceItem: oldServiceItem, + verrs: validate.NewErrors(), + } + err := serviceItemData.checkReasonWasUpdatedOnRejectedSIT(suite.AppContextForTest()) + + suite.Error(err) + suite.IsType(apperror.ConflictError{}, err) + suite.NoVerrs(serviceItemData.verrs) + suite.Contains(err.Error(), "- you must provide a new reason when resubmitting a previously rejected SIT service item") + }) + suite.Run("checkReasonWasUpdatedOnRejectedSIT - success", func() { // Under test: checkReasonWasUpdatedOnRejectedSIT ensures that the reason value is being updated // Set up: Create any SIT service item @@ -456,6 +620,34 @@ func (suite *MTOServiceItemServiceSuite) TestUpdateMTOServiceItemData() { suite.NoVerrs(serviceItemData.verrs) }) + suite.Run("checkReasonWasUpdatedOnRejectedSIT - international - success", func() { + // Under test: checkReasonWasUpdatedOnRejectedSIT ensures that the reason value is being updated + // Set up: Create any SIT service item + // Expected outcome: No errors + oldServiceItem, newServiceItem := setupTestData() + + // only checks rejected SIT service items + newServiceItem.Status = models.MTOServiceItemStatusSubmitted + oldServiceItem.Status = models.MTOServiceItemStatusRejected + + // This only checks SIT service items + newServiceItem.ReService.Code = models.ReServiceCodeIDFSIT + oldServiceItem.ReService.Code = models.ReServiceCodeIDFSIT + + newServiceItem.Reason = models.StringPointer("one reason") + oldServiceItem.Reason = models.StringPointer("another reason") + + serviceItemData := updateMTOServiceItemData{ + updatedServiceItem: newServiceItem, + oldServiceItem: oldServiceItem, + verrs: validate.NewErrors(), + } + err := serviceItemData.checkReasonWasUpdatedOnRejectedSIT(suite.AppContextForTest()) + + suite.NoError(err) + suite.NoVerrs(serviceItemData.verrs) + }) + // Test getVerrs for successful example suite.Run("getVerrs - success", func() { // Under test: getVerrs returns a list of validation errors @@ -793,8 +985,9 @@ func (suite *MTOServiceItemServiceSuite) TestUpdateMTOServiceItemData() { suite.Run("SITDepartureDate - Does not error or update shipment auth end date when set after the authorized end date", func() { // Under test: checkSITDepartureDate checks that - // the SITDepartureDate is not later than the authorized end date - // Set up: Create an old and new DOPSIT and DDDSIT, with a date later than the + // the SITDepartureDate can be later than the authorized end date + // and that the authorized end dates is not updated when that occurs + // Set up: Create an old and new DOPSIT and DDDSIT, with a departure date later than the // shipment and try to update. // Expected outcome: No ERROR if departure date comes after the end date. // Shipment auth end date does not change @@ -827,12 +1020,13 @@ func (suite *MTOServiceItemServiceSuite) TestUpdateMTOServiceItemData() { }, { Model: models.MTOServiceItem{ - SITEntryDate: &later, + SITEntryDate: &before, }, }, }, nil) newSITServiceItem := oldSITServiceItem - newSITServiceItem.SITDepartureDate = &later + newSITDepartureDate := later.AddDate(0, 0, 1) + newSITServiceItem.SITDepartureDate = &newSITDepartureDate serviceItemData := updateMTOServiceItemData{ updatedServiceItem: newSITServiceItem, oldServiceItem: oldSITServiceItem, @@ -842,23 +1036,139 @@ func (suite *MTOServiceItemServiceSuite) TestUpdateMTOServiceItemData() { suite.NoError(err) suite.False(serviceItemData.verrs.HasAny()) - // Double check the shipment and ensure that the SITDepartureDate is in fact after the authorized end date + // Double check the shipment and ensure that the SITDepartureDate is after the authorized end date and does not alter the authorized end date var postUpdateShipment models.MTOShipment err = suite.DB().Find(&postUpdateShipment, mtoShipment.ID) suite.NoError(err) if tc.reServiceCode == models.ReServiceCodeDOPSIT { suite.True(mtoShipment.OriginSITAuthEndDate.Truncate(24 * time.Hour).Equal(postUpdateShipment.OriginSITAuthEndDate.Truncate(24 * time.Hour))) - suite.True(newSITServiceItem.SITEntryDate.Truncate(24 * time.Hour).After(postUpdateShipment.OriginSITAuthEndDate.Truncate(24 * time.Hour))) + suite.True(newSITServiceItem.SITDepartureDate.Truncate(24 * time.Hour).After(postUpdateShipment.OriginSITAuthEndDate.Truncate(24 * time.Hour))) } if tc.reServiceCode == models.ReServiceCodeDDDSIT { suite.True(mtoShipment.DestinationSITAuthEndDate.Truncate(24 * time.Hour).Equal(postUpdateShipment.DestinationSITAuthEndDate.Truncate(24 * time.Hour))) - suite.True(newSITServiceItem.SITEntryDate.Truncate(24 * time.Hour).After(postUpdateShipment.DestinationSITAuthEndDate.Truncate(24 * time.Hour))) + suite.True(newSITServiceItem.SITDepartureDate.Truncate(24 * time.Hour).After(postUpdateShipment.DestinationSITAuthEndDate.Truncate(24 * time.Hour))) + } + } + + }) + + suite.Run("SITDepartureDate - Does not error or update shipment auth end date when set after the authorized end date - international", func() { + // Under test: checkSITDepartureDate checks that + // the SITDepartureDate is not later than the authorized end date + // Set up: Create an old and new IOPSIT and IDDSIT, with a date later than the + // shipment and try to update. + // Expected outcome: No ERROR if departure date comes after the end date. + // Shipment auth end date does not change + mtoShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{OriginSITAuthEndDate: &now, + DestinationSITAuthEndDate: &now}, + }, + }, nil) + testCases := []struct { + reServiceCode models.ReServiceCode + }{ + { + reServiceCode: models.ReServiceCodeIOPSIT, + }, + { + reServiceCode: models.ReServiceCodeIDDSIT, + }, + } + for _, tc := range testCases { + oldSITServiceItem := factory.BuildMTOServiceItem(nil, []factory.Customization{ + { + Model: models.ReService{ + Code: tc.reServiceCode, + }, + }, + { + Model: mtoShipment, + LinkOnly: true, + }, + { + Model: models.MTOServiceItem{ + SITEntryDate: &before, + }, + }, + }, nil) + newSITServiceItem := oldSITServiceItem + newSITServiceItem.SITDepartureDate = &later + serviceItemData := updateMTOServiceItemData{ + updatedServiceItem: newSITServiceItem, + oldServiceItem: oldSITServiceItem, + verrs: validate.NewErrors(), + } + err := serviceItemData.checkSITDepartureDate(suite.AppContextForTest()) + suite.NoError(err) + suite.False(serviceItemData.verrs.HasAny()) + + // Double check the shipment and ensure that the SITDepartureDate is after the authorized end date and does not alter the authorized end date + var postUpdateShipment models.MTOShipment + err = suite.DB().Find(&postUpdateShipment, mtoShipment.ID) + suite.NoError(err) + if tc.reServiceCode == models.ReServiceCodeIOPSIT { + suite.True(mtoShipment.OriginSITAuthEndDate.Truncate(24 * time.Hour).Equal(postUpdateShipment.OriginSITAuthEndDate.Truncate(24 * time.Hour))) + suite.True(newSITServiceItem.SITDepartureDate.Truncate(24 * time.Hour).After(postUpdateShipment.OriginSITAuthEndDate.Truncate(24 * time.Hour))) + } + if tc.reServiceCode == models.ReServiceCodeIDDSIT { + suite.True(mtoShipment.DestinationSITAuthEndDate.Truncate(24 * time.Hour).Equal(postUpdateShipment.DestinationSITAuthEndDate.Truncate(24 * time.Hour))) + suite.True(newSITServiceItem.SITDepartureDate.Truncate(24 * time.Hour).After(postUpdateShipment.DestinationSITAuthEndDate.Truncate(24 * time.Hour))) } } + }) + + suite.Run("SITDepartureDate - errors when set before or equal the SIT entry date", func() { + mtoShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{OriginSITAuthEndDate: &now, + DestinationSITAuthEndDate: &now}, + }, + }, nil) + testCases := []struct { + reServiceCode models.ReServiceCode + }{ + { + reServiceCode: models.ReServiceCodeDOPSIT, + }, + { + reServiceCode: models.ReServiceCodeDDDSIT, + }, + } + for _, tc := range testCases { + oldSITServiceItem := factory.BuildMTOServiceItem(nil, []factory.Customization{ + { + Model: models.ReService{ + Code: tc.reServiceCode, + }, + }, + { + Model: mtoShipment, + LinkOnly: true, + }, + { + Model: models.MTOServiceItem{ + SITEntryDate: &later, + }, + }, + }, nil) + newSITServiceItem := oldSITServiceItem + newSITServiceItem.SITDepartureDate = &before + serviceItemData := updateMTOServiceItemData{ + updatedServiceItem: newSITServiceItem, + oldServiceItem: oldSITServiceItem, + verrs: validate.NewErrors(), + } + err := serviceItemData.checkSITDepartureDate(suite.AppContextForTest()) + suite.NoError(err) // Just verrs + suite.True(serviceItemData.verrs.HasAny()) + suite.Contains(serviceItemData.verrs.Keys(), "SITDepartureDate") + suite.Contains(serviceItemData.verrs.Get("SITDepartureDate"), "SIT departure date cannot be set before or equal to the SIT entry date.") + } }) - suite.Run("SITDepartureDate - errors when set before the SIT entry date", func() { + suite.Run("SITDepartureDate - errors when set equal to the SIT entry date", func() { mtoShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ { Model: models.MTOShipment{OriginSITAuthEndDate: &now, @@ -875,6 +1185,56 @@ func (suite *MTOServiceItemServiceSuite) TestUpdateMTOServiceItemData() { reServiceCode: models.ReServiceCodeDDDSIT, }, } + for _, tc := range testCases { + oldSITServiceItem := factory.BuildMTOServiceItem(nil, []factory.Customization{ + { + Model: models.ReService{ + Code: tc.reServiceCode, + }, + }, + { + Model: mtoShipment, + LinkOnly: true, + }, + { + Model: models.MTOServiceItem{ + SITEntryDate: &now, + }, + }, + }, nil) + newSITServiceItem := oldSITServiceItem + newSITServiceItem.SITDepartureDate = &now + serviceItemData := updateMTOServiceItemData{ + updatedServiceItem: newSITServiceItem, + oldServiceItem: oldSITServiceItem, + verrs: validate.NewErrors(), + } + err := serviceItemData.checkSITDepartureDate(suite.AppContextForTest()) + suite.NoError(err) // Just verrs + suite.True(serviceItemData.verrs.HasAny()) + suite.Contains(serviceItemData.verrs.Keys(), "SITDepartureDate") + suite.Contains(serviceItemData.verrs.Get("SITDepartureDate"), "SIT departure date cannot be set before or equal to the SIT entry date.") + } + + }) + + suite.Run("SITDepartureDate - errors when set before the SIT entry date - international", func() { + mtoShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{OriginSITAuthEndDate: &now, + DestinationSITAuthEndDate: &now}, + }, + }, nil) + testCases := []struct { + reServiceCode models.ReServiceCode + }{ + { + reServiceCode: models.ReServiceCodeIOPSIT, + }, + { + reServiceCode: models.ReServiceCodeIDDSIT, + }, + } for _, tc := range testCases { oldSITServiceItem := factory.BuildMTOServiceItem(nil, []factory.Customization{ { @@ -903,7 +1263,7 @@ func (suite *MTOServiceItemServiceSuite) TestUpdateMTOServiceItemData() { suite.NoError(err) // Just verrs suite.True(serviceItemData.verrs.HasAny()) suite.Contains(serviceItemData.verrs.Keys(), "SITDepartureDate") - suite.Contains(serviceItemData.verrs.Get("SITDepartureDate"), "SIT departure date cannot be set before the SIT entry date.") + suite.Contains(serviceItemData.verrs.Get("SITDepartureDate"), "SIT departure date cannot be set before or equal to the SIT entry date.") } }) @@ -1393,4 +1753,49 @@ func (suite *MTOServiceItemServiceSuite) TestCreateMTOServiceItemValidators() { ) suite.Contains(err.Error(), expectedError) }) + + suite.Run("checkSITEntryDateBeforeDepartureDate - success when the SIT entry date is before the SIT departure date", func() { + s := mtoServiceItemCreator{} + serviceItem := setupTestData() + //Set SIT entry date = today, SIT departure date = tomorrow + serviceItem.SITEntryDate = models.TimePointer(time.Now()) + serviceItem.SITDepartureDate = models.TimePointer(time.Now().AddDate(0, 0, 1)) + err := s.checkSITEntryDateBeforeDepartureDate(&serviceItem) + suite.NoError(err) + }) + + suite.Run("checkSITEntryDateBeforeDepartureDate - error when the SIT entry date is after the SIT departure date", func() { + s := mtoServiceItemCreator{} + serviceItem := setupTestData() + //Set SIT entry date = tomorrow, SIT departure date = today + serviceItem.SITEntryDate = models.TimePointer(time.Now().AddDate(0, 0, 1)) + serviceItem.SITDepartureDate = models.TimePointer(time.Now()) + err := s.checkSITEntryDateBeforeDepartureDate(&serviceItem) + suite.Error(err) + suite.IsType(apperror.UnprocessableEntityError{}, err) + expectedError := fmt.Sprintf( + "the SIT Departure Date (%s) must be after the SIT Entry Date (%s)", + serviceItem.SITDepartureDate.Format("2006-01-02"), + serviceItem.SITEntryDate.Format("2006-01-02"), + ) + suite.Contains(err.Error(), expectedError) + }) + + suite.Run("checkSITEntryDateBeforeDepartureDate - error when the SIT entry date is the same as the SIT departure date", func() { + s := mtoServiceItemCreator{} + serviceItem := setupTestData() + //Set SIT entry date = today, SIT departure date = today + today := models.TimePointer(time.Now()) + serviceItem.SITEntryDate = today + serviceItem.SITDepartureDate = today + err := s.checkSITEntryDateBeforeDepartureDate(&serviceItem) + suite.Error(err) + suite.IsType(apperror.UnprocessableEntityError{}, err) + expectedError := fmt.Sprintf( + "the SIT Departure Date (%s) must be after the SIT Entry Date (%s)", + serviceItem.SITDepartureDate.Format("2006-01-02"), + serviceItem.SITEntryDate.Format("2006-01-02"), + ) + suite.Contains(err.Error(), expectedError) + }) } diff --git a/pkg/services/mto_shipment/mto_shipment_creator_test.go b/pkg/services/mto_shipment/mto_shipment_creator_test.go index 75138d1d4a3..aa46f145aee 100644 --- a/pkg/services/mto_shipment/mto_shipment_creator_test.go +++ b/pkg/services/mto_shipment/mto_shipment_creator_test.go @@ -286,6 +286,41 @@ func (suite *MTOShipmentServiceSuite) TestCreateMTOShipment() { suite.Equal("failed to create pickup address - the country GB is not supported at this time - only US is allowed", err.Error()) }) + suite.Run("If the shipment has an international address it should be returned", func() { + subtestData := suite.createSubtestData(nil) + creator := subtestData.shipmentCreator + + internationalAddress := factory.BuildAddress(nil, []factory.Customization{ + { + Model: models.Country{ + Country: "GB", + CountryName: "UNITED KINGDOM", + }, + }, + }, nil) + // stubbed countries need an ID + internationalAddress.ID = uuid.Must(uuid.NewV4()) + + mtoShipment := factory.BuildMTOShipment(nil, []factory.Customization{ + { + Model: subtestData.move, + LinkOnly: true, + }, + { + Model: internationalAddress, + LinkOnly: true, + }, + }, nil) + + mtoShipmentClear := clearShipmentIDFields(&mtoShipment) + mtoShipmentClear.MTOServiceItems = models.MTOServiceItems{} + + _, err := creator.CreateMTOShipment(suite.AppContextForTest(), mtoShipmentClear) + + suite.Error(err) + suite.Equal("failed to create pickup address - the country GB is not supported at this time - only US is allowed", err.Error()) + }) + suite.Run("If the shipment is created successfully it should return ShipmentLocator", func() { subtestData := suite.createSubtestData(nil) creator := subtestData.shipmentCreator diff --git a/pkg/services/mto_shipment/mto_shipment_updater.go b/pkg/services/mto_shipment/mto_shipment_updater.go index fb75c795a77..14e43668f68 100644 --- a/pkg/services/mto_shipment/mto_shipment_updater.go +++ b/pkg/services/mto_shipment/mto_shipment_updater.go @@ -430,6 +430,8 @@ func (f *mtoShipmentUpdater) UpdateMTOShipment(appCtx appcontext.AppContext, mto // update fails, the entire transaction will be rolled back. func (f *mtoShipmentUpdater) updateShipmentRecord(appCtx appcontext.AppContext, dbShipment *models.MTOShipment, newShipment *models.MTOShipment, eTag string) error { var autoReweighShipments models.MTOShipments + var verrs *validate.Errors + var move *models.Move transactionError := appCtx.NewTransaction(func(txnAppCtx appcontext.AppContext) error { // temp optimistic locking solution til query builder is re-tooled to handle nested updates updatedAt, err := etag.DecodeEtag(eTag) @@ -714,7 +716,7 @@ func (f *mtoShipmentUpdater) updateShipmentRecord(appCtx appcontext.AppContext, } if copyOfAgent.ID == uuid.Nil { // create a new agent if it doesn't already exist - verrs, err := f.builder.CreateOne(txnAppCtx, ©OfAgent) + verrs, err = f.builder.CreateOne(txnAppCtx, ©OfAgent) if verrs != nil && verrs.HasAny() { return verrs } @@ -725,11 +727,12 @@ func (f *mtoShipmentUpdater) updateShipmentRecord(appCtx appcontext.AppContext, } } + // var move *models.Move // If the estimated weight was updated on an approved shipment then it would mean the move could qualify for // excess weight risk depending on the weight allowance and other shipment estimated weights if newShipment.PrimeEstimatedWeight != nil || (newShipment.ShipmentType == models.MTOShipmentTypeHHGOutOfNTS && newShipment.NTSRecordedWeight != nil) { // checking if the total of shipment weight & new prime estimated weight is 90% or more of allowed weight - move, verrs, err := f.moveWeights.CheckExcessWeight(txnAppCtx, dbShipment.MoveTaskOrderID, *newShipment) + move, verrs, err = f.moveWeights.CheckExcessWeight(txnAppCtx, dbShipment.MoveTaskOrderID, *newShipment) if verrs != nil && verrs.HasAny() { return errors.New(verrs.Error()) } @@ -768,13 +771,12 @@ func (f *mtoShipmentUpdater) updateShipmentRecord(appCtx appcontext.AppContext, } } - if newShipment.PrimeActualWeight != nil { - if dbShipment.PrimeActualWeight == nil || *newShipment.PrimeActualWeight != *dbShipment.PrimeActualWeight { - var err error - autoReweighShipments, err = f.moveWeights.CheckAutoReweigh(txnAppCtx, dbShipment.MoveTaskOrderID, newShipment) - if err != nil { - return err - } + if (dbShipment.PrimeEstimatedWeight == nil || *newShipment.PrimeEstimatedWeight != *dbShipment.PrimeEstimatedWeight) || + (newShipment.PrimeActualWeight != nil && dbShipment.PrimeActualWeight == nil || *newShipment.PrimeActualWeight != *dbShipment.PrimeActualWeight) { + var err error + autoReweighShipments, err = f.moveWeights.CheckAutoReweigh(txnAppCtx, dbShipment.MoveTaskOrderID, newShipment) + if err != nil { + return err } } @@ -848,6 +850,16 @@ func (f *mtoShipmentUpdater) updateShipmentRecord(appCtx appcontext.AppContext, newShipment = models.DetermineShipmentMarketCode(newShipment) } + // RDD for UB shipments only need the pick up date, shipment origin address and destination address to determine required delivery date + if newShipment.ScheduledPickupDate != nil && !newShipment.ScheduledPickupDate.IsZero() && newShipment.ShipmentType == models.MTOShipmentTypeUnaccompaniedBaggage { + calculatedRDD, err := CalculateRequiredDeliveryDate(appCtx, f.planner, *newShipment.PickupAddress, *newShipment.DestinationAddress, *newShipment.ScheduledPickupDate, newShipment.PrimeEstimatedWeight.Int(), newShipment.MarketCode, newShipment.MoveTaskOrderID, newShipment.ShipmentType) + if err != nil { + return err + } + + newShipment.RequiredDeliveryDate = calculatedRDD + } + if err := txnAppCtx.DB().Update(newShipment); err != nil { return err } @@ -1075,7 +1087,7 @@ func (o *mtoShipmentStatusUpdater) setRequiredDeliveryDate(appCtx appcontext.App pickupLocation = shipment.PickupAddress deliveryLocation = shipment.DestinationAddress } - requiredDeliveryDate, calcErr := CalculateRequiredDeliveryDate(appCtx, o.planner, *pickupLocation, *deliveryLocation, *shipment.ScheduledPickupDate, weight.Int(), shipment.MarketCode) + requiredDeliveryDate, calcErr := CalculateRequiredDeliveryDate(appCtx, o.planner, *pickupLocation, *deliveryLocation, *shipment.ScheduledPickupDate, weight.Int(), shipment.MarketCode, shipment.MoveTaskOrderID, shipment.ShipmentType) if calcErr != nil { return calcErr } @@ -1192,18 +1204,7 @@ func reServiceCodesForShipment(shipment models.MTOShipment) []models.ReServiceCo // CalculateRequiredDeliveryDate function is used to get a distance calculation using the pickup and destination addresses. It then uses // the value returned to make a fetch on the ghc_domestic_transit_times table and returns a required delivery date // based on the max_days_transit_time. -func CalculateRequiredDeliveryDate(appCtx appcontext.AppContext, planner route.Planner, pickupAddress models.Address, destinationAddress models.Address, pickupDate time.Time, weight int, marketCode models.MarketCode) (*time.Time, error) { - // Okay, so this is something to get us able to take care of the 20 day condition over in the gdoc linked in this - // story: https://dp3.atlassian.net/browse/MB-1141 - // We unfortunately didn't get a lot of guidance regarding vicinity. So for now we're taking zip codes that are the - // explicitly mentioned 20 day cities and those in the same county (that I've manually compiled together here). - // If a move is in that group it adds 20 days, if it's not in that group, but is in Alaska it adds 10 days. - // Else it will not do either of those things. - // The cities for 20 days are: Adak, Kodiak, Juneau, Ketchikan, and Sitka. As well as others in their 'vicinity.' - twentyDayAKZips := [28]string{"99546", "99547", "99591", "99638", "99660", "99685", "99692", "99550", "99608", - "99615", "99619", "99624", "99643", "99644", "99697", "99650", "99801", "99802", "99803", "99811", "99812", - "99950", "99824", "99850", "99901", "99928", "99950", "99835"} - +func CalculateRequiredDeliveryDate(appCtx appcontext.AppContext, planner route.Planner, pickupAddress models.Address, destinationAddress models.Address, pickupDate time.Time, weight int, marketCode models.MarketCode, moveID uuid.UUID, shipmentType models.MTOShipmentType) (*time.Time, error) { internationalShipment := marketCode == models.MarketCodeInternational distance, err := planner.ZipTransitDistance(appCtx, pickupAddress.PostalCode, destinationAddress.PostalCode, internationalShipment) @@ -1225,17 +1226,65 @@ func CalculateRequiredDeliveryDate(appCtx appcontext.AppContext, planner route.P // Add the max transit time to the pickup date to get the new required delivery date requiredDeliveryDate := pickupDate.AddDate(0, 0, ghcDomesticTransitTime.MaxDaysTransitTime) - // Let's add some days if we're dealing with an alaska shipment. - if destinationAddress.State == "AK" { - for _, zip := range twentyDayAKZips { - if destinationAddress.PostalCode == zip { - // Add an extra 10 days here, so that after we add the 10 for being in AK we wind up with a total of 20 - requiredDeliveryDate = requiredDeliveryDate.AddDate(0, 0, 10) - break + destinationIsAlaska, err := destinationAddress.IsAddressAlaska() + if err != nil { + return nil, fmt.Errorf("destination address is nil for move ID: %s", moveID) + } + pickupIsAlaska, err := pickupAddress.IsAddressAlaska() + if err != nil { + return nil, fmt.Errorf("pickup address is nil for move ID: %s", moveID) + } + // Let's add some days if we're dealing with a shipment between CONUS/Alaska + if (destinationIsAlaska || pickupIsAlaska) && !(destinationIsAlaska && pickupIsAlaska) { + var rateAreaID uuid.UUID + var intlTransTime models.InternationalTransitTime + + contract, err := models.FetchContractForMove(appCtx, moveID) + if err != nil { + return nil, fmt.Errorf("error fetching contract for move ID: %s", moveID) + } + + if destinationIsAlaska { + rateAreaID, err = models.FetchRateAreaID(appCtx.DB(), destinationAddress.ID, &uuid.Nil, contract.ID) + if err != nil { + return nil, fmt.Errorf("error fetching destination rate area id for address ID: %s", destinationAddress.ID) + } + err = appCtx.DB().Where("destination_rate_area_id = $1", rateAreaID).First(&intlTransTime) + if err != nil { + switch err { + case sql.ErrNoRows: + return nil, fmt.Errorf("no international transit time found for destination rate area ID: %s", rateAreaID) + default: + return nil, err + } + } + } + + if pickupIsAlaska { + rateAreaID, err = models.FetchRateAreaID(appCtx.DB(), pickupAddress.ID, &uuid.Nil, contract.ID) + if err != nil { + return nil, fmt.Errorf("error fetching pickup rate area id for address ID: %s", pickupAddress.ID) + } + err = appCtx.DB().Where("origin_rate_area_id = $1", rateAreaID).First(&intlTransTime) + if err != nil { + switch err { + case sql.ErrNoRows: + return nil, fmt.Errorf("no international transit time found for pickup rate area ID: %s", rateAreaID) + default: + return nil, err + } + } + } + + if shipmentType != models.MTOShipmentTypeUnaccompaniedBaggage { + if intlTransTime.HhgTransitTime != nil { + requiredDeliveryDate = requiredDeliveryDate.AddDate(0, 0, *intlTransTime.HhgTransitTime) + } + } else { + if intlTransTime.UbTransitTime != nil { + requiredDeliveryDate = requiredDeliveryDate.AddDate(0, 0, *intlTransTime.UbTransitTime) } } - // Add an extra 10 days for being in AK - requiredDeliveryDate = requiredDeliveryDate.AddDate(0, 0, 10) } // return the value diff --git a/pkg/services/mto_shipment/mto_shipment_updater_test.go b/pkg/services/mto_shipment/mto_shipment_updater_test.go index 5cdd5100b13..a7d995c4c9b 100644 --- a/pkg/services/mto_shipment/mto_shipment_updater_test.go +++ b/pkg/services/mto_shipment/mto_shipment_updater_test.go @@ -156,6 +156,9 @@ func (suite *MTOShipmentServiceSuite) TestMTOShipmentUpdater() { eTag := etag.GenerateEtag(time.Now()) + var testScheduledPickupDate time.Time + mtoShipment.ScheduledPickupDate = &testScheduledPickupDate + session := auth.Session{} _, err := mtoShipmentUpdaterCustomer.UpdateMTOShipment(suite.AppContextWithSessionForTest(&session), &mtoShipment, eTag, "test") suite.Error(err) @@ -191,6 +194,8 @@ func (suite *MTOShipmentServiceSuite) TestMTOShipmentUpdater() { setupTestData() eTag := etag.GenerateEtag(oldMTOShipment.UpdatedAt) + var testScheduledPickupDate time.Time + mtoShipment.ScheduledPickupDate = &testScheduledPickupDate session := auth.Session{} updatedMTOShipment, err := mtoShipmentUpdaterCustomer.UpdateMTOShipment(suite.AppContextWithSessionForTest(&session), &mtoShipment, eTag, "test") @@ -211,11 +216,13 @@ func (suite *MTOShipmentServiceSuite) TestMTOShipmentUpdater() { suite.Run("Updater can handle optional queries set as nil", func() { setupTestData() + var testScheduledPickupDate time.Time oldMTOShipment2 := factory.BuildMTOShipment(suite.DB(), nil, nil) mtoShipment2 := models.MTOShipment{ - ID: oldMTOShipment2.ID, - ShipmentType: "UNACCOMPANIED_BAGGAGE", + ID: oldMTOShipment2.ID, + ShipmentType: "UNACCOMPANIED_BAGGAGE", + ScheduledPickupDate: &testScheduledPickupDate, } eTag := etag.GenerateEtag(oldMTOShipment2.UpdatedAt) @@ -2462,6 +2469,279 @@ func (suite *MTOShipmentServiceSuite) TestUpdateMTOShipmentStatus() { } }) + suite.Run("Test that we are properly adding days to Alaska shipments", func() { + reContract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + Contract: reContract, + ContractID: reContract.ID, + StartDate: time.Now(), + EndDate: time.Now().Add(time.Hour * 12), + Escalation: 1.0, + EscalationCompounded: 1.0, + }, + }) + move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + appCtx := suite.AppContextForTest() + + ghcDomesticTransitTime0LbsUpper := models.GHCDomesticTransitTime{ + MaxDaysTransitTime: 12, + WeightLbsLower: 10001, + WeightLbsUpper: 0, + DistanceMilesLower: 0, + DistanceMilesUpper: 10000, + } + verrs, err := suite.DB().ValidateAndCreate(&ghcDomesticTransitTime0LbsUpper) + suite.Assert().False(verrs.HasAny()) + suite.NoError(err) + + conusAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress2}) + zone1Address := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddressAKZone1}) + zone2Address := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddressAKZone2}) + zone3Address := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddressAKZone3}) + zone4Address := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddressAKZone4}) + zone5Address := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddressAKZone5}) + + estimatedWeight := unit.Pound(11000) + + testCases10Days := []struct { + pickupLocation models.Address + destinationLocation models.Address + }{ + {conusAddress, zone1Address}, + {conusAddress, zone2Address}, + {zone1Address, conusAddress}, + {zone2Address, conusAddress}, + } + // adding 22 days; ghcDomesticTransitTime0LbsUpper.MaxDaysTransitTime is 12, plus 10 for Zones 1 and 2 + rdd10DaysDate := testdatagen.DateInsidePeakRateCycle.AddDate(0, 0, 22) + for _, testCase := range testCases10Days { + shipment := factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + ShipmentType: models.MTOShipmentTypeHHG, + ScheduledPickupDate: &testdatagen.DateInsidePeakRateCycle, + PrimeEstimatedWeight: &estimatedWeight, + Status: models.MTOShipmentStatusSubmitted, + }, + }, + { + Model: testCase.pickupLocation, + Type: &factory.Addresses.PickupAddress, + LinkOnly: true, + }, + { + Model: testCase.destinationLocation, + Type: &factory.Addresses.DeliveryAddress, + LinkOnly: true, + }, + }, nil) + shipmentEtag := etag.GenerateEtag(shipment.UpdatedAt) + _, err = updater.UpdateMTOShipmentStatus(appCtx, shipment.ID, status, nil, nil, shipmentEtag) + suite.NoError(err) + + fetchedShipment := models.MTOShipment{} + err = suite.DB().Find(&fetchedShipment, shipment.ID) + suite.NoError(err) + suite.NotNil(fetchedShipment.RequiredDeliveryDate) + suite.Equal(rdd10DaysDate.Format(time.RFC3339), fetchedShipment.RequiredDeliveryDate.Format(time.RFC3339)) + } + + testCases20Days := []struct { + pickupLocation models.Address + destinationLocation models.Address + }{ + {conusAddress, zone3Address}, + {conusAddress, zone4Address}, + {zone3Address, conusAddress}, + {zone4Address, conusAddress}, + } + // adding 32 days; ghcDomesticTransitTime0LbsUpper.MaxDaysTransitTime is 12, plus 20 for Zones 3 and 4 + rdd20DaysDate := testdatagen.DateInsidePeakRateCycle.AddDate(0, 0, 32) + for _, testCase := range testCases20Days { + shipment := factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + ShipmentType: models.MTOShipmentTypeHHG, + ScheduledPickupDate: &testdatagen.DateInsidePeakRateCycle, + PrimeEstimatedWeight: &estimatedWeight, + Status: models.MTOShipmentStatusSubmitted, + }, + }, + { + Model: testCase.pickupLocation, + Type: &factory.Addresses.PickupAddress, + LinkOnly: true, + }, + { + Model: testCase.destinationLocation, + Type: &factory.Addresses.DeliveryAddress, + LinkOnly: true, + }, + }, nil) + shipmentEtag := etag.GenerateEtag(shipment.UpdatedAt) + _, err = updater.UpdateMTOShipmentStatus(appCtx, shipment.ID, status, nil, nil, shipmentEtag) + suite.NoError(err) + + fetchedShipment := models.MTOShipment{} + err = suite.DB().Find(&fetchedShipment, shipment.ID) + suite.NoError(err) + suite.NotNil(fetchedShipment.RequiredDeliveryDate) + suite.Equal(rdd20DaysDate.Format(time.RFC3339), fetchedShipment.RequiredDeliveryDate.Format(time.RFC3339)) + } + testCases60Days := []struct { + pickupLocation models.Address + destinationLocation models.Address + }{ + {conusAddress, zone5Address}, + {zone5Address, conusAddress}, + } + + // adding 72 days; ghcDomesticTransitTime0LbsUpper.MaxDaysTransitTime is 12, plus 60 for Zone 5 HHG + rdd60DaysDate := testdatagen.DateInsidePeakRateCycle.AddDate(0, 0, 72) + for _, testCase := range testCases60Days { + shipment := factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + ShipmentType: models.MTOShipmentTypeHHG, + ScheduledPickupDate: &testdatagen.DateInsidePeakRateCycle, + PrimeEstimatedWeight: &estimatedWeight, + Status: models.MTOShipmentStatusSubmitted, + }, + }, + { + Model: testCase.pickupLocation, + Type: &factory.Addresses.PickupAddress, + LinkOnly: true, + }, + { + Model: testCase.destinationLocation, + Type: &factory.Addresses.DeliveryAddress, + LinkOnly: true, + }, + }, nil) + shipmentEtag := etag.GenerateEtag(shipment.UpdatedAt) + _, err = updater.UpdateMTOShipmentStatus(appCtx, shipment.ID, status, nil, nil, shipmentEtag) + suite.NoError(err) + + fetchedShipment := models.MTOShipment{} + err = suite.DB().Find(&fetchedShipment, shipment.ID) + suite.NoError(err) + suite.NotNil(fetchedShipment.RequiredDeliveryDate) + suite.Equal(rdd60DaysDate.Format(time.RFC3339), fetchedShipment.RequiredDeliveryDate.Format(time.RFC3339)) + } + + // adding 42 days; ghcDomesticTransitTime0LbsUpper.MaxDaysTransitTime is 12, plus 30 for Zone 5 UB + rdd60DaysDateUB := testdatagen.DateInsidePeakRateCycle.AddDate(0, 0, 42) + for _, testCase := range testCases60Days { + shipment := factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + ShipmentType: models.MTOShipmentTypeUnaccompaniedBaggage, + ScheduledPickupDate: &testdatagen.DateInsidePeakRateCycle, + PrimeEstimatedWeight: &estimatedWeight, + Status: models.MTOShipmentStatusSubmitted, + }, + }, + { + Model: testCase.pickupLocation, + Type: &factory.Addresses.PickupAddress, + LinkOnly: true, + }, + { + Model: testCase.destinationLocation, + Type: &factory.Addresses.DeliveryAddress, + LinkOnly: true, + }, + }, nil) + shipmentEtag := etag.GenerateEtag(shipment.UpdatedAt) + _, err = updater.UpdateMTOShipmentStatus(appCtx, shipment.ID, status, nil, nil, shipmentEtag) + suite.NoError(err) + + fetchedShipment := models.MTOShipment{} + err = suite.DB().Find(&fetchedShipment, shipment.ID) + suite.NoError(err) + suite.NotNil(fetchedShipment.RequiredDeliveryDate) + suite.Equal(rdd60DaysDateUB.Format(time.RFC3339), fetchedShipment.RequiredDeliveryDate.Format(time.RFC3339)) + } + }) + + suite.Run("Update RDD on UB Shipment on status change", func() { + reContract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + Contract: reContract, + ContractID: reContract.ID, + StartDate: time.Now(), + EndDate: time.Now().Add(time.Hour * 12), + Escalation: 1.0, + EscalationCompounded: 1.0, + }, + }) + move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + appCtx := suite.AppContextForTest() + + ghcDomesticTransitTime0LbsUpper := models.GHCDomesticTransitTime{ + MaxDaysTransitTime: 12, + WeightLbsLower: 10001, + WeightLbsUpper: 0, + DistanceMilesLower: 0, + DistanceMilesUpper: 10000, + } + verrs, err := suite.DB().ValidateAndCreate(&ghcDomesticTransitTime0LbsUpper) + suite.Assert().False(verrs.HasAny()) + suite.NoError(err) + + conusAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress2}) + zone1Address := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddressAKZone1}) + estimatedWeight := unit.Pound(11000) + shipment := factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + ShipmentType: models.MTOShipmentTypeUnaccompaniedBaggage, + ScheduledPickupDate: &testdatagen.DateInsidePeakRateCycle, + PrimeEstimatedWeight: &estimatedWeight, + Status: models.MTOShipmentStatusSubmitted, + }, + }, + { + Model: conusAddress, + Type: &factory.Addresses.PickupAddress, + LinkOnly: true, + }, + { + Model: zone1Address, + Type: &factory.Addresses.DeliveryAddress, + LinkOnly: true, + }, + }, nil) + shipmentEtag := etag.GenerateEtag(shipment.UpdatedAt) + mtoShipment, err := updater.UpdateMTOShipmentStatus(appCtx, shipment.ID, status, nil, nil, shipmentEtag) + suite.NoError(err) + suite.NotNil(mtoShipment.RequiredDeliveryDate) + suite.False(mtoShipment.RequiredDeliveryDate.IsZero()) + }) + suite.Run("Cannot set SUBMITTED status on shipment via UpdateMTOShipmentStatus", func() { setupTestData() @@ -3137,8 +3417,6 @@ func (suite *MTOShipmentServiceSuite) TestUpdateShipmentActualWeightAutoReweigh( }, nil) // there is a validator check about updating the status primeShipment.Status = "" - estimatedWeight := unit.Pound(7200) - primeShipment.PrimeEstimatedWeight = &estimatedWeight moveWeights.On("CheckExcessWeight", mock.AnythingOfType("*appcontext.appContext"), primeShipment.MoveTaskOrderID, mock.AnythingOfType("models.MTOShipment")).Return(&primeShipment.MoveTaskOrder, nil, nil) @@ -3161,14 +3439,15 @@ func (suite *MTOShipmentServiceSuite) TestUpdateShipmentActualWeightAutoReweigh( now := time.Now() pickupDate := now.AddDate(0, 0, 10) - actualWeight := unit.Pound(7200) + weight := unit.Pound(7200) primeShipment := factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{ { Model: models.MTOShipment{ - Status: models.MTOShipmentStatusApproved, - ApprovedDate: &now, - ScheduledPickupDate: &pickupDate, - PrimeActualWeight: &actualWeight, + Status: models.MTOShipmentStatusApproved, + ApprovedDate: &now, + ScheduledPickupDate: &pickupDate, + PrimeActualWeight: &weight, + PrimeEstimatedWeight: &weight, }, }, { @@ -3180,7 +3459,8 @@ func (suite *MTOShipmentServiceSuite) TestUpdateShipmentActualWeightAutoReweigh( }, nil) // there is a validator check about updating the status primeShipment.Status = "" - primeShipment.PrimeActualWeight = &actualWeight + primeShipment.PrimeActualWeight = &weight + primeShipment.PrimeEstimatedWeight = &weight session := auth.Session{} _, err := mockedUpdater.UpdateMTOShipment(suite.AppContextWithSessionForTest(&session), &primeShipment, etag.GenerateEtag(primeShipment.UpdatedAt), "test") @@ -3547,3 +3827,97 @@ func (suite *MTOShipmentServiceSuite) TestUpdateDomesticServiceItems() { } }) } + +func (suite *MTOShipmentServiceSuite) TestUpdateRequiredDeliveryDateUpdate() { + + builder := query.NewQueryBuilder() + fetcher := fetch.NewFetcher(builder) + planner := &mocks.Planner{} + planner.On("ZipTransitDistance", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("string"), + mock.AnythingOfType("string"), + true, + ).Return(500, nil) + moveRouter := moveservices.NewMoveRouter() + waf := entitlements.NewWeightAllotmentFetcher() + moveWeights := moveservices.NewMoveWeights(NewShipmentReweighRequester(), waf) + mockShipmentRecalculator := mockservices.PaymentRequestShipmentRecalculator{} + mockSender := setUpMockNotificationSender() + addressCreator := address.NewAddressCreator() + addressUpdater := address.NewAddressUpdater() + + mtoShipmentUpdaterPrime := NewPrimeMTOShipmentUpdater(builder, fetcher, planner, moveRouter, moveWeights, mockSender, &mockShipmentRecalculator, addressUpdater, addressCreator) + + suite.Run("should update requiredDeliveryDate when scheduledPickupDate is updated", func() { + reContract := testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + Contract: reContract, + ContractID: reContract.ID, + StartDate: time.Now(), + EndDate: time.Now().AddDate(1, 0, 0), + Escalation: 1.0, + EscalationCompounded: 1.0, + }, + }) + move := factory.BuildAvailableToPrimeMove(suite.DB(), nil, nil) + appCtx := suite.AppContextForTest() + + ghcDomesticTransitTime := models.GHCDomesticTransitTime{ + MaxDaysTransitTime: 12, + WeightLbsLower: 0, + WeightLbsUpper: 10000, + DistanceMilesLower: 0, + DistanceMilesUpper: 10000, + } + verrs, err := suite.DB().ValidateAndCreate(&ghcDomesticTransitTime) + suite.Assert().False(verrs.HasAny()) + suite.NoError(err) + + conusAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress2}) + zone1Address := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddressAKZone1}) + estimatedWeight := unit.Pound(4000) + oldUbShipment := factory.BuildMTOShipmentMinimal(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + ShipmentType: models.MTOShipmentTypeUnaccompaniedBaggage, + ScheduledPickupDate: &testdatagen.DateInsidePeakRateCycle, + PrimeEstimatedWeight: &estimatedWeight, + Status: models.MTOShipmentStatusSubmitted, + }, + }, + { + Model: conusAddress, + Type: &factory.Addresses.PickupAddress, + LinkOnly: true, + }, + { + Model: zone1Address, + Type: &factory.Addresses.DeliveryAddress, + LinkOnly: true, + }, + }, nil) + + suite.Nil(oldUbShipment.RequiredDeliveryDate) + + pickUpDate := time.Now().AddDate(0, 0, 12) + newUbShipment := models.MTOShipment{ + ID: oldUbShipment.ID, + ShipmentType: models.MTOShipmentTypeUnaccompaniedBaggage, + ScheduledPickupDate: &pickUpDate, + } + + eTag := etag.GenerateEtag(oldUbShipment.UpdatedAt) + updatedMTOShipment, err := mtoShipmentUpdaterPrime.UpdateMTOShipment(appCtx, &newUbShipment, eTag, "test") + + suite.Nil(err) + suite.NotNil(updatedMTOShipment) + suite.NotNil(updatedMTOShipment.RequiredDeliveryDate) + suite.False(updatedMTOShipment.RequiredDeliveryDate.IsZero()) + }) +} diff --git a/pkg/services/mto_shipment/rules.go b/pkg/services/mto_shipment/rules.go index 0fe7e481ebc..9a40f09604b 100644 --- a/pkg/services/mto_shipment/rules.go +++ b/pkg/services/mto_shipment/rules.go @@ -337,13 +337,13 @@ func checkPrimeValidationsOnModel(planner route.Planner) validator { // If we have all the data, calculate RDD if latestSchedPickupDate != nil && (latestEstimatedWeight != nil || (older.ShipmentType == models.MTOShipmentTypeHHGOutOfNTS && - older.NTSRecordedWeight != nil)) && latestPickupAddress != nil && latestDestinationAddress != nil { + older.NTSRecordedWeight != nil)) && latestPickupAddress != nil && latestDestinationAddress != nil && older.ShipmentType != models.MTOShipmentTypeUnaccompaniedBaggage { weight := latestEstimatedWeight if older.ShipmentType == models.MTOShipmentTypeHHGOutOfNTS && older.NTSRecordedWeight != nil { weight = older.NTSRecordedWeight } requiredDeliveryDate, err := CalculateRequiredDeliveryDate(appCtx, planner, *latestPickupAddress, - *latestDestinationAddress, *latestSchedPickupDate, weight.Int(), older.MarketCode) + *latestDestinationAddress, *latestSchedPickupDate, weight.Int(), older.MarketCode, older.MoveTaskOrderID, older.ShipmentType) if err != nil { verrs.Add("requiredDeliveryDate", err.Error()) } diff --git a/pkg/services/mto_shipment/shipment_approver.go b/pkg/services/mto_shipment/shipment_approver.go index 9191657787c..801507ea7a6 100644 --- a/pkg/services/mto_shipment/shipment_approver.go +++ b/pkg/services/mto_shipment/shipment_approver.go @@ -247,7 +247,7 @@ func (f *shipmentApprover) setRequiredDeliveryDate(appCtx appcontext.AppContext, deliveryLocation = shipment.DestinationAddress weight = shipment.PrimeEstimatedWeight.Int() } - requiredDeliveryDate, calcErr := CalculateRequiredDeliveryDate(appCtx, f.planner, *pickupLocation, *deliveryLocation, *shipment.ScheduledPickupDate, weight, shipment.MarketCode) + requiredDeliveryDate, calcErr := CalculateRequiredDeliveryDate(appCtx, f.planner, *pickupLocation, *deliveryLocation, *shipment.ScheduledPickupDate, weight, shipment.MarketCode, shipment.MoveTaskOrderID, shipment.ShipmentType) if calcErr != nil { return calcErr } diff --git a/pkg/services/mto_shipment/shipment_approver_test.go b/pkg/services/mto_shipment/shipment_approver_test.go index 492f0862bad..9d776f2f4da 100644 --- a/pkg/services/mto_shipment/shipment_approver_test.go +++ b/pkg/services/mto_shipment/shipment_approver_test.go @@ -1290,7 +1290,7 @@ func (suite *MTOShipmentServiceSuite) TestApproveShipment() { } expectedReServiceNames := []string{ "International UB price", - "International POE Fuel Surcharge", + "International POE fuel surcharge", "International UB pack", "International UB unpack", } @@ -1305,6 +1305,7 @@ func (suite *MTOShipmentServiceSuite) TestApproveShipment() { }) suite.Run("If the OCONUS to CONUS UB mtoShipment is approved successfully it should create pre approved mtoServiceItems", func() { + var scheduledPickupDate time.Time internationalShipment := factory.BuildMTOShipment(suite.AppContextForTest().DB(), []factory.Customization{ { Model: models.Move{ @@ -1323,9 +1324,10 @@ func (suite *MTOShipmentServiceSuite) TestApproveShipment() { }, { Model: models.MTOShipment{ - MarketCode: models.MarketCodeInternational, - Status: models.MTOShipmentStatusSubmitted, - ShipmentType: models.MTOShipmentTypeUnaccompaniedBaggage, + MarketCode: models.MarketCodeInternational, + Status: models.MTOShipmentStatusSubmitted, + ShipmentType: models.MTOShipmentTypeUnaccompaniedBaggage, + ScheduledPickupDate: &scheduledPickupDate, }, }, { @@ -1358,7 +1360,7 @@ func (suite *MTOShipmentServiceSuite) TestApproveShipment() { } expectedReServiceNames := []string{ "International UB price", - "International POD Fuel Surcharge", + "International POD fuel surcharge", "International UB pack", "International UB unpack", } @@ -1373,6 +1375,7 @@ func (suite *MTOShipmentServiceSuite) TestApproveShipment() { }) suite.Run("If the OCONUS to OCONUS UB mtoShipment is approved successfully it should create pre approved mtoServiceItems", func() { + var scheduledPickupDate time.Time internationalShipment := factory.BuildMTOShipment(suite.AppContextForTest().DB(), []factory.Customization{ { Model: models.Move{ @@ -1391,9 +1394,10 @@ func (suite *MTOShipmentServiceSuite) TestApproveShipment() { }, { Model: models.MTOShipment{ - MarketCode: models.MarketCodeInternational, - Status: models.MTOShipmentStatusSubmitted, - ShipmentType: models.MTOShipmentTypeUnaccompaniedBaggage, + MarketCode: models.MarketCodeInternational, + Status: models.MTOShipmentStatusSubmitted, + ShipmentType: models.MTOShipmentTypeUnaccompaniedBaggage, + ScheduledPickupDate: &scheduledPickupDate, }, }, { diff --git a/pkg/services/office_user/office_user_fetcher.go b/pkg/services/office_user/office_user_fetcher.go index 99a6448f1fe..9708f4c137b 100644 --- a/pkg/services/office_user/office_user_fetcher.go +++ b/pkg/services/office_user/office_user_fetcher.go @@ -151,7 +151,8 @@ func (o *officeUserFetcherPop) FetchOfficeUsersWithWorkloadByRoleAndOffice(appCt WHERE roles.role_type = $1 AND transportation_offices.id = $2 AND office_users.active = TRUE - GROUP BY office_users.id, office_users.first_name, office_users.last_name` + GROUP BY office_users.id, office_users.first_name, office_users.last_name + ORDER BY office_users.last_name ASC, office_users.first_name ASC` err := appCtx.DB().RawQuery(query, role, officeID).All(&officeUsers) if err != nil { diff --git a/pkg/services/orchestrators/shipment/shipment_updater.go b/pkg/services/orchestrators/shipment/shipment_updater.go index 70b0406a842..d8d82af8e90 100644 --- a/pkg/services/orchestrators/shipment/shipment_updater.go +++ b/pkg/services/orchestrators/shipment/shipment_updater.go @@ -6,6 +6,7 @@ import ( "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/unit" ) // shipmentUpdater is the concrete struct implementing the services.ShipmentUpdater interface @@ -15,16 +16,18 @@ type shipmentUpdater struct { ppmShipmentUpdater services.PPMShipmentUpdater boatShipmentUpdater services.BoatShipmentUpdater mobileHomeShipmentUpdater services.MobileHomeShipmentUpdater + mtoServiceItemCreator services.MTOServiceItemCreator } // NewShipmentUpdater creates a new shipmentUpdater struct with the basic checks and service dependencies. -func NewShipmentUpdater(mtoShipmentUpdater services.MTOShipmentUpdater, ppmShipmentUpdater services.PPMShipmentUpdater, boatShipmentUpdater services.BoatShipmentUpdater, mobileHomeShipmentUpdater services.MobileHomeShipmentUpdater) services.ShipmentUpdater { +func NewShipmentUpdater(mtoShipmentUpdater services.MTOShipmentUpdater, ppmShipmentUpdater services.PPMShipmentUpdater, boatShipmentUpdater services.BoatShipmentUpdater, mobileHomeShipmentUpdater services.MobileHomeShipmentUpdater, mtoServiceItemCreator services.MTOServiceItemCreator) services.ShipmentUpdater { return &shipmentUpdater{ checks: basicShipmentChecks(), mtoShipmentUpdater: mtoShipmentUpdater, ppmShipmentUpdater: ppmShipmentUpdater, boatShipmentUpdater: boatShipmentUpdater, mobileHomeShipmentUpdater: mobileHomeShipmentUpdater, + mtoServiceItemCreator: mtoServiceItemCreator, } } @@ -43,6 +46,20 @@ func (s *shipmentUpdater) UpdateShipment(appCtx appcontext.AppContext, shipment return err } + if mtoShipment != nil && (mtoShipment.ShipmentType != models.MTOShipmentTypePPM) && (shipment.PrimeEstimatedWeight != nil || mtoShipment.PrimeEstimatedWeight != nil) && mtoShipment.Status == models.MTOShipmentStatusApproved { + mtoShipment, err = AddPricingEstimatesToMTOServiceItems(appCtx, *s, mtoShipment, shipment) + if err != nil { + return err + } + } + + if mtoShipment.MTOServiceItems != nil { + _, mtoErr := appCtx.DB().ValidateAndUpdate(&mtoShipment.MTOServiceItems) + if mtoErr != nil { + return mtoErr + } + } + isBoatShipment := shipment.ShipmentType == models.MTOShipmentTypeBoatHaulAway || shipment.ShipmentType == models.MTOShipmentTypeBoatTowAway if shipment.ShipmentType == models.MTOShipmentTypePPM { @@ -122,3 +139,30 @@ func (s *shipmentUpdater) UpdateShipment(appCtx appcontext.AppContext, shipment return mtoShipment, nil } + +func AddPricingEstimatesToMTOServiceItems(appCtx appcontext.AppContext, shipmentUpdater shipmentUpdater, mtoShipment *models.MTOShipment, shipmentDelta *models.MTOShipment) (*models.MTOShipment, error) { + mtoShipmentCopy := mtoShipment + + for index, serviceItem := range mtoShipmentCopy.MTOServiceItems { + var estimatedWeightToUse unit.Pound + if shipmentDelta.PrimeEstimatedWeight != nil { + estimatedWeightToUse = *shipmentDelta.PrimeEstimatedWeight + } else { + estimatedWeightToUse = *mtoShipmentCopy.PrimeEstimatedWeight + } + + serviceItemEstimatedPrice, err := shipmentUpdater.mtoServiceItemCreator.FindEstimatedPrice(appCtx, &serviceItem, *mtoShipment) + + // store actual captured weight + mtoShipmentCopy.MTOServiceItems[index].EstimatedWeight = &estimatedWeightToUse + mtoShipmentCopy.PrimeEstimatedWeight = &estimatedWeightToUse + + if serviceItemEstimatedPrice != 0 && err == nil { + mtoShipmentCopy.MTOServiceItems[index].PricingEstimate = &serviceItemEstimatedPrice + } + if err != nil { + return mtoShipmentCopy, err + } + } + return mtoShipmentCopy, nil +} diff --git a/pkg/services/orchestrators/shipment/shipment_updater_test.go b/pkg/services/orchestrators/shipment/shipment_updater_test.go index 3e46c05d001..3b9fec24cac 100644 --- a/pkg/services/orchestrators/shipment/shipment_updater_test.go +++ b/pkg/services/orchestrators/shipment/shipment_updater_test.go @@ -2,6 +2,7 @@ package shipment import ( "fmt" + "time" "github.com/gofrs/uuid" "github.com/stretchr/testify/mock" @@ -11,8 +12,14 @@ import ( "github.com/transcom/mymove/pkg/etag" "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" + routemocks "github.com/transcom/mymove/pkg/route/mocks" "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/services/ghcrateengine" "github.com/transcom/mymove/pkg/services/mocks" + moveservices "github.com/transcom/mymove/pkg/services/move" + mtoserviceitem "github.com/transcom/mymove/pkg/services/mto_service_item" + "github.com/transcom/mymove/pkg/services/query" + "github.com/transcom/mymove/pkg/testdatagen" "github.com/transcom/mymove/pkg/unit" ) @@ -28,6 +35,7 @@ func (suite *ShipmentSuite) TestUpdateShipment() { type subtestDataObjects struct { mockMTOShipmentUpdater *mocks.MTOShipmentUpdater + mockMtoServiceItemCreator *mocks.MTOServiceItemCreator mockPPMShipmentUpdater *mocks.PPMShipmentUpdater mockBoatShipmentUpdater *mocks.BoatShipmentUpdater mockMobileHomeShipmentUpdater *mocks.MobileHomeShipmentUpdater @@ -36,10 +44,18 @@ func (suite *ShipmentSuite) TestUpdateShipment() { fakeError error } + planner := &routemocks.Planner{} + moveRouter := moveservices.NewMoveRouter() + builder := query.NewQueryBuilder() + mtoServiceItemCreator := mtoserviceitem.NewMTOServiceItemCreator(planner, builder, moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) + makeSubtestData := func(returnErrorForMTOShipment bool, returnErrorForPPMShipment bool, returnErrorForBoatShipment bool, returnErrorForMobileHomeShipment bool) (subtestData subtestDataObjects) { mockMTOShipmentUpdater := mocks.MTOShipmentUpdater{} subtestData.mockMTOShipmentUpdater = &mockMTOShipmentUpdater + mockMtoServiceItemCreator := mocks.MTOServiceItemCreator{} + subtestData.mockMtoServiceItemCreator = &mockMtoServiceItemCreator + mockPPMShipmentUpdater := mocks.PPMShipmentUpdater{} subtestData.mockPPMShipmentUpdater = &mockPPMShipmentUpdater @@ -49,9 +65,8 @@ func (suite *ShipmentSuite) TestUpdateShipment() { mockMobileHomeShipmentUpdater := mocks.MobileHomeShipmentUpdater{} subtestData.mockMobileHomeShipmentUpdater = &mockMobileHomeShipmentUpdater - subtestData.shipmentUpdaterOrchestrator = NewShipmentUpdater(subtestData.mockMTOShipmentUpdater, subtestData.mockPPMShipmentUpdater, subtestData.mockBoatShipmentUpdater, subtestData.mockMobileHomeShipmentUpdater) - - subtestData.shipmentUpdaterOrchestrator = NewShipmentUpdater(subtestData.mockMTOShipmentUpdater, subtestData.mockPPMShipmentUpdater, subtestData.mockBoatShipmentUpdater, subtestData.mockMobileHomeShipmentUpdater) + subtestData.shipmentUpdaterOrchestrator = NewShipmentUpdater(subtestData.mockMTOShipmentUpdater, subtestData.mockPPMShipmentUpdater, subtestData.mockBoatShipmentUpdater, subtestData.mockMobileHomeShipmentUpdater, mtoServiceItemCreator) + subtestData.shipmentUpdaterOrchestrator = NewShipmentUpdater(subtestData.mockMTOShipmentUpdater, subtestData.mockPPMShipmentUpdater, subtestData.mockBoatShipmentUpdater, subtestData.mockMobileHomeShipmentUpdater, mtoServiceItemCreator) if returnErrorForMTOShipment { subtestData.fakeError = apperror.NewInvalidInputError(uuid.Nil, nil, nil, "Pickup date missing") @@ -177,6 +192,26 @@ func (suite *ShipmentSuite) TestUpdateShipment() { return subtestData } + makeServiceItemSubtestData := func() (subtestData subtestDataObjects) { + mockMTOShipmentUpdater := mocks.MTOShipmentUpdater{} + subtestData.mockMTOShipmentUpdater = &mockMTOShipmentUpdater + + mockMtoServiceItemCreator := mocks.MTOServiceItemCreator{} + subtestData.mockMtoServiceItemCreator = &mockMtoServiceItemCreator + + mockPPMShipmentUpdater := mocks.PPMShipmentUpdater{} + subtestData.mockPPMShipmentUpdater = &mockPPMShipmentUpdater + + mockBoatShipmentUpdater := mocks.BoatShipmentUpdater{} + subtestData.mockBoatShipmentUpdater = &mockBoatShipmentUpdater + + mockMobileHomeShipmentUpdater := mocks.MobileHomeShipmentUpdater{} + subtestData.mockMobileHomeShipmentUpdater = &mockMobileHomeShipmentUpdater + + subtestData.shipmentUpdaterOrchestrator = NewShipmentUpdater(subtestData.mockMTOShipmentUpdater, subtestData.mockPPMShipmentUpdater, subtestData.mockBoatShipmentUpdater, subtestData.mockMobileHomeShipmentUpdater, subtestData.mockMtoServiceItemCreator) + + return subtestData + } suite.Run("Returns an InvalidInputError if there is an error with the shipment info that was input", func() { appCtx := suite.AppContextForTest() @@ -465,4 +500,133 @@ func (suite *ShipmentSuite) TestUpdateShipment() { mock.AnythingOfType("uuid.UUID"), ) }) + + suite.Run("Updating weight will update the estimated price of service items", func() { + appCtx := suite.AppContextForTest() + + subtestData := makeServiceItemSubtestData() + + estimatedWeight := unit.Pound(2000) + pickupAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress2}) + deliveryAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress3}) + + shipment := factory.BuildMTOShipment(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + ID: uuid.Must(uuid.FromString("a5e95c1d-97c3-4f79-8097-c12dd2557ac7")), + Status: models.MTOShipmentStatusApproved, + ShipmentType: models.MTOShipmentTypeHHG, + PrimeEstimatedWeight: &estimatedWeight, + }, + }, + { + Model: pickupAddress, + LinkOnly: true, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: deliveryAddress, + LinkOnly: true, + Type: &factory.Addresses.DeliveryAddress, + }, + }, nil) + + reServiceCodeFSC := factory.FetchReServiceByCode(suite.DB(), models.ReServiceCodeFSC) + + startDate := time.Now().AddDate(-1, 0, 0) + endDate := startDate.AddDate(1, 1, 1) + reason := "lorem ipsum" + + testdatagen.FetchOrMakeReContract(suite.DB(), testdatagen.Assertions{}) + testdatagen.FetchOrMakeReContractYear(suite.DB(), + testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + Name: "Test Contract Year", + EscalationCompounded: 1.125, + StartDate: startDate, + EndDate: endDate, + }, + }) + + testdatagen.FetchOrMakeGHCDieselFuelPrice(suite.DB(), testdatagen.Assertions{ + GHCDieselFuelPrice: models.GHCDieselFuelPrice{ + FuelPriceInMillicents: unit.Millicents(281400), + PublicationDate: time.Date(2020, time.March, 9, 0, 0, 0, 0, time.UTC), + EffectiveDate: time.Date(2020, time.March, 10, 0, 0, 0, 0, time.UTC), + EndDate: time.Date(2025, time.March, 17, 0, 0, 0, 0, time.UTC), + }, + }) + + actualPickupAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress2}) + requestedPickupDate := time.Date(2020, time.October, 24, 0, 0, 0, 0, time.UTC) + + serviceItemFSC := models.MTOServiceItem{ + MoveTaskOrder: shipment.MoveTaskOrder, + MoveTaskOrderID: shipment.MoveTaskOrderID, + MTOShipment: shipment, + MTOShipmentID: &shipment.ID, + ReService: reServiceCodeFSC, + Reason: &reason, + SITOriginHHGActualAddress: &actualPickupAddress, + Status: models.MTOServiceItemStatusSubmitted, + } + + shipment.MTOServiceItems = append(shipment.MTOServiceItems, serviceItemFSC) + suite.MustSave(&shipment) + + eTag := etag.GenerateEtag(shipment.UpdatedAt) + + subtestData.mockMtoServiceItemCreator.On("ZipTransitDistance", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + false, + false, + ).Return(800, nil) + + returnCents := unit.Cents(123) + + subtestData.mockMtoServiceItemCreator.On("FindEstimatedPrice", + mock.AnythingOfType("*appcontext.appContext"), + mock.Anything, + mock.Anything, + mock.Anything, + ).Return(returnCents, nil) + + subtestData.mockMTOShipmentUpdater. + On( + updateMTOShipmentMethodName, + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("*models.MTOShipment"), + mock.AnythingOfType("string"), + mock.AnythingOfType("string")). + Return( + &models.MTOShipment{ + ID: uuid.Must(uuid.FromString("a5e95c1d-97c3-4f79-8097-c12dd2557ac7")), + Status: models.MTOShipmentStatusApproved, + ShipmentType: models.MTOShipmentTypeHHG, + PrimeEstimatedWeight: &estimatedWeight, + RequestedPickupDate: &requestedPickupDate, + MTOServiceItems: models.MTOServiceItems{serviceItemFSC}, + PickupAddress: &pickupAddress, + DestinationAddress: &deliveryAddress, + }, nil) + + // Need to start a transaction so we can assert the call with the correct appCtx + err := appCtx.NewTransaction(func(txAppCtx appcontext.AppContext) error { + mtoShipment, err := subtestData.shipmentUpdaterOrchestrator.UpdateShipment(txAppCtx, &shipment, eTag, "test") + + suite.NoError(err) + suite.NotNil(mtoShipment) + + expectedPrice := unit.Cents(123) + expectedWeight := unit.Pound(2000) + suite.Equal(expectedWeight, *mtoShipment.MTOServiceItems[0].EstimatedWeight) + suite.Equal(expectedPrice, *mtoShipment.MTOServiceItems[0].PricingEstimate) + + return nil + }) + + suite.NoError(err) // just making golangci-lint happy + }) } diff --git a/pkg/services/order.go b/pkg/services/order.go index c1bd25d1b84..217f3e069f2 100644 --- a/pkg/services/order.go +++ b/pkg/services/order.go @@ -20,6 +20,7 @@ import ( type OrderFetcher interface { FetchOrder(appCtx appcontext.AppContext, orderID uuid.UUID) (*models.Order, error) ListOrders(appCtx appcontext.AppContext, officeUserID uuid.UUID, role roles.RoleType, params *ListOrderParams) ([]models.Move, int, error) + ListDestinationRequestsOrders(appCtx appcontext.AppContext, officeUserID uuid.UUID, role roles.RoleType, params *ListOrderParams) ([]models.Move, int, error) ListAllOrderLocations(appCtx appcontext.AppContext, officeUserID uuid.UUID, params *ListOrderParams) ([]models.Move, error) } diff --git a/pkg/services/order/order_fetcher.go b/pkg/services/order/order_fetcher.go index 923a4dfe82b..5761af41a01 100644 --- a/pkg/services/order/order_fetcher.go +++ b/pkg/services/order/order_fetcher.go @@ -2,6 +2,7 @@ package order import ( "database/sql" + "encoding/json" "fmt" "regexp" "strings" @@ -9,6 +10,8 @@ import ( "github.com/gobuffalo/pop/v6" "github.com/gofrs/uuid" + "github.com/jinzhu/copier" + "github.com/lib/pq" "go.uber.org/zap" "github.com/transcom/mymove/pkg/appcontext" @@ -122,8 +125,9 @@ func (f orderFetcher) ListOrders(appCtx appcontext.AppContext, officeUserID uuid tooAssignedUserQuery := tooAssignedUserFilter(params.TOOAssignedUser) sortOrderQuery := sortOrder(params.Sort, params.Order, ppmCloseoutGblocs) counselingQuery := counselingOfficeFilter(params.CounselingOffice) + tooDestinationRequestsQuery := tooQueueOriginRequestsFilter(role) // Adding to an array so we can iterate over them and apply the filters after the query structure is set below - options := [20]QueryOption{branchQuery, locatorQuery, dodIDQuery, emplidQuery, customerNameQuery, originDutyLocationQuery, destinationDutyLocationQuery, moveStatusQuery, gblocQuery, submittedAtQuery, appearedInTOOAtQuery, requestedMoveDateQuery, ppmTypeQuery, closeoutInitiatedQuery, closeoutLocationQuery, ppmStatusQuery, sortOrderQuery, scAssignedUserQuery, tooAssignedUserQuery, counselingQuery} + options := [21]QueryOption{branchQuery, locatorQuery, dodIDQuery, emplidQuery, customerNameQuery, originDutyLocationQuery, destinationDutyLocationQuery, moveStatusQuery, gblocQuery, submittedAtQuery, appearedInTOOAtQuery, requestedMoveDateQuery, ppmTypeQuery, closeoutInitiatedQuery, closeoutLocationQuery, ppmStatusQuery, sortOrderQuery, scAssignedUserQuery, tooAssignedUserQuery, counselingQuery, tooDestinationRequestsQuery} var query *pop.Query if ppmCloseoutGblocs { @@ -307,7 +311,118 @@ func (f orderFetcher) ListOrders(appCtx appcontext.AppContext, officeUserID uuid return moves, count, nil } -// TODO: Update query to select distinct duty locations +// this is a custom/temporary struct used in the below service object to get destination queue moves +type MoveWithCount struct { + models.Move + OrdersRaw json.RawMessage `json:"orders" db:"orders"` + Orders *models.Order `json:"-"` + MTOShipmentsRaw json.RawMessage `json:"mto_shipments" db:"mto_shipments"` + MTOShipments *models.MTOShipments `json:"-"` + CounselingOfficeRaw json.RawMessage `json:"counseling_transportation_office" db:"counseling_transportation_office"` + CounselingOffice *models.TransportationOffice `json:"-"` + TOOAssignedRaw json.RawMessage `json:"too_assigned" db:"too_assigned"` + TOOAssignedUser *models.OfficeUser `json:"-"` + TotalCount int64 `json:"total_count" db:"total_count"` +} + +type JSONB []byte + +func (j *JSONB) UnmarshalJSON(data []byte) error { + *j = data + return nil +} + +func (f orderFetcher) ListDestinationRequestsOrders(appCtx appcontext.AppContext, officeUserID uuid.UUID, role roles.RoleType, params *services.ListOrderParams) ([]models.Move, int, error) { + var moves []models.Move + var movesWithCount []MoveWithCount + + // getting the office user's GBLOC + gblocFetcher := officeuser.NewOfficeUserGblocFetcher() + officeUserGbloc, gblocErr := gblocFetcher.FetchGblocForOfficeUser(appCtx, officeUserID) + if gblocErr != nil { + return []models.Move{}, 0, gblocErr + } + + // calling the database function with all passed in parameters + err := appCtx.DB().RawQuery("SELECT * FROM get_destination_queue($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)", + officeUserGbloc, + params.CustomerName, + params.Edipi, + params.Emplid, + pq.Array(params.Status), + params.Locator, + params.RequestedMoveDate, + params.AppearedInTOOAt, + params.Branch, + strings.Join(params.OriginDutyLocation, " "), + params.CounselingOffice, + params.TOOAssignedUser, + params.Page, + params.PerPage, + params.Sort, + params.Order). + All(&movesWithCount) + + if err != nil { + return []models.Move{}, 0, err + } + + // each row is sent back with the total count from the db func, so we will take the value from the first one + var count int64 + if len(movesWithCount) > 0 { + count = movesWithCount[0].TotalCount + } else { + count = 0 + } + + // we have to manually loop through each move and populate the nested objects that the queue uses/needs + for i := range movesWithCount { + // populating Move.Orders struct + var order models.Order + if err := json.Unmarshal(movesWithCount[i].OrdersRaw, &order); err != nil { + return nil, 0, fmt.Errorf("error unmarshaling orders JSON: %w", err) + } + movesWithCount[i].OrdersRaw = nil + movesWithCount[i].Orders = &order + + // populating Move.MTOShipments array + var shipments models.MTOShipments + if err := json.Unmarshal(movesWithCount[i].MTOShipmentsRaw, &shipments); err != nil { + return nil, 0, fmt.Errorf("error unmarshaling shipments JSON: %w", err) + } + movesWithCount[i].MTOShipmentsRaw = nil + movesWithCount[i].MTOShipments = &shipments + + // populating Moves.CounselingOffice struct + var counselingTransportationOffice models.TransportationOffice + if err := json.Unmarshal(movesWithCount[i].CounselingOfficeRaw, &counselingTransportationOffice); err != nil { + return nil, 0, fmt.Errorf("error unmarshaling counseling_transportation_office JSON: %w", err) + } + movesWithCount[i].CounselingOfficeRaw = nil + movesWithCount[i].CounselingOffice = &counselingTransportationOffice + + // populating Moves.TOOAssigned struct + var tooAssigned models.OfficeUser + if err := json.Unmarshal(movesWithCount[i].TOOAssignedRaw, &tooAssigned); err != nil { + return nil, 0, fmt.Errorf("error unmarshaling too_assigned JSON: %w", err) + } + movesWithCount[i].TOOAssignedRaw = nil + movesWithCount[i].TOOAssignedUser = &tooAssigned + } + + // the handler consumes a Move object and NOT the MoveWithCount struct used in this func + // so we have to copy our custom struct into the Move struct + for _, moveWithCount := range movesWithCount { + var move models.Move + if err := copier.Copy(&move, &moveWithCount); err != nil { + return nil, 0, fmt.Errorf("error copying movesWithCount into Moves struct: %w", err) + } + moves = append(moves, move) + } + + return moves, int(count), nil +} + func (f orderFetcher) ListAllOrderLocations(appCtx appcontext.AppContext, officeUserID uuid.UUID, params *services.ListOrderParams) ([]models.Move, error) { var moves []models.Move var err error @@ -781,3 +896,95 @@ func sortOrder(sort *string, order *string, ppmCloseoutGblocs bool) QueryOption } } } + +// We want to filter out any moves that have ONLY destination type requests to them, such as destination SIT, shuttle, out of the +// task order queue. If the moves have origin SIT, excess weight risks, or sit extensions with origin SIT service items, they +// should still appear in the task order queue, which is what this query looks for +func tooQueueOriginRequestsFilter(role roles.RoleType) QueryOption { + return func(query *pop.Query) { + if role == roles.RoleTypeTOO { + baseQuery := ` + -- check for moves with destination requests and NOT origin requests, then return the inverse for the TOO queue with the NOT wrapped around the query + NOT ( + -- check for moves with destination requests + ( + -- moves with submitted destination SIT or shuttle submitted service items + EXISTS ( + SELECT 1 + FROM mto_service_items msi + JOIN re_services rs ON msi.re_service_id = rs.id + WHERE msi.mto_shipment_id = mto_shipments.id + AND msi.status = 'SUBMITTED' + AND rs.code IN ('DDFSIT', 'DDASIT', 'DDDSIT', 'DDSHUT', 'DDSFSC', + 'IDFSIT', 'IDASIT', 'IDDSIT', 'IDSHUT', 'IDSFSC') + ) + -- requested shipment address update + OR EXISTS ( + SELECT 1 + FROM shipment_address_updates sau + WHERE sau.shipment_id = mto_shipments.id + AND sau.status = 'REQUESTED' + ) + -- Moves with SIT extensions and ONLY destination SIT service items we filter out of TOO queue + OR ( + EXISTS ( + SELECT 1 + FROM sit_extensions se + JOIN mto_service_items msi ON se.mto_shipment_id = msi.mto_shipment_id + JOIN re_services rs ON msi.re_service_id = rs.id + WHERE se.mto_shipment_id = mto_shipments.id + AND se.status = 'PENDING' + AND rs.code IN ('DDFSIT', 'DDASIT', 'DDDSIT', 'DDSHUT', 'DDSFSC', + 'IDFSIT', 'IDASIT', 'IDDSIT', 'IDSHUT', 'IDSFSC') + ) + -- make sure there are NO origin SIT service items (otherwise, it should be in both queues) + AND NOT EXISTS ( + SELECT 1 + FROM mto_service_items msi + JOIN re_services rs ON msi.re_service_id = rs.id + WHERE msi.mto_shipment_id = mto_shipments.id + AND msi.status = 'SUBMITTED' + AND rs.code IN ('ICRT', 'IUBPK', 'IOFSIT', 'IOASIT', 'IOPSIT', 'IOSHUT', + 'IHUPK', 'IUCRT', 'DCRT', 'MS', 'CS', 'DOFSIT', 'DOASIT', + 'DOPSIT', 'DOSFSC', 'IOSFSC', 'DUPK', 'DUCRT', 'DOSHUT', + 'FSC', 'DMHF', 'DBTF', 'DBHF', 'IBTF', 'IBHF', 'DCRTSA', + 'DLH', 'DOP', 'DPK', 'DSH', 'DNPK', 'INPK', 'UBP', + 'ISLH', 'POEFSC', 'PODFSC', 'IHPK') + ) + ) + ) + -- check for moves with origin requests or conditions where move should appear in TOO queue + AND NOT ( + -- keep moves in the TOO queue with origin submitted service items + EXISTS ( + SELECT 1 + FROM mto_service_items msi + JOIN re_services rs ON msi.re_service_id = rs.id + WHERE msi.mto_shipment_id = mto_shipments.id + AND msi.status = 'SUBMITTED' + AND rs.code IN ('ICRT', 'IUBPK', 'IOFSIT', 'IOASIT', 'IOPSIT', 'IOSHUT', + 'IHUPK', 'IUCRT', 'DCRT', 'MS', 'CS', 'DOFSIT', 'DOASIT', + 'DOPSIT', 'DOSFSC', 'IOSFSC', 'DUPK', 'DUCRT', 'DOSHUT', + 'FSC', 'DMHF', 'DBTF', 'DBHF', 'IBTF', 'IBHF', 'DCRTSA', + 'DLH', 'DOP', 'DPK', 'DSH', 'DNPK', 'INPK', 'UBP', + 'ISLH', 'POEFSC', 'PODFSC', 'IHPK') + ) + -- keep moves in the TOO queue if they have an unacknowledged excess weight risk + OR ( + ( + moves.excess_weight_qualified_at IS NOT NULL + AND moves.excess_weight_acknowledged_at IS NULL + ) + OR ( + moves.excess_unaccompanied_baggage_weight_qualified_at IS NOT NULL + AND moves.excess_unaccompanied_baggage_weight_acknowledged_at IS NULL + ) + ) + ) + ) + + ` + query.Where(baseQuery) + } + } +} diff --git a/pkg/services/order/order_fetcher_test.go b/pkg/services/order/order_fetcher_test.go index 57e85401246..2629fa5f0a5 100644 --- a/pkg/services/order/order_fetcher_test.go +++ b/pkg/services/order/order_fetcher_test.go @@ -5,6 +5,7 @@ import ( "time" "github.com/gofrs/uuid" + "github.com/stretchr/testify/mock" "github.com/transcom/mymove/pkg/auth" "github.com/transcom/mymove/pkg/factory" @@ -12,6 +13,7 @@ import ( "github.com/transcom/mymove/pkg/models/roles" "github.com/transcom/mymove/pkg/services" "github.com/transcom/mymove/pkg/services/entitlements" + "github.com/transcom/mymove/pkg/services/mocks" moveservice "github.com/transcom/mymove/pkg/services/move" officeuserservice "github.com/transcom/mymove/pkg/services/office_user" "github.com/transcom/mymove/pkg/testdatagen" @@ -580,74 +582,59 @@ func (suite *OrderServiceSuite) TestListOrders() { suite.Equal(1, len(moves)) suite.Equal(createdPPM.Shipment.MoveTaskOrder.Locator, moves[0].Locator) }) -} -func (suite *OrderServiceSuite) TestListOrderWithAssignedUserSingle() { - // Under test: ListOrders - // Set up: Make a move, assign one to an SC office user - // Expected outcome: Only the one move with the assigned user should be returned - assignedOfficeUserUpdater := moveservice.NewAssignedOfficeUserUpdater(moveservice.NewMoveFetcher()) - scUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeServicesCounselor}) - var orderFetcherTest orderFetcher - session := auth.Session{ - ApplicationName: auth.OfficeApp, - Roles: scUser.User.Roles, - OfficeUserID: scUser.ID, - IDToken: "fake_token", - AccessToken: "fakeAccessToken", - } - - appCtx := suite.AppContextWithSessionForTest(&session) - - createdMove := factory.BuildMoveWithShipment(suite.DB(), nil, nil) - createdMove.SCAssignedID = &scUser.ID - createdMove.SCAssignedUser = &scUser - _, updateError := assignedOfficeUserUpdater.UpdateAssignedOfficeUser(appCtx, createdMove.ID, &scUser, roles.RoleTypeServicesCounselor) - - moves, _, err := orderFetcherTest.ListOrders(suite.AppContextWithSessionForTest(&session), scUser.ID, roles.RoleTypeServicesCounselor, &services.ListOrderParams{ - SCAssignedUser: &scUser.LastName, - }) - suite.FatalNoError(err) - suite.FatalNoError(updateError) - suite.Equal(1, len(moves)) - suite.Equal(moves[0].SCAssignedID, createdMove.SCAssignedID) - suite.Equal(createdMove.SCAssignedUser.ID, moves[0].SCAssignedUser.ID) - suite.Equal(createdMove.SCAssignedUser.FirstName, moves[0].SCAssignedUser.FirstName) - suite.Equal(createdMove.SCAssignedUser.LastName, moves[0].SCAssignedUser.LastName) -} -func (suite *OrderServiceSuite) TestListOrdersUSMCGBLOC() { - waf := entitlements.NewWeightAllotmentFetcher() - orderFetcher := NewOrderFetcher(waf) + suite.Run("task order queue does not return move with ONLY a destination address update request", func() { + officeUser, _, session := setupTestData() + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), + }, + }}, nil) - suite.Run("returns USMC order for USMC office user", func() { - marines := models.AffiliationMARINES - // It doesn't matter what the Origin GBLOC is for the move. Only the Marines - // affiliation matters for office users who are tied to the USMC GBLOC. - factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + testUUID := uuid.UUID{} + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ { - Model: models.ServiceMember{ - Affiliation: &marines, + Model: move, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + DestinationAddressID: &testUUID, }, }, }, nil) - // Create move where service member has the default ARMY affiliation - factory.BuildMoveWithShipment(suite.DB(), nil, nil) - tioRole := roles.Role{RoleType: roles.RoleTypeTIO} - tooRole := roles.Role{RoleType: roles.RoleTypeTOO} - officeUserOooRah := factory.BuildOfficeUser(suite.DB(), []factory.Customization{ + suite.NotNil(shipment) + + shipmentAddressUpdate := factory.BuildShipmentAddressUpdate(suite.DB(), []factory.Customization{ { - Model: models.TransportationOffice{ - Gbloc: "USMC", - }, + Model: shipment, + LinkOnly: true, }, { - Model: models.User{ - Roles: []roles.Role{tioRole, tooRole}, + Model: move, + LinkOnly: true, + }, + { + Model: models.ShipmentAddressUpdate{ + NewAddressID: testUUID, }, }, - }, nil) - // Create office user tied to the default KKFA GBLOC + }, []factory.Trait{factory.GetTraitShipmentAddressUpdateRequested}) + suite.NotNil(shipmentAddressUpdate) + + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) + + suite.FatalNoError(err) + // even though 2 moves were created, one by setupTestData(), only one will be returned from the call to List Orders since we filter out + // the one with only a shipment address update to be routed to the destination requests queue + suite.Equal(1, moveCount) + suite.Equal(1, len(moves)) + }) + + suite.Run("task order queue returns a move with origin service items and destination address update request", func() { officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) session := auth.Session{ ApplicationName: auth.OfficeApp, @@ -657,340 +644,430 @@ func (suite *OrderServiceSuite) TestListOrdersUSMCGBLOC() { AccessToken: "fakeAccessToken", } - params := services.ListOrderParams{PerPage: models.Int64Pointer(2), Page: models.Int64Pointer(1)} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUserOooRah.ID, roles.RoleTypeServicesCounselor, ¶ms) - - suite.FatalNoError(err) - suite.Equal(1, len(moves)) - suite.Equal(models.AffiliationMARINES, *moves[0].Orders.ServiceMember.Affiliation) - - params = services.ListOrderParams{PerPage: models.Int64Pointer(2), Page: models.Int64Pointer(1)} - moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeServicesCounselor, ¶ms) - - suite.FatalNoError(err) - suite.Equal(1, len(moves)) - suite.Equal(models.AffiliationARMY, *moves[0].Orders.ServiceMember.Affiliation) - }) -} + // build a move with only destination shuttle service item + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), + }, + }}, nil) -func getMoveNeedsServiceCounseling(suite *OrderServiceSuite, showMove bool, affiliation models.ServiceMemberAffiliation) models.Move { - nonCloseoutMove := factory.BuildMove(suite.DB(), []factory.Customization{ - { - Model: models.Move{ - Status: models.MoveStatusNeedsServiceCounseling, - Show: &showMove, - }, - }, - { - Model: models.ServiceMember{ - Affiliation: &affiliation, + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, }, - }, - }, nil) - - return nonCloseoutMove -} - -func getSubmittedMove(suite *OrderServiceSuite, showMove bool, affiliation models.ServiceMemberAffiliation) models.Move { - move := factory.BuildMove(suite.DB(), []factory.Customization{ - { - Model: models.Move{ - Status: models.MoveStatusSUBMITTED, - Show: &showMove, + }, nil) + suite.NotNil(shipment) + originSITServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, }, - }, - { - Model: models.ServiceMember{ - Affiliation: &affiliation, + { + Model: models.ReService{ + Code: models.ReServiceCodeDOASIT, + }, }, - }, - }, nil) - return move -} + }, nil) + suite.NotNil(originSITServiceItem) -func buildPPMShipmentNeedsCloseout(suite *OrderServiceSuite, move models.Move) models.PPMShipment { - ppm := factory.BuildMinimalPPMShipment(suite.DB(), []factory.Customization{ - { - Model: models.PPMShipment{ - Status: models.PPMShipmentStatusNeedsCloseout, + testUUID := uuid.UUID{} + shipmentAddressUpdate := factory.BuildShipmentAddressUpdate(suite.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, }, - }, - { - Model: move, - LinkOnly: true, - }, - }, nil) - return ppm -} - -func buildPPMShipmentDraft(suite *OrderServiceSuite, move models.Move) models.PPMShipment { - ppm := factory.BuildMinimalPPMShipment(suite.DB(), []factory.Customization{ - { - Model: models.PPMShipment{ - Status: models.PPMShipmentStatusDraft, + { + Model: move, + LinkOnly: true, }, - }, - { - Model: move, - LinkOnly: true, - }, - }, nil) - return ppm -} - -func buildPPMShipmentCloseoutComplete(suite *OrderServiceSuite, move models.Move) models.PPMShipment { - ppm := factory.BuildMinimalPPMShipment(suite.DB(), []factory.Customization{ - { - Model: models.PPMShipment{ - Status: models.PPMShipmentStatusCloseoutComplete, + { + Model: models.ShipmentAddressUpdate{ + NewAddressID: testUUID, + }, }, - }, - { - Model: move, - LinkOnly: true, - }, - }, nil) - return ppm -} -func (suite *OrderServiceSuite) TestListOrdersPPMCloseoutForArmyAirforce() { - waf := entitlements.NewWeightAllotmentFetcher() - orderFetcher := NewOrderFetcher(waf) + }, []factory.Trait{factory.GetTraitShipmentAddressUpdateRequested}) + suite.NotNil(shipmentAddressUpdate) - var session auth.Session + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) - suite.Run("office user in normal GBLOC should only see non-Navy/Marines/CoastGuard moves that need closeout in closeout tab", func() { - officeUserSC := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeServicesCounselor}) + suite.FatalNoError(err) + suite.Equal(1, moveCount) + suite.Equal(1, len(moves)) + }) - session = auth.Session{ + suite.Run("task order queue does not return move with ONLY requested destination SIT service items", func() { + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + session := auth.Session{ ApplicationName: auth.OfficeApp, - Roles: officeUserSC.User.Roles, - OfficeUserID: officeUserSC.ID, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, IDToken: "fake_token", AccessToken: "fakeAccessToken", } - move := getMoveNeedsServiceCounseling(suite, true, models.AffiliationARMY) - buildPPMShipmentNeedsCloseout(suite, move) + // build a move with only origin service items + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), + }, + }}, nil) - afMove := getMoveNeedsServiceCounseling(suite, true, models.AffiliationAIRFORCE) - buildPPMShipmentDraft(suite, afMove) + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + }, nil) + suite.NotNil(shipment) + originSITServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeDOFSIT, + }, + }, + }, nil) + suite.NotNil(originSITServiceItem) - cgMove := getMoveNeedsServiceCounseling(suite, true, models.AffiliationCOASTGUARD) - buildPPMShipmentNeedsCloseout(suite, cgMove) + // build a move with destination service items + move2 := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), + }, + }}, nil) + shipment2 := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move2, + LinkOnly: true, + }, + }, nil) - params := services.ListOrderParams{PerPage: models.Int64Pointer(9), Page: models.Int64Pointer(1), NeedsPPMCloseout: models.BoolPointer(true), Status: []string{string(models.MoveStatusNeedsServiceCounseling)}} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUserSC.ID, roles.RoleTypeServicesCounselor, ¶ms) + destinationSITServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: shipment2, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeDDFSIT, + }, + }, + }, nil) + + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) + + suite.Equal(models.MTOServiceItemStatusSubmitted, destinationSITServiceItem.Status) suite.FatalNoError(err) + // even though 2 moves were created, only one will be returned from the call to List Orders since we filter out + // the one with only destination service items + suite.Equal(1, moveCount) suite.Equal(1, len(moves)) - suite.Equal(move.Locator, moves[0].Locator) }) - suite.Run("office user in normal GBLOC should not see moves that require closeout in counseling tab", func() { - officeUserSC := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeServicesCounselor}) - - session = auth.Session{ + suite.Run("task order queue returns a move with origin requested SIT service items", func() { + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + session := auth.Session{ ApplicationName: auth.OfficeApp, - Roles: officeUserSC.User.Roles, - OfficeUserID: officeUserSC.ID, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, IDToken: "fake_token", AccessToken: "fakeAccessToken", } - closeoutMove := getMoveNeedsServiceCounseling(suite, true, models.AffiliationARMY) - buildPPMShipmentCloseoutComplete(suite, closeoutMove) + // build a move with only origin service items + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), + }, + }}, nil) - // PPM moves that are not in one of the closeout statuses - nonCloseoutMove := getMoveNeedsServiceCounseling(suite, true, models.AffiliationAIRFORCE) - buildPPMShipmentDraft(suite, nonCloseoutMove) - - params := services.ListOrderParams{PerPage: models.Int64Pointer(9), Page: models.Int64Pointer(1), NeedsPPMCloseout: models.BoolPointer(false), Status: []string{string(models.MoveStatusNeedsServiceCounseling)}} + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + }, nil) + suite.NotNil(shipment) + originSITServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeDOFSIT, + }, + }, + }, nil) + suite.NotNil(originSITServiceItem) - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUserSC.ID, roles.RoleTypeServicesCounselor, ¶ms) + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) suite.FatalNoError(err) + suite.Equal(1, moveCount) suite.Equal(1, len(moves)) - suite.Equal(nonCloseoutMove.Locator, moves[0].Locator) }) -} - -func (suite *OrderServiceSuite) TestListOrdersPPMCloseoutForNavyCoastGuardAndMarines() { - waf := entitlements.NewWeightAllotmentFetcher() - orderFetcher := NewOrderFetcher(waf) - - suite.Run("returns Navy order for NAVY office user when there's a ppm shipment in closeout", func() { - // It doesn't matter what the Origin GBLOC is for the move. Only the navy - // affiliation matters for SC who are tied to the NAVY GBLOC. - move := getSubmittedMove(suite, true, models.AffiliationNAVY) - buildPPMShipmentNeedsCloseout(suite, move) - - cgMove := getSubmittedMove(suite, true, models.AffiliationCOASTGUARD) - buildPPMShipmentNeedsCloseout(suite, cgMove) - - officeUserSC := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ - { - Model: models.TransportationOffice{ - Gbloc: "NAVY", - }, - }, - }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + suite.Run("task order queue returns a move with both origin and destination requested SIT service items", func() { + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) session := auth.Session{ ApplicationName: auth.OfficeApp, - Roles: officeUserSC.User.Roles, - OfficeUserID: officeUserSC.ID, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, IDToken: "fake_token", AccessToken: "fakeAccessToken", } - params := services.ListOrderParams{PerPage: models.Int64Pointer(9), Page: models.Int64Pointer(1)} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUserSC.ID, roles.RoleTypeServicesCounselor, ¶ms) - - suite.FatalNoError(err) - suite.Equal(1, len(moves)) - suite.Equal(models.AffiliationNAVY, *moves[0].Orders.ServiceMember.Affiliation) - - }) - - suite.Run("returns TVCB order for TVCB office user when there's a ppm shipment in closeout", func() { - // It doesn't matter what the Origin GBLOC is for the move. Only the marines - // affiliation matters for SC who are tied to the TVCB GBLOC. - move := getSubmittedMove(suite, true, models.AffiliationMARINES) - buildPPMShipmentNeedsCloseout(suite, move) - - nonMarineMove := getSubmittedMove(suite, true, models.AffiliationARMY) - buildPPMShipmentNeedsCloseout(suite, nonMarineMove) + // build a move with only origin service items + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), + }, + }}, nil) - officeUserSC := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ { - Model: models.TransportationOffice{ - Gbloc: "TVCB", + Model: move, + LinkOnly: true, + }, + }, nil) + suite.NotNil(shipment) + originSITServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeDOFSIT, }, }, - }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + }, nil) + suite.NotNil(originSITServiceItem) - session := auth.Session{ - ApplicationName: auth.OfficeApp, - Roles: officeUserSC.User.Roles, - OfficeUserID: officeUserSC.ID, - IDToken: "fake_token", - AccessToken: "fakeAccessToken", - } + destinationSITServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeDDFSIT, + }, + }, + }, nil) - params := services.ListOrderParams{PerPage: models.Int64Pointer(2), Page: models.Int64Pointer(1)} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUserSC.ID, roles.RoleTypeServicesCounselor, ¶ms) + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) + suite.Equal(models.MTOServiceItemStatusSubmitted, destinationSITServiceItem.Status) suite.FatalNoError(err) + suite.Equal(1, moveCount) suite.Equal(1, len(moves)) - suite.Equal(models.AffiliationMARINES, *moves[0].Orders.ServiceMember.Affiliation) - }) - suite.Run("returns coast guard order for USCG office user when there's a ppm shipment in closeout and filters out non coast guard moves", func() { - // It doesn't matter what the Origin GBLOC is for the move. Only the coast guard - // affiliation matters for SC who are tied to the USCG GBLOC. - move := getSubmittedMove(suite, true, models.AffiliationCOASTGUARD) - buildPPMShipmentNeedsCloseout(suite, move) - - armyMove := getSubmittedMove(suite, true, models.AffiliationARMY) - buildPPMShipmentNeedsCloseout(suite, armyMove) - - officeUserSC := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ - { - Model: models.TransportationOffice{ - Gbloc: "USCG", - }, - }, - }, []roles.RoleType{roles.RoleTypeServicesCounselor}) - + suite.Run("task order queue returns a move with origin shuttle service item", func() { + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) session := auth.Session{ ApplicationName: auth.OfficeApp, - Roles: officeUserSC.User.Roles, - OfficeUserID: officeUserSC.ID, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, IDToken: "fake_token", AccessToken: "fakeAccessToken", } - params := services.ListOrderParams{PerPage: models.Int64Pointer(2), Page: models.Int64Pointer(1)} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUserSC.ID, roles.RoleTypeServicesCounselor, ¶ms) + // build a move with only origin shuttle service item + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), + }, + }}, nil) + + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + }, nil) + suite.NotNil(shipment) + internationalOriginShuttleServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeIOSHUT, + }, + }, + }, nil) + suite.NotNil(internationalOriginShuttleServiceItem) + + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) suite.FatalNoError(err) + suite.Equal(1, moveCount) suite.Equal(1, len(moves)) - suite.Equal(models.AffiliationCOASTGUARD, *moves[0].Orders.ServiceMember.Affiliation) }) - suite.Run("Filters out moves with PPM shipments not in the status of NeedsApproval", func() { + suite.Run("task order queue does not return a move with ONLY destination shuttle service item", func() { + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } - cgMoveInWrongStatus := getSubmittedMove(suite, true, models.AffiliationCOASTGUARD) - buildPPMShipmentCloseoutComplete(suite, cgMoveInWrongStatus) + // build a move with only destination shuttle service item + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), + }, + }}, nil) - officeUserSC := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ { - Model: models.TransportationOffice{ - Gbloc: "USCG", + Model: move, + LinkOnly: true, + }, + }, nil) + suite.NotNil(shipment) + domesticDestinationShuttleServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeDDSHUT, }, }, - }, []roles.RoleType{roles.RoleTypeServicesCounselor}) - var session auth.Session - params := services.ListOrderParams{PerPage: models.Int64Pointer(2), Page: models.Int64Pointer(1)} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUserSC.ID, roles.RoleTypeServicesCounselor, ¶ms) + }, nil) + suite.NotNil(domesticDestinationShuttleServiceItem) + + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) suite.FatalNoError(err) + suite.Equal(0, moveCount) suite.Equal(0, len(moves)) }) - suite.Run("Filters out moves with no PPM shipment", func() { + suite.Run("task order queue returns a move with both origin and destination shuttle service item", func() { + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } - moveWithHHG := getSubmittedMove(suite, true, models.AffiliationCOASTGUARD) - factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + // build a move with only destination shuttle service item + move := factory.BuildMove(suite.DB(), []factory.Customization{ { - Model: models.MTOShipment{ - ShipmentType: models.MTOShipmentTypeHHG, + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), }, + }}, nil) + + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, }, + }, nil) + suite.NotNil(shipment) + domesticOriginShuttleServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ { - Model: moveWithHHG, + Model: shipment, LinkOnly: true, }, + { + Model: models.ReService{ + Code: models.ReServiceCodeDOSHUT, + }, + }, }, nil) + suite.NotNil(domesticOriginShuttleServiceItem) - officeUserSC := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + internationalDestinationShuttleServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ { - Model: models.TransportationOffice{ - Gbloc: "USCG", + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeIDSHUT, }, }, - }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + }, nil) + suite.NotNil(internationalDestinationShuttleServiceItem) + + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) + + suite.FatalNoError(err) + suite.Equal(1, moveCount) + suite.Equal(1, len(moves)) + }) + suite.Run("task order queue returns a move with excess weight flagged for review", func() { + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) session := auth.Session{ ApplicationName: auth.OfficeApp, - Roles: officeUserSC.User.Roles, - OfficeUserID: officeUserSC.ID, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, IDToken: "fake_token", AccessToken: "fakeAccessToken", } - params := services.ListOrderParams{PerPage: models.Int64Pointer(2), Page: models.Int64Pointer(1)} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUserSC.ID, roles.RoleTypeServicesCounselor, ¶ms) - - suite.FatalNoError(err) - suite.Equal(0, len(moves)) - }) -} - -func (suite *OrderServiceSuite) TestListOrdersMarines() { - waf := entitlements.NewWeightAllotmentFetcher() - suite.Run("does not return moves where the service member affiliation is Marines for non-USMC office user", func() { - - orderFetcher := NewOrderFetcher(waf) - marines := models.AffiliationMARINES - factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + now := time.Now() + // build a move with a ExcessWeightQualifiedAt value + move := factory.BuildMove(suite.DB(), []factory.Customization{ { - Model: models.ServiceMember{ - Affiliation: &marines, + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), + ExcessWeightQualifiedAt: &now, }, + }}, nil) + + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, }, }, nil) + suite.NotNil(shipment) + + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) + + suite.FatalNoError(err) + suite.Equal(1, moveCount) + suite.Equal(1, len(moves)) + }) + + suite.Run("task order queue returns a move with unaccompanied baggage excess weight flagged for review", func() { officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) session := auth.Session{ ApplicationName: auth.OfficeApp, @@ -1000,159 +1077,97 @@ func (suite *OrderServiceSuite) TestListOrdersMarines() { AccessToken: "fakeAccessToken", } - params := services.ListOrderParams{PerPage: models.Int64Pointer(2), Page: models.Int64Pointer(1)} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - - suite.FatalNoError(err) - suite.Equal(0, len(moves)) - }) -} + now := time.Now() + // build a move with a ExcessUnaccompaniedBaggageWeightQualifiedAt value + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), + ExcessUnaccompaniedBaggageWeightQualifiedAt: &now, + }, + }}, nil) -func (suite *OrderServiceSuite) TestListOrdersWithEmptyFields() { - expectedOrder := factory.BuildOrder(suite.DB(), nil, nil) - waf := entitlements.NewWeightAllotmentFetcher() - expectedOrder.Entitlement = nil - expectedOrder.EntitlementID = nil - expectedOrder.Grade = nil - expectedOrder.OriginDutyLocation = nil - expectedOrder.OriginDutyLocationID = nil - suite.MustSave(&expectedOrder) - - move := factory.BuildMove(suite.DB(), []factory.Customization{ - { - Model: expectedOrder, - LinkOnly: true, - }, - }, nil) - // Only orders with shipments are returned, so we need to add a shipment - // to the move we just created - factory.BuildMTOShipment(suite.DB(), []factory.Customization{ - { - Model: move, - LinkOnly: true, - }, - { - Model: models.MTOShipment{ - Status: models.MTOShipmentStatusSubmitted, - }, - }, - }, nil) - // Add a second shipment to make sure we only return 1 order even if its - // move has more than one shipment - factory.BuildMTOShipment(suite.DB(), []factory.Customization{ - { - Model: move, - LinkOnly: true, - }, - { - Model: models.MTOShipment{ - Status: models.MTOShipmentStatusSubmitted, + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, }, - }, - }, nil) - - officeUser := factory.BuildOfficeUser(suite.DB(), nil, nil) - session := auth.Session{ - ApplicationName: auth.OfficeApp, - Roles: officeUser.User.Roles, - OfficeUserID: officeUser.ID, - IDToken: "fake_token", - AccessToken: "fakeAccessToken", - } - - orderFetcher := NewOrderFetcher(waf) - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{PerPage: models.Int64Pointer(1), Page: models.Int64Pointer(1)}) - - suite.FatalNoError(err) - suite.Nil(moves) - -} - -func (suite *OrderServiceSuite) TestListOrdersWithPagination() { - officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) - waf := entitlements.NewWeightAllotmentFetcher() - session := auth.Session{ - ApplicationName: auth.OfficeApp, - Roles: officeUser.User.Roles, - OfficeUserID: officeUser.ID, - IDToken: "fake_token", - AccessToken: "fakeAccessToken", - } - - for i := 0; i < 2; i++ { - factory.BuildMoveWithShipment(suite.DB(), nil, nil) - } - - orderFetcher := NewOrderFetcher(waf) - params := services.ListOrderParams{Page: models.Int64Pointer(1), PerPage: models.Int64Pointer(1)} - moves, count, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - - suite.NoError(err) - suite.Equal(1, len(moves)) - suite.Equal(2, count) - -} + }, nil) + suite.NotNil(shipment) -func (suite *OrderServiceSuite) TestListOrdersWithSortOrder() { + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) - // SET UP: Service Members for sorting by Service Member Last Name and Branch - // - We'll need two other service members to test the last name sort, Lea Spacemen and Leo Zephyer - serviceMemberFirstName := "Lea" - serviceMemberLastName := "Zephyer" - affiliation := models.AffiliationNAVY - edipi := "9999999999" - var officeUser models.OfficeUser - waf := entitlements.NewWeightAllotmentFetcher() - // SET UP: Dates for sorting by Requested Move Date - // - We want dates 2 and 3 to sandwich requestedMoveDate1 so we can test that the min() query is working - requestedMoveDate1 := time.Date(testdatagen.GHCTestYear, 02, 20, 0, 0, 0, 0, time.UTC) - requestedMoveDate2 := time.Date(testdatagen.GHCTestYear, 03, 03, 0, 0, 0, 0, time.UTC) - requestedMoveDate3 := time.Date(testdatagen.GHCTestYear, 01, 15, 0, 0, 0, 0, time.UTC) + suite.FatalNoError(err) + suite.Equal(1, moveCount) + suite.Equal(1, len(moves)) + }) - setupTestData := func() (models.Move, models.Move, auth.Session) { + suite.Run("task order queue returns a move with a pending SIT extension and origin service items to review", func() { + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } - // CREATE EXPECTED MOVES - expectedMove1 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ - { // Default New Duty Location name is Fort Eisenhower + // build a move with origin service items + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { Model: models.Move{ - Status: models.MoveStatusAPPROVED, - Locator: "AA1234", + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), }, - }, + }}, nil) + + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ { - Model: models.MTOShipment{ - RequestedPickupDate: &requestedMoveDate1, - }, + Model: move, + LinkOnly: true, }, }, nil) - expectedMove2 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + suite.NotNil(shipment) + originSITServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ { - Model: models.Move{ - Locator: "TTZ123", + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, }, }, { - Model: models.ServiceMember{ - Affiliation: &affiliation, - FirstName: &serviceMemberFirstName, - Edipi: &edipi, - }, + Model: shipment, + LinkOnly: true, }, { - Model: models.MTOShipment{ - RequestedPickupDate: &requestedMoveDate2, + Model: models.ReService{ + Code: models.ReServiceCodeDOFSIT, }, }, }, nil) - // Create a second shipment so we can test min() sort - factory.BuildMTOShipmentWithMove(&expectedMove2, suite.DB(), []factory.Customization{ + suite.NotNil(originSITServiceItem) + sitExtension := factory.BuildSITDurationUpdate(suite.DB(), []factory.Customization{ { - Model: models.MTOShipment{ - RequestedPickupDate: &requestedMoveDate3, + Model: shipment, + LinkOnly: true, + }, + { + Model: models.SITDurationUpdate{ + Status: models.SITExtensionStatusPending, }, }, }, nil) - officeUser = factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + suite.NotNil(sitExtension) + + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) + + suite.FatalNoError(err) + suite.Equal(1, moveCount) + suite.Equal(1, len(moves)) + }) + + suite.Run("task order queue does NOT return a move with a pending SIT extension and only destination service items to review", func() { + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) session := auth.Session{ ApplicationName: auth.OfficeApp, Roles: officeUser.User.Roles, @@ -1161,86 +1176,61 @@ func (suite *OrderServiceSuite) TestListOrdersWithSortOrder() { AccessToken: "fakeAccessToken", } - return expectedMove1, expectedMove2, session - } - - orderFetcher := NewOrderFetcher(waf) - - suite.Run("Sort by locator code", func() { - expectedMove1, expectedMove2, session := setupTestData() - params := services.ListOrderParams{Sort: models.StringPointer("locator"), Order: models.StringPointer("asc")} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(2, len(moves)) - suite.Equal(expectedMove1.Locator, moves[0].Locator) - suite.Equal(expectedMove2.Locator, moves[1].Locator) - - params = services.ListOrderParams{Sort: models.StringPointer("locator"), Order: models.StringPointer("desc")} - moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(2, len(moves)) - suite.Equal(expectedMove2.Locator, moves[0].Locator) - suite.Equal(expectedMove1.Locator, moves[1].Locator) - }) - - suite.Run("Sort by move status", func() { - expectedMove1, expectedMove2, session := setupTestData() - params := services.ListOrderParams{Sort: models.StringPointer("status"), Order: models.StringPointer("asc")} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(2, len(moves)) - suite.Equal(expectedMove1.Status, moves[0].Status) - suite.Equal(expectedMove2.Status, moves[1].Status) - - params = services.ListOrderParams{Sort: models.StringPointer("status"), Order: models.StringPointer("desc")} - moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(2, len(moves)) - suite.Equal(expectedMove2.Status, moves[0].Status) - suite.Equal(expectedMove1.Status, moves[1].Status) - }) - - suite.Run("Sort by service member affiliations", func() { - expectedMove1, expectedMove2, session := setupTestData() - params := services.ListOrderParams{Sort: models.StringPointer("branch"), Order: models.StringPointer("asc")} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(2, len(moves)) - suite.Equal(*expectedMove1.Orders.ServiceMember.Affiliation, *moves[0].Orders.ServiceMember.Affiliation) - suite.Equal(*expectedMove2.Orders.ServiceMember.Affiliation, *moves[1].Orders.ServiceMember.Affiliation) + // build a move with destination service items + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), + }, + }}, nil) - params = services.ListOrderParams{Sort: models.StringPointer("branch"), Order: models.StringPointer("desc")} - moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(2, len(moves)) - suite.Equal(*expectedMove2.Orders.ServiceMember.Affiliation, *moves[0].Orders.ServiceMember.Affiliation) - suite.Equal(*expectedMove1.Orders.ServiceMember.Affiliation, *moves[1].Orders.ServiceMember.Affiliation) - }) + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + }, nil) + suite.NotNil(shipment) + destinationSITServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + }, + }, + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeDDFSIT, + }, + }, + }, nil) + suite.NotNil(destinationSITServiceItem) + sitExtension := factory.BuildSITDurationUpdate(suite.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.SITDurationUpdate{ + Status: models.SITExtensionStatusPending, + }, + }, + }, nil) + suite.NotNil(sitExtension) - suite.Run("Sort by request move date", func() { - _, _, session := setupTestData() - params := services.ListOrderParams{Sort: models.StringPointer("requestedMoveDate"), Order: models.StringPointer("asc")} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(2, len(moves)) - suite.Equal(2, len(moves[0].MTOShipments)) // the move with two shipments has the earlier date - suite.Equal(1, len(moves[1].MTOShipments)) - // NOTE: You have to use Jan 02, 2006 as the example for date/time formatting in Go - suite.Equal(requestedMoveDate1.Format("2006/01/02"), moves[1].MTOShipments[0].RequestedPickupDate.Format("2006/01/02")) + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) - params = services.ListOrderParams{Sort: models.StringPointer("requestedMoveDate"), Order: models.StringPointer("desc")} - moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(2, len(moves)) - suite.Equal(1, len(moves[0].MTOShipments)) // the move with one shipment should be first - suite.Equal(2, len(moves[1].MTOShipments)) - suite.Equal(requestedMoveDate1.Format("2006/01/02"), moves[0].MTOShipments[0].RequestedPickupDate.Format("2006/01/02")) + suite.FatalNoError(err) + suite.Equal(0, moveCount) + suite.Equal(0, len(moves)) }) - suite.Run("Sort by submitted date (appearedInTooAt) in TOO queue ", func() { - // Scenario: In order to sort the moves the submitted_at, service_counseling_completed_at, and approvals_requested_at are checked to which are the minimum - // Expected: The moves appear in the order they are created below - officeUser = factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + suite.Run("task order queue returns a move with a pending SIT extension and BOTH origin and destination service items to review", func() { + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) session := auth.Session{ ApplicationName: auth.OfficeApp, Roles: officeUser.User.Roles, @@ -1248,376 +1238,1815 @@ func (suite *OrderServiceSuite) TestListOrdersWithSortOrder() { IDToken: "fake_token", AccessToken: "fakeAccessToken", } - now := time.Now() - oneWeekAgo := now.AddDate(0, 0, -7) - move1 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + + // build a move with only origin service items + move := factory.BuildMove(suite.DB(), []factory.Customization{ { Model: models.Move{ - SubmittedAt: &oneWeekAgo, + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), }, + }}, nil) + + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, }, }, nil) - move2 := factory.BuildApprovalsRequestedMove(suite.DB(), nil, nil) - factory.BuildMTOShipmentWithMove(&move2, suite.DB(), nil, nil) - move3 := factory.BuildServiceCounselingCompletedMove(suite.DB(), nil, nil) - factory.BuildMTOShipmentWithMove(&move3, suite.DB(), nil, nil) - - params := services.ListOrderParams{Sort: models.StringPointer("appearedInTooAt"), Order: models.StringPointer("asc")} - - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(3, len(moves)) - suite.Equal(moves[0].ID, move1.ID) - suite.Equal(moves[1].ID, move2.ID) - suite.Equal(moves[2].ID, move3.ID) - }) - - // MUST BE LAST, ADDS EXTRA MOVE - suite.Run("Sort by service member last name", func() { - _, _, session := setupTestData() + suite.NotNil(shipment) + originSITServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeDOFSIT, + }, + }, + }, nil) + suite.NotNil(originSITServiceItem) - // Last name sort is the only one that needs 3 moves for a complete test, so add that here at the end - factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + destinationSITServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ { - Model: models.ServiceMember{ // Leo Zephyer - LastName: &serviceMemberLastName, + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeDDFSIT, }, }, }, nil) - params := services.ListOrderParams{Sort: models.StringPointer("customerName"), Order: models.StringPointer("asc")} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(3, len(moves)) - suite.Equal("Spacemen, Lea", *moves[0].Orders.ServiceMember.LastName+", "+*moves[0].Orders.ServiceMember.FirstName) - suite.Equal("Spacemen, Leo", *moves[1].Orders.ServiceMember.LastName+", "+*moves[1].Orders.ServiceMember.FirstName) - suite.Equal("Zephyer, Leo", *moves[2].Orders.ServiceMember.LastName+", "+*moves[2].Orders.ServiceMember.FirstName) + sitExtension := factory.BuildSITDurationUpdate(suite.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.SITDurationUpdate{ + Status: models.SITExtensionStatusPending, + }, + }, + }, nil) + suite.NotNil(sitExtension) - params = services.ListOrderParams{Sort: models.StringPointer("customerName"), Order: models.StringPointer("desc")} - moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) - suite.NoError(err) - suite.Equal(3, len(moves)) - suite.Equal("Zephyer, Leo", *moves[0].Orders.ServiceMember.LastName+", "+*moves[0].Orders.ServiceMember.FirstName) - suite.Equal("Spacemen, Leo", *moves[1].Orders.ServiceMember.LastName+", "+*moves[1].Orders.ServiceMember.FirstName) - suite.Equal("Spacemen, Lea", *moves[2].Orders.ServiceMember.LastName+", "+*moves[2].Orders.ServiceMember.FirstName) + suite.Equal(models.MTOServiceItemStatusSubmitted, destinationSITServiceItem.Status) + suite.FatalNoError(err) + suite.Equal(1, moveCount) + suite.Equal(1, len(moves)) }) } +func (suite *OrderServiceSuite) TestListOrderWithAssignedUserSingle() { + // Under test: ListOrders + // Set up: Make a move, assign one to an SC office user + // Expected outcome: Only the one move with the assigned user should be returned + assignedOfficeUserUpdater := moveservice.NewAssignedOfficeUserUpdater(moveservice.NewMoveFetcher()) + scUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeServicesCounselor}) + var orderFetcherTest orderFetcher + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: scUser.User.Roles, + OfficeUserID: scUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } -func getTransportationOffice(suite *OrderServiceSuite, name string) models.TransportationOffice { - trasportationOffice := factory.BuildTransportationOffice(suite.DB(), []factory.Customization{ - { - Model: models.TransportationOffice{ - Name: name, - }, - }}, nil) - return trasportationOffice -} + appCtx := suite.AppContextWithSessionForTest(&session) -func getPPMShipmentWithCloseoutOfficeNeedsCloseout(suite *OrderServiceSuite, closeoutOffice models.TransportationOffice) models.PPMShipment { - ppm := factory.BuildPPMShipmentThatNeedsCloseout(suite.DB(), nil, []factory.Customization{ - { - Model: closeoutOffice, - LinkOnly: true, - Type: &factory.TransportationOffices.CloseoutOffice, - }, + createdMove := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + createdMove.SCAssignedID = &scUser.ID + createdMove.SCAssignedUser = &scUser + _, updateError := assignedOfficeUserUpdater.UpdateAssignedOfficeUser(appCtx, createdMove.ID, &scUser, roles.RoleTypeServicesCounselor) + + moves, _, err := orderFetcherTest.ListOrders(suite.AppContextWithSessionForTest(&session), scUser.ID, roles.RoleTypeServicesCounselor, &services.ListOrderParams{ + SCAssignedUser: &scUser.LastName, }) - return ppm -} -func (suite *OrderServiceSuite) TestListOrdersNeedingServicesCounselingWithPPMCloseoutColumnsSort() { - defaultShipmentPickupPostalCode := "90210" + suite.FatalNoError(err) + suite.FatalNoError(updateError) + suite.Equal(1, len(moves)) + suite.Equal(moves[0].SCAssignedID, createdMove.SCAssignedID) + suite.Equal(createdMove.SCAssignedUser.ID, moves[0].SCAssignedUser.ID) + suite.Equal(createdMove.SCAssignedUser.FirstName, moves[0].SCAssignedUser.FirstName) + suite.Equal(createdMove.SCAssignedUser.LastName, moves[0].SCAssignedUser.LastName) +} +func (suite *OrderServiceSuite) TestListOrdersUSMCGBLOC() { waf := entitlements.NewWeightAllotmentFetcher() - setupTestData := func() models.OfficeUser { - // Make an office user → GBLOC X - officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) - factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), "50309", officeUser.TransportationOffice.Gbloc) - - // Ensure there's an entry connecting the default shipment pickup postal code with the office user's gbloc - factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), - defaultShipmentPickupPostalCode, - officeUser.TransportationOffice.Gbloc) - - return officeUser - } orderFetcher := NewOrderFetcher(waf) - var session auth.Session - - suite.Run("Sort by PPM closeout initiated", func() { - officeUser := setupTestData() - // Create a PPM submitted on April 1st - closeoutInitiatedDate1 := time.Date(2022, 04, 01, 0, 0, 0, 0, time.UTC) - closeoutOffice := factory.BuildTransportationOffice(suite.DB(), []factory.Customization{ + suite.Run("returns USMC order for USMC office user", func() { + marines := models.AffiliationMARINES + // It doesn't matter what the Origin GBLOC is for the move. Only the Marines + // affiliation matters for office users who are tied to the USMC GBLOC. + factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ { - Model: models.TransportationOffice{Gbloc: "KKFA"}, + Model: models.ServiceMember{ + Affiliation: &marines, + }, }, }, nil) + // Create move where service member has the default ARMY affiliation + factory.BuildMoveWithShipment(suite.DB(), nil, nil) - ppm1 := factory.BuildPPMShipmentThatNeedsCloseout(suite.DB(), nil, []factory.Customization{ + tioRole := roles.Role{RoleType: roles.RoleTypeTIO} + tooRole := roles.Role{RoleType: roles.RoleTypeTOO} + officeUserOooRah := factory.BuildOfficeUser(suite.DB(), []factory.Customization{ { - Model: models.PPMShipment{ - SubmittedAt: &closeoutInitiatedDate1, + Model: models.TransportationOffice{ + Gbloc: "USMC", }, }, { - Model: closeoutOffice, - LinkOnly: true, - Type: &factory.TransportationOffices.CloseoutOffice, - }, - }) - - // Create a PPM submitted on April 2nd - closeoutInitiatedDate2 := time.Date(2022, 04, 02, 0, 0, 0, 0, time.UTC) - ppm2 := factory.BuildPPMShipmentThatNeedsCloseout(suite.DB(), nil, []factory.Customization{ - { - Model: models.PPMShipment{ - SubmittedAt: &closeoutInitiatedDate2, + Model: models.User{ + Roles: []roles.Role{tioRole, tooRole}, }, }, - { - Model: closeoutOffice, - LinkOnly: true, - Type: &factory.TransportationOffices.CloseoutOffice, - }, - }) + }, nil) + // Create office user tied to the default KKFA GBLOC + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } - // Sort by closeout initiated date (ascending) - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ - NeedsPPMCloseout: models.BoolPointer(true), - Sort: models.StringPointer("closeoutInitiated"), - Order: models.StringPointer("asc"), - }) + params := services.ListOrderParams{PerPage: models.Int64Pointer(2), Page: models.Int64Pointer(1)} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUserOooRah.ID, roles.RoleTypeServicesCounselor, ¶ms) suite.FatalNoError(err) - suite.Equal(2, len(moves)) - suite.Equal(ppm1.Shipment.MoveTaskOrder.Locator, moves[0].Locator) - suite.Equal(ppm2.Shipment.MoveTaskOrder.Locator, moves[1].Locator) + suite.Equal(1, len(moves)) + suite.Equal(models.AffiliationMARINES, *moves[0].Orders.ServiceMember.Affiliation) - // Sort by closeout initiated date (descending) - moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ - NeedsPPMCloseout: models.BoolPointer(true), - Sort: models.StringPointer("closeoutInitiated"), - Order: models.StringPointer("desc"), - }) + params = services.ListOrderParams{PerPage: models.Int64Pointer(2), Page: models.Int64Pointer(1)} + moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeServicesCounselor, ¶ms) suite.FatalNoError(err) - suite.Equal(2, len(moves)) - suite.Equal(ppm2.Shipment.MoveTaskOrder.Locator, moves[0].Locator) - suite.Equal(ppm1.Shipment.MoveTaskOrder.Locator, moves[1].Locator) + suite.Equal(1, len(moves)) + suite.Equal(models.AffiliationARMY, *moves[0].Orders.ServiceMember.Affiliation) }) +} - suite.Run("Sort by PPM closeout location", func() { - officeUser := setupTestData() - - locationA := getTransportationOffice(suite, "A") - ppmShipmentA := getPPMShipmentWithCloseoutOfficeNeedsCloseout(suite, locationA) +func getMoveNeedsServiceCounseling(suite *OrderServiceSuite, showMove bool, affiliation models.ServiceMemberAffiliation) models.Move { + nonCloseoutMove := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusNeedsServiceCounseling, + Show: &showMove, + }, + }, + { + Model: models.ServiceMember{ + Affiliation: &affiliation, + }, + }, + }, nil) - locationB := getTransportationOffice(suite, "B") - ppmShipmentB := getPPMShipmentWithCloseoutOfficeNeedsCloseout(suite, locationB) + return nonCloseoutMove +} - // Sort by closeout location (ascending) - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ - NeedsPPMCloseout: models.BoolPointer(true), - Sort: models.StringPointer("closeoutLocation"), - Order: models.StringPointer("asc"), - }) +func getSubmittedMove(suite *OrderServiceSuite, showMove bool, affiliation models.ServiceMemberAffiliation) models.Move { + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusSUBMITTED, + Show: &showMove, + }, + }, + { + Model: models.ServiceMember{ + Affiliation: &affiliation, + }, + }, + }, nil) + return move +} + +func buildPPMShipmentNeedsCloseout(suite *OrderServiceSuite, move models.Move) models.PPMShipment { + ppm := factory.BuildMinimalPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.PPMShipment{ + Status: models.PPMShipmentStatusNeedsCloseout, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + return ppm +} + +func buildPPMShipmentDraft(suite *OrderServiceSuite, move models.Move) models.PPMShipment { + ppm := factory.BuildMinimalPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.PPMShipment{ + Status: models.PPMShipmentStatusDraft, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + return ppm +} + +func buildPPMShipmentCloseoutComplete(suite *OrderServiceSuite, move models.Move) models.PPMShipment { + ppm := factory.BuildMinimalPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.PPMShipment{ + Status: models.PPMShipmentStatusCloseoutComplete, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + return ppm +} +func (suite *OrderServiceSuite) TestListOrdersPPMCloseoutForArmyAirforce() { + waf := entitlements.NewWeightAllotmentFetcher() + orderFetcher := NewOrderFetcher(waf) + + var session auth.Session + + suite.Run("office user in normal GBLOC should only see non-Navy/Marines/CoastGuard moves that need closeout in closeout tab", func() { + officeUserSC := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeServicesCounselor}) + + session = auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUserSC.User.Roles, + OfficeUserID: officeUserSC.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + move := getMoveNeedsServiceCounseling(suite, true, models.AffiliationARMY) + buildPPMShipmentNeedsCloseout(suite, move) + + afMove := getMoveNeedsServiceCounseling(suite, true, models.AffiliationAIRFORCE) + buildPPMShipmentDraft(suite, afMove) + + cgMove := getMoveNeedsServiceCounseling(suite, true, models.AffiliationCOASTGUARD) + buildPPMShipmentNeedsCloseout(suite, cgMove) + + params := services.ListOrderParams{PerPage: models.Int64Pointer(9), Page: models.Int64Pointer(1), NeedsPPMCloseout: models.BoolPointer(true), Status: []string{string(models.MoveStatusNeedsServiceCounseling)}} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUserSC.ID, roles.RoleTypeServicesCounselor, ¶ms) suite.FatalNoError(err) - suite.Equal(2, len(moves)) - suite.Equal(ppmShipmentA.Shipment.MoveTaskOrder.Locator, moves[0].Locator) - suite.Equal(ppmShipmentB.Shipment.MoveTaskOrder.Locator, moves[1].Locator) + suite.Equal(1, len(moves)) + suite.Equal(move.Locator, moves[0].Locator) + }) - // Sort by closeout location (descending) - moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ - NeedsPPMCloseout: models.BoolPointer(true), - Sort: models.StringPointer("closeoutLocation"), - Order: models.StringPointer("desc"), - }) + suite.Run("office user in normal GBLOC should not see moves that require closeout in counseling tab", func() { + officeUserSC := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeServicesCounselor}) + + session = auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUserSC.User.Roles, + OfficeUserID: officeUserSC.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + closeoutMove := getMoveNeedsServiceCounseling(suite, true, models.AffiliationARMY) + buildPPMShipmentCloseoutComplete(suite, closeoutMove) + + // PPM moves that are not in one of the closeout statuses + nonCloseoutMove := getMoveNeedsServiceCounseling(suite, true, models.AffiliationAIRFORCE) + buildPPMShipmentDraft(suite, nonCloseoutMove) + + params := services.ListOrderParams{PerPage: models.Int64Pointer(9), Page: models.Int64Pointer(1), NeedsPPMCloseout: models.BoolPointer(false), Status: []string{string(models.MoveStatusNeedsServiceCounseling)}} + + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUserSC.ID, roles.RoleTypeServicesCounselor, ¶ms) suite.FatalNoError(err) - suite.Equal(2, len(moves)) - suite.Equal(ppmShipmentB.Shipment.MoveTaskOrder.Locator, moves[0].Locator) - suite.Equal(ppmShipmentA.Shipment.MoveTaskOrder.Locator, moves[1].Locator) + suite.Equal(1, len(moves)) + suite.Equal(nonCloseoutMove.Locator, moves[0].Locator) }) +} - suite.Run("Sort by destination duty location", func() { - officeUser := setupTestData() +func (suite *OrderServiceSuite) TestListOrdersPPMCloseoutForNavyCoastGuardAndMarines() { + waf := entitlements.NewWeightAllotmentFetcher() + orderFetcher := NewOrderFetcher(waf) - dutyLocationA := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + suite.Run("returns Navy order for NAVY office user when there's a ppm shipment in closeout", func() { + // It doesn't matter what the Origin GBLOC is for the move. Only the navy + // affiliation matters for SC who are tied to the NAVY GBLOC. + move := getSubmittedMove(suite, true, models.AffiliationNAVY) + buildPPMShipmentNeedsCloseout(suite, move) + + cgMove := getSubmittedMove(suite, true, models.AffiliationCOASTGUARD) + buildPPMShipmentNeedsCloseout(suite, cgMove) + + officeUserSC := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ { - Model: models.DutyLocation{ - Name: "A", + Model: models.TransportationOffice{ + Gbloc: "NAVY", }, }, - }, nil) - closeoutOffice := factory.BuildTransportationOffice(suite.DB(), []factory.Customization{ + }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUserSC.User.Roles, + OfficeUserID: officeUserSC.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + params := services.ListOrderParams{PerPage: models.Int64Pointer(9), Page: models.Int64Pointer(1)} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUserSC.ID, roles.RoleTypeServicesCounselor, ¶ms) + + suite.FatalNoError(err) + suite.Equal(1, len(moves)) + suite.Equal(models.AffiliationNAVY, *moves[0].Orders.ServiceMember.Affiliation) + + }) + + suite.Run("returns TVCB order for TVCB office user when there's a ppm shipment in closeout", func() { + // It doesn't matter what the Origin GBLOC is for the move. Only the marines + // affiliation matters for SC who are tied to the TVCB GBLOC. + move := getSubmittedMove(suite, true, models.AffiliationMARINES) + buildPPMShipmentNeedsCloseout(suite, move) + + nonMarineMove := getSubmittedMove(suite, true, models.AffiliationARMY) + buildPPMShipmentNeedsCloseout(suite, nonMarineMove) + + officeUserSC := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ { - Model: models.TransportationOffice{Gbloc: "KKFA"}, + Model: models.TransportationOffice{ + Gbloc: "TVCB", + }, + }, + }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUserSC.User.Roles, + OfficeUserID: officeUserSC.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + params := services.ListOrderParams{PerPage: models.Int64Pointer(2), Page: models.Int64Pointer(1)} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUserSC.ID, roles.RoleTypeServicesCounselor, ¶ms) + + suite.FatalNoError(err) + suite.Equal(1, len(moves)) + suite.Equal(models.AffiliationMARINES, *moves[0].Orders.ServiceMember.Affiliation) + + }) + + suite.Run("returns coast guard order for USCG office user when there's a ppm shipment in closeout and filters out non coast guard moves", func() { + // It doesn't matter what the Origin GBLOC is for the move. Only the coast guard + // affiliation matters for SC who are tied to the USCG GBLOC. + move := getSubmittedMove(suite, true, models.AffiliationCOASTGUARD) + buildPPMShipmentNeedsCloseout(suite, move) + + armyMove := getSubmittedMove(suite, true, models.AffiliationARMY) + buildPPMShipmentNeedsCloseout(suite, armyMove) + + officeUserSC := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{ + Gbloc: "USCG", + }, + }, + }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUserSC.User.Roles, + OfficeUserID: officeUserSC.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + params := services.ListOrderParams{PerPage: models.Int64Pointer(2), Page: models.Int64Pointer(1)} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUserSC.ID, roles.RoleTypeServicesCounselor, ¶ms) + + suite.FatalNoError(err) + suite.Equal(1, len(moves)) + suite.Equal(models.AffiliationCOASTGUARD, *moves[0].Orders.ServiceMember.Affiliation) + }) + + suite.Run("Filters out moves with PPM shipments not in the status of NeedsApproval", func() { + + cgMoveInWrongStatus := getSubmittedMove(suite, true, models.AffiliationCOASTGUARD) + buildPPMShipmentCloseoutComplete(suite, cgMoveInWrongStatus) + + officeUserSC := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{ + Gbloc: "USCG", + }, + }, + }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + var session auth.Session + params := services.ListOrderParams{PerPage: models.Int64Pointer(2), Page: models.Int64Pointer(1)} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUserSC.ID, roles.RoleTypeServicesCounselor, ¶ms) + + suite.FatalNoError(err) + suite.Equal(0, len(moves)) + }) + + suite.Run("Filters out moves with no PPM shipment", func() { + + moveWithHHG := getSubmittedMove(suite, true, models.AffiliationCOASTGUARD) + factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + ShipmentType: models.MTOShipmentTypeHHG, + }, + }, + { + Model: moveWithHHG, + LinkOnly: true, }, }, nil) - ppmShipmentA := factory.BuildPPMShipmentThatNeedsCloseout(suite.DB(), nil, []factory.Customization{ + officeUserSC := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{ + Gbloc: "USCG", + }, + }, + }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUserSC.User.Roles, + OfficeUserID: officeUserSC.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + params := services.ListOrderParams{PerPage: models.Int64Pointer(2), Page: models.Int64Pointer(1)} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUserSC.ID, roles.RoleTypeServicesCounselor, ¶ms) + + suite.FatalNoError(err) + suite.Equal(0, len(moves)) + }) +} + +func (suite *OrderServiceSuite) TestListOrdersMarines() { + waf := entitlements.NewWeightAllotmentFetcher() + suite.Run("does not return moves where the service member affiliation is Marines for non-USMC office user", func() { + + orderFetcher := NewOrderFetcher(waf) + marines := models.AffiliationMARINES + factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ + Affiliation: &marines, + }, + }, + }, nil) + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + params := services.ListOrderParams{PerPage: models.Int64Pointer(2), Page: models.Int64Pointer(1)} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + + suite.FatalNoError(err) + suite.Equal(0, len(moves)) + }) +} + +func (suite *OrderServiceSuite) TestListOrdersWithEmptyFields() { + expectedOrder := factory.BuildOrder(suite.DB(), nil, nil) + waf := entitlements.NewWeightAllotmentFetcher() + expectedOrder.Entitlement = nil + expectedOrder.EntitlementID = nil + expectedOrder.Grade = nil + expectedOrder.OriginDutyLocation = nil + expectedOrder.OriginDutyLocationID = nil + suite.MustSave(&expectedOrder) + + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: expectedOrder, + LinkOnly: true, + }, + }, nil) + // Only orders with shipments are returned, so we need to add a shipment + // to the move we just created + factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusSubmitted, + }, + }, + }, nil) + // Add a second shipment to make sure we only return 1 order even if its + // move has more than one shipment + factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusSubmitted, + }, + }, + }, nil) + + officeUser := factory.BuildOfficeUser(suite.DB(), nil, nil) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + orderFetcher := NewOrderFetcher(waf) + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{PerPage: models.Int64Pointer(1), Page: models.Int64Pointer(1)}) + + suite.FatalNoError(err) + suite.Nil(moves) + +} + +func (suite *OrderServiceSuite) TestListOrdersWithPagination() { + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + waf := entitlements.NewWeightAllotmentFetcher() + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + for i := 0; i < 2; i++ { + factory.BuildMoveWithShipment(suite.DB(), nil, nil) + } + + orderFetcher := NewOrderFetcher(waf) + params := services.ListOrderParams{Page: models.Int64Pointer(1), PerPage: models.Int64Pointer(1)} + moves, count, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + + suite.NoError(err) + suite.Equal(1, len(moves)) + suite.Equal(2, count) + +} + +func (suite *OrderServiceSuite) TestListOrdersWithSortOrder() { + + // SET UP: Service Members for sorting by Service Member Last Name and Branch + // - We'll need two other service members to test the last name sort, Lea Spacemen and Leo Zephyer + serviceMemberFirstName := "Lea" + serviceMemberLastName := "Zephyer" + affiliation := models.AffiliationNAVY + edipi := "9999999999" + var officeUser models.OfficeUser + waf := entitlements.NewWeightAllotmentFetcher() + // SET UP: Dates for sorting by Requested Move Date + // - We want dates 2 and 3 to sandwich requestedMoveDate1 so we can test that the min() query is working + requestedMoveDate1 := time.Date(testdatagen.GHCTestYear, 02, 20, 0, 0, 0, 0, time.UTC) + requestedMoveDate2 := time.Date(testdatagen.GHCTestYear, 03, 03, 0, 0, 0, 0, time.UTC) + requestedMoveDate3 := time.Date(testdatagen.GHCTestYear, 01, 15, 0, 0, 0, 0, time.UTC) + + setupTestData := func() (models.Move, models.Move, auth.Session) { + + // CREATE EXPECTED MOVES + expectedMove1 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { // Default New Duty Location name is Fort Eisenhower + Model: models.Move{ + Status: models.MoveStatusAPPROVED, + Locator: "AA1234", + }, + }, + { + Model: models.MTOShipment{ + RequestedPickupDate: &requestedMoveDate1, + }, + }, + }, nil) + expectedMove2 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Locator: "TTZ123", + }, + }, + { + Model: models.ServiceMember{ + Affiliation: &affiliation, + FirstName: &serviceMemberFirstName, + Edipi: &edipi, + }, + }, + { + Model: models.MTOShipment{ + RequestedPickupDate: &requestedMoveDate2, + }, + }, + }, nil) + // Create a second shipment so we can test min() sort + factory.BuildMTOShipmentWithMove(&expectedMove2, suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + RequestedPickupDate: &requestedMoveDate3, + }, + }, + }, nil) + officeUser = factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + return expectedMove1, expectedMove2, session + } + + orderFetcher := NewOrderFetcher(waf) + + suite.Run("Sort by locator code", func() { + expectedMove1, expectedMove2, session := setupTestData() + params := services.ListOrderParams{Sort: models.StringPointer("locator"), Order: models.StringPointer("asc")} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(2, len(moves)) + suite.Equal(expectedMove1.Locator, moves[0].Locator) + suite.Equal(expectedMove2.Locator, moves[1].Locator) + + params = services.ListOrderParams{Sort: models.StringPointer("locator"), Order: models.StringPointer("desc")} + moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(2, len(moves)) + suite.Equal(expectedMove2.Locator, moves[0].Locator) + suite.Equal(expectedMove1.Locator, moves[1].Locator) + }) + + suite.Run("Sort by move status", func() { + expectedMove1, expectedMove2, session := setupTestData() + params := services.ListOrderParams{Sort: models.StringPointer("status"), Order: models.StringPointer("asc")} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(2, len(moves)) + suite.Equal(expectedMove1.Status, moves[0].Status) + suite.Equal(expectedMove2.Status, moves[1].Status) + + params = services.ListOrderParams{Sort: models.StringPointer("status"), Order: models.StringPointer("desc")} + moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(2, len(moves)) + suite.Equal(expectedMove2.Status, moves[0].Status) + suite.Equal(expectedMove1.Status, moves[1].Status) + }) + + suite.Run("Sort by service member affiliations", func() { + expectedMove1, expectedMove2, session := setupTestData() + params := services.ListOrderParams{Sort: models.StringPointer("branch"), Order: models.StringPointer("asc")} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(2, len(moves)) + suite.Equal(*expectedMove1.Orders.ServiceMember.Affiliation, *moves[0].Orders.ServiceMember.Affiliation) + suite.Equal(*expectedMove2.Orders.ServiceMember.Affiliation, *moves[1].Orders.ServiceMember.Affiliation) + + params = services.ListOrderParams{Sort: models.StringPointer("branch"), Order: models.StringPointer("desc")} + moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(2, len(moves)) + suite.Equal(*expectedMove2.Orders.ServiceMember.Affiliation, *moves[0].Orders.ServiceMember.Affiliation) + suite.Equal(*expectedMove1.Orders.ServiceMember.Affiliation, *moves[1].Orders.ServiceMember.Affiliation) + }) + + suite.Run("Sort by request move date", func() { + _, _, session := setupTestData() + params := services.ListOrderParams{Sort: models.StringPointer("requestedMoveDate"), Order: models.StringPointer("asc")} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(2, len(moves)) + suite.Equal(2, len(moves[0].MTOShipments)) // the move with two shipments has the earlier date + suite.Equal(1, len(moves[1].MTOShipments)) + // NOTE: You have to use Jan 02, 2006 as the example for date/time formatting in Go + suite.Equal(requestedMoveDate1.Format("2006/01/02"), moves[1].MTOShipments[0].RequestedPickupDate.Format("2006/01/02")) + + params = services.ListOrderParams{Sort: models.StringPointer("requestedMoveDate"), Order: models.StringPointer("desc")} + moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(2, len(moves)) + suite.Equal(1, len(moves[0].MTOShipments)) // the move with one shipment should be first + suite.Equal(2, len(moves[1].MTOShipments)) + suite.Equal(requestedMoveDate1.Format("2006/01/02"), moves[0].MTOShipments[0].RequestedPickupDate.Format("2006/01/02")) + }) + + suite.Run("Sort by submitted date (appearedInTooAt) in TOO queue ", func() { + // Scenario: In order to sort the moves the submitted_at, service_counseling_completed_at, and approvals_requested_at are checked to which are the minimum + // Expected: The moves appear in the order they are created below + officeUser = factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + now := time.Now() + oneWeekAgo := now.AddDate(0, 0, -7) + move1 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + SubmittedAt: &oneWeekAgo, + }, + }, + }, nil) + move2 := factory.BuildApprovalsRequestedMove(suite.DB(), nil, nil) + factory.BuildMTOShipmentWithMove(&move2, suite.DB(), nil, nil) + move3 := factory.BuildServiceCounselingCompletedMove(suite.DB(), nil, nil) + factory.BuildMTOShipmentWithMove(&move3, suite.DB(), nil, nil) + + params := services.ListOrderParams{Sort: models.StringPointer("appearedInTooAt"), Order: models.StringPointer("asc")} + + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(3, len(moves)) + suite.Equal(moves[0].ID, move1.ID) + suite.Equal(moves[1].ID, move2.ID) + suite.Equal(moves[2].ID, move3.ID) + }) + + // MUST BE LAST, ADDS EXTRA MOVE + suite.Run("Sort by service member last name", func() { + _, _, session := setupTestData() + + // Last name sort is the only one that needs 3 moves for a complete test, so add that here at the end + factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ // Leo Zephyer + LastName: &serviceMemberLastName, + }, + }, + }, nil) + params := services.ListOrderParams{Sort: models.StringPointer("customerName"), Order: models.StringPointer("asc")} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + + suite.NoError(err) + suite.Equal(3, len(moves)) + suite.Equal("Spacemen, Lea", *moves[0].Orders.ServiceMember.LastName+", "+*moves[0].Orders.ServiceMember.FirstName) + suite.Equal("Spacemen, Leo", *moves[1].Orders.ServiceMember.LastName+", "+*moves[1].Orders.ServiceMember.FirstName) + suite.Equal("Zephyer, Leo", *moves[2].Orders.ServiceMember.LastName+", "+*moves[2].Orders.ServiceMember.FirstName) + + params = services.ListOrderParams{Sort: models.StringPointer("customerName"), Order: models.StringPointer("desc")} + moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + + suite.NoError(err) + suite.Equal(3, len(moves)) + suite.Equal("Zephyer, Leo", *moves[0].Orders.ServiceMember.LastName+", "+*moves[0].Orders.ServiceMember.FirstName) + suite.Equal("Spacemen, Leo", *moves[1].Orders.ServiceMember.LastName+", "+*moves[1].Orders.ServiceMember.FirstName) + suite.Equal("Spacemen, Lea", *moves[2].Orders.ServiceMember.LastName+", "+*moves[2].Orders.ServiceMember.FirstName) + }) +} + +func getTransportationOffice(suite *OrderServiceSuite, name string) models.TransportationOffice { + trasportationOffice := factory.BuildTransportationOffice(suite.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{ + Name: name, + }, + }}, nil) + return trasportationOffice +} + +func getPPMShipmentWithCloseoutOfficeNeedsCloseout(suite *OrderServiceSuite, closeoutOffice models.TransportationOffice) models.PPMShipment { + ppm := factory.BuildPPMShipmentThatNeedsCloseout(suite.DB(), nil, []factory.Customization{ + { + Model: closeoutOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CloseoutOffice, + }, + }) + return ppm +} + +func (suite *OrderServiceSuite) TestListOrdersNeedingServicesCounselingWithPPMCloseoutColumnsSort() { + defaultShipmentPickupPostalCode := "90210" + waf := entitlements.NewWeightAllotmentFetcher() + setupTestData := func() models.OfficeUser { + // Make an office user → GBLOC X + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), "50309", officeUser.TransportationOffice.Gbloc) + + // Ensure there's an entry connecting the default shipment pickup postal code with the office user's gbloc + factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), + defaultShipmentPickupPostalCode, + officeUser.TransportationOffice.Gbloc) + + return officeUser + } + orderFetcher := NewOrderFetcher(waf) + + var session auth.Session + + suite.Run("Sort by PPM closeout initiated", func() { + officeUser := setupTestData() + // Create a PPM submitted on April 1st + closeoutInitiatedDate1 := time.Date(2022, 04, 01, 0, 0, 0, 0, time.UTC) + closeoutOffice := factory.BuildTransportationOffice(suite.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{Gbloc: "KKFA"}, + }, + }, nil) + + ppm1 := factory.BuildPPMShipmentThatNeedsCloseout(suite.DB(), nil, []factory.Customization{ + { + Model: models.PPMShipment{ + SubmittedAt: &closeoutInitiatedDate1, + }, + }, + { + Model: closeoutOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CloseoutOffice, + }, + }) + + // Create a PPM submitted on April 2nd + closeoutInitiatedDate2 := time.Date(2022, 04, 02, 0, 0, 0, 0, time.UTC) + ppm2 := factory.BuildPPMShipmentThatNeedsCloseout(suite.DB(), nil, []factory.Customization{ + { + Model: models.PPMShipment{ + SubmittedAt: &closeoutInitiatedDate2, + }, + }, + { + Model: closeoutOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CloseoutOffice, + }, + }) + + // Sort by closeout initiated date (ascending) + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ + NeedsPPMCloseout: models.BoolPointer(true), + Sort: models.StringPointer("closeoutInitiated"), + Order: models.StringPointer("asc"), + }) + + suite.FatalNoError(err) + suite.Equal(2, len(moves)) + suite.Equal(ppm1.Shipment.MoveTaskOrder.Locator, moves[0].Locator) + suite.Equal(ppm2.Shipment.MoveTaskOrder.Locator, moves[1].Locator) + + // Sort by closeout initiated date (descending) + moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ + NeedsPPMCloseout: models.BoolPointer(true), + Sort: models.StringPointer("closeoutInitiated"), + Order: models.StringPointer("desc"), + }) + + suite.FatalNoError(err) + suite.Equal(2, len(moves)) + suite.Equal(ppm2.Shipment.MoveTaskOrder.Locator, moves[0].Locator) + suite.Equal(ppm1.Shipment.MoveTaskOrder.Locator, moves[1].Locator) + }) + + suite.Run("Sort by PPM closeout location", func() { + officeUser := setupTestData() + + locationA := getTransportationOffice(suite, "A") + ppmShipmentA := getPPMShipmentWithCloseoutOfficeNeedsCloseout(suite, locationA) + + locationB := getTransportationOffice(suite, "B") + ppmShipmentB := getPPMShipmentWithCloseoutOfficeNeedsCloseout(suite, locationB) + + // Sort by closeout location (ascending) + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ + NeedsPPMCloseout: models.BoolPointer(true), + Sort: models.StringPointer("closeoutLocation"), + Order: models.StringPointer("asc"), + }) + + suite.FatalNoError(err) + suite.Equal(2, len(moves)) + suite.Equal(ppmShipmentA.Shipment.MoveTaskOrder.Locator, moves[0].Locator) + suite.Equal(ppmShipmentB.Shipment.MoveTaskOrder.Locator, moves[1].Locator) + + // Sort by closeout location (descending) + moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ + NeedsPPMCloseout: models.BoolPointer(true), + Sort: models.StringPointer("closeoutLocation"), + Order: models.StringPointer("desc"), + }) + + suite.FatalNoError(err) + suite.Equal(2, len(moves)) + suite.Equal(ppmShipmentB.Shipment.MoveTaskOrder.Locator, moves[0].Locator) + suite.Equal(ppmShipmentA.Shipment.MoveTaskOrder.Locator, moves[1].Locator) + }) + + suite.Run("Sort by destination duty location", func() { + officeUser := setupTestData() + + dutyLocationA := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + Name: "A", + }, + }, + }, nil) + closeoutOffice := factory.BuildTransportationOffice(suite.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{Gbloc: "KKFA"}, + }, + }, nil) + + ppmShipmentA := factory.BuildPPMShipmentThatNeedsCloseout(suite.DB(), nil, []factory.Customization{ + { + Model: dutyLocationA, + LinkOnly: true, + Type: &factory.DutyLocations.NewDutyLocation, + }, + { + Model: closeoutOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CloseoutOffice, + }, + }) + dutyLocationB := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + Name: "B", + }, + }, + }, nil) + ppmShipmentB := factory.BuildPPMShipmentThatNeedsCloseout(suite.DB(), nil, []factory.Customization{ + { + Model: dutyLocationB, + LinkOnly: true, + Type: &factory.DutyLocations.NewDutyLocation, + }, + { + Model: closeoutOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CloseoutOffice, + }, + }) + + // Sort by destination duty location (ascending) + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ + NeedsPPMCloseout: models.BoolPointer(true), + Sort: models.StringPointer("destinationDutyLocation"), + Order: models.StringPointer("asc"), + }) + + suite.FatalNoError(err) + suite.Equal(2, len(moves)) + suite.Equal(ppmShipmentA.Shipment.MoveTaskOrder.Locator, moves[0].Locator) + suite.Equal(ppmShipmentB.Shipment.MoveTaskOrder.Locator, moves[1].Locator) + + // Sort by destination duty location (descending) + moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ + NeedsPPMCloseout: models.BoolPointer(true), + Sort: models.StringPointer("destinationDutyLocation"), + Order: models.StringPointer("desc"), + }) + + suite.FatalNoError(err) + suite.Equal(2, len(moves)) + suite.Equal(ppmShipmentB.Shipment.MoveTaskOrder.Locator, moves[0].Locator) + suite.Equal(ppmShipmentA.Shipment.MoveTaskOrder.Locator, moves[1].Locator) + }) + + suite.Run("Sort by PPM type (full or partial)", func() { + officeUser := setupTestData() + closeoutOffice := factory.BuildTransportationOffice(suite.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{Gbloc: "KKFA"}, + }, + }, nil) + ppmShipmentPartial := factory.BuildPPMShipmentThatNeedsCloseout(suite.DB(), nil, []factory.Customization{ + { + Model: models.Move{ + PPMType: models.StringPointer("Partial"), + }, + }, + { + Model: closeoutOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CloseoutOffice, + }, + }) + ppmShipmentFull := factory.BuildPPMShipmentThatNeedsCloseout(suite.DB(), nil, []factory.Customization{ + { + Model: models.Move{ + PPMType: models.StringPointer("FULL"), + }, + }, + { + Model: closeoutOffice, + LinkOnly: true, + Type: &factory.TransportationOffices.CloseoutOffice, + }, + }) + + // Sort by PPM type (ascending) + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ + NeedsPPMCloseout: models.BoolPointer(true), + Sort: models.StringPointer("ppmType"), + Order: models.StringPointer("asc"), + }) + + suite.FatalNoError(err) + suite.Equal(2, len(moves)) + suite.Equal(ppmShipmentFull.Shipment.MoveTaskOrder.Locator, moves[0].Locator) + suite.Equal(ppmShipmentPartial.Shipment.MoveTaskOrder.Locator, moves[1].Locator) + + // Sort by PPM type (descending) + moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ + NeedsPPMCloseout: models.BoolPointer(true), + Sort: models.StringPointer("ppmType"), + Order: models.StringPointer("desc"), + }) + + suite.FatalNoError(err) + suite.Equal(2, len(moves)) + suite.Equal(ppmShipmentPartial.Shipment.MoveTaskOrder.Locator, moves[0].Locator) + suite.Equal(ppmShipmentFull.Shipment.MoveTaskOrder.Locator, moves[1].Locator) + }) + suite.Run("Sort by PPM status", func() { + officeUser := setupTestData() + closeoutOffice := factory.BuildTransportationOffice(suite.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{Gbloc: "KKFA"}, + }, + }, nil) + ppmShipmentNeedsCloseout := getPPMShipmentWithCloseoutOfficeNeedsCloseout(suite, closeoutOffice) + + // Sort by PPM type (ascending) + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ + NeedsPPMCloseout: models.BoolPointer(true), + Sort: models.StringPointer("ppmStatus"), + Order: models.StringPointer("asc"), + }) + + suite.FatalNoError(err) + suite.Equal(1, len(moves)) + suite.Equal(ppmShipmentNeedsCloseout.Status, moves[0].MTOShipments[0].PPMShipment.Status) + + // Sort by PPM type (descending) + moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ + NeedsPPMCloseout: models.BoolPointer(true), + Sort: models.StringPointer("ppmStatus"), + Order: models.StringPointer("desc"), + }) + + suite.FatalNoError(err) + suite.Equal(1, len(moves)) + suite.Equal(ppmShipmentNeedsCloseout.Status, moves[0].MTOShipments[0].PPMShipment.Status) + }) +} + +func (suite *OrderServiceSuite) TestListOrdersNeedingServicesCounselingWithGBLOCSortFilter() { + waf := entitlements.NewWeightAllotmentFetcher() + suite.Run("Filter by origin GBLOC", func() { + + // TESTCASE SCENARIO + // Under test: OrderFetcher.ListOrders function + // Mocked: None + // Set up: We create 2 moves with different GBLOCs, KKFA and ZANY. Both moves require service counseling + // We create an office user with the GBLOC KKFA + // Then we request a list of moves sorted by GBLOC, ascending for service counseling + // Expected outcome: + // We expect only the move that matches the counselors GBLOC - aka the KKFA move. + + // Create a services counselor (default GBLOC is KKFA) + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeServicesCounselor}) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + // Create a move with Origin KKFA, needs service couseling + kkfaMove := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusNeedsServiceCounseling, + }, + }, + }, nil) + // Create data for a second Origin ZANY + dutyLocationAddress2 := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "Anchor 1212", + City: "Fort Eisenhower", + State: "GA", + PostalCode: "89898", + }, + }, + }, nil) + + factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), dutyLocationAddress2.PostalCode, "ZANY") + originDutyLocation2 := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + { + Model: models.DutyLocation{ + Name: "Fort Sam Snap", + }, + }, + { + Model: dutyLocationAddress2, + LinkOnly: true, + }, + }, nil) + + // Create a second move from the ZANY gbloc + factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusNeedsServiceCounseling, + Locator: "ZZ1234", + }, + }, + { + Model: originDutyLocation2, + LinkOnly: true, + Type: &factory.DutyLocations.OriginDutyLocation, + }, + }, nil) + // Setup and run the function under test requesting status NEEDS SERVICE COUNSELING + orderFetcher := NewOrderFetcher(waf) + statuses := []string{"NEEDS SERVICE COUNSELING"} + // Sort by origin GBLOC, filter by status + params := services.ListOrderParams{Sort: models.StringPointer("originGBLOC"), Order: models.StringPointer("asc"), Status: statuses} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeServicesCounselor, ¶ms) + + // Expect only LKNQ move to be returned + suite.NoError(err) + suite.Equal(1, len(moves)) + suite.Equal(kkfaMove.ID, moves[0].ID) + }) +} + +func (suite *OrderServiceSuite) TestListOrdersForTOOWithNTSRelease() { + // Make an NTS-Release shipment (and a move). Should not have a pickup address. + factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + ShipmentType: models.MTOShipmentTypeHHGOutOfNTS, + }, + }, + }, nil) + waf := entitlements.NewWeightAllotmentFetcher() + // Make a TOO user and the postal code to GBLOC link. + tooOfficeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: tooOfficeUser.User.Roles, + OfficeUserID: tooOfficeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + orderFetcher := NewOrderFetcher(waf) + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), tooOfficeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) + + suite.FatalNoError(err) + suite.Equal(1, moveCount) + suite.Len(moves, 1) +} + +func (suite *OrderServiceSuite) TestListOrdersForTOOWithPPM() { + postalCode := "50309" + partialPPMType := models.MovePPMTypePARTIAL + waf := entitlements.NewWeightAllotmentFetcher() + ppmShipment := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.Order{ + ID: uuid.UUID{uuid.V4}, + }, + }, + { + Model: models.Move{ + Status: models.MoveStatusAPPROVED, + PPMType: &partialPPMType, + }, + }, + { + Model: models.Address{ + PostalCode: postalCode, + }, + Type: &factory.Addresses.PickupAddress, + }, + }, nil) + // Make a TOO user. + tooOfficeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: tooOfficeUser.User.Roles, + OfficeUserID: tooOfficeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + // GBLOC for the below doesn't really matter, it just means the query for the moves passes the inner join in ListOrders + factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), ppmShipment.PickupAddress.PostalCode, tooOfficeUser.TransportationOffice.Gbloc) + + orderFetcher := NewOrderFetcher(waf) + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), tooOfficeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) + suite.FatalNoError(err) + suite.Equal(1, moveCount) + suite.Len(moves, 1) +} + +func (suite *OrderServiceSuite) TestListOrdersWithViewAsGBLOCParam() { + var hqOfficeUser models.OfficeUser + var hqOfficeUserAGFM models.OfficeUser + waf := entitlements.NewWeightAllotmentFetcher() + requestedMoveDate1 := time.Date(testdatagen.GHCTestYear, 02, 20, 0, 0, 0, 0, time.UTC) + requestedMoveDate2 := time.Date(testdatagen.GHCTestYear, 03, 03, 0, 0, 0, 0, time.UTC) + + setupTestData := func() (models.Move, models.Move, models.MTOShipment, auth.Session, auth.Session) { + // CREATE EXPECTED MOVES + expectedMove1 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { // Default New Duty Location name is Fort Eisenhower + Model: models.Move{ + Status: models.MoveStatusAPPROVED, + Locator: "AA1234", + }, + }, + { + Model: models.MTOShipment{ + RequestedPickupDate: &requestedMoveDate1, + }, + }, + }, nil) + expectedMove2 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Locator: "TTZ123", + }, + }, + { + Model: models.MTOShipment{ + RequestedPickupDate: &requestedMoveDate2, + }, + }, + }, nil) + + factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), "06001", "AGFM") + + expectedShipment3 := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{ + Name: "Fort Punxsutawney", + Gbloc: "AGFM", + }, + }, + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusSubmitted, + }, + }, + { + Model: models.Address{ + PostalCode: "06001", + }, + Type: &factory.Addresses.PickupAddress, + }, + }, nil) + + hqOfficeUser = factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeHQ}) + hqSession := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: hqOfficeUser.User.Roles, + OfficeUserID: hqOfficeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + hqOfficeUserAGFM = factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{ + Name: "Scott AFB", + Gbloc: "AGFM", + }, + }, + }, []roles.RoleType{roles.RoleTypeHQ}) + hqSessionAGFM := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: hqOfficeUserAGFM.User.Roles, + OfficeUserID: hqOfficeUserAGFM.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + return expectedMove1, expectedMove2, expectedShipment3, hqSession, hqSessionAGFM + } + + orderFetcher := NewOrderFetcher(waf) + + suite.Run("Sort by locator code", func() { + expectedMove1, expectedMove2, expectedShipment3, hqSession, hqSessionAGFM := setupTestData() + + // Request as an HQ user with their default GBLOC, KKFA + params := services.ListOrderParams{Sort: models.StringPointer("locator"), Order: models.StringPointer("asc")} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&hqSession), hqOfficeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(2, len(moves)) + suite.Equal(expectedMove1.Locator, moves[0].Locator) + suite.Equal(expectedMove2.Locator, moves[1].Locator) + + // Expect the same results with a ViewAsGBLOC that equals the user's default GBLOC, KKFA + params = services.ListOrderParams{Sort: models.StringPointer("locator"), Order: models.StringPointer("asc"), ViewAsGBLOC: models.StringPointer("KKFA")} + moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&hqSession), hqOfficeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(2, len(moves)) + suite.Equal(expectedMove1.Locator, moves[0].Locator) + suite.Equal(expectedMove2.Locator, moves[1].Locator) + + // Expect the AGFM move when using the ViewAsGBLOC param set to AGFM + params = services.ListOrderParams{ViewAsGBLOC: models.StringPointer("AGFM")} + moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&hqSession), hqOfficeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(1, len(moves)) + suite.Equal(expectedShipment3.ID, moves[0].MTOShipments[0].ID) + + // Expect the same results without a ViewAsGBLOC for a user whose default GBLOC is AGFM + params = services.ListOrderParams{} + moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&hqSessionAGFM), hqOfficeUserAGFM.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(1, len(moves)) + suite.Equal(expectedShipment3.ID, moves[0].MTOShipments[0].ID) + }) +} + +func (suite *OrderServiceSuite) TestListOrdersForTOOWithPPMWithDeletedShipment() { + postalCode := "50309" + deletedAt := time.Now() + waf := entitlements.NewWeightAllotmentFetcher() + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusSUBMITTED, + }, + }, + }, nil) + ppmShipment := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + PostalCode: postalCode, + }, + Type: &factory.Addresses.PickupAddress, + }, + }, nil) + factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusSubmitted, + DeletedAt: &deletedAt, + }, + }, + { + Model: ppmShipment, + LinkOnly: true, + }, + }, nil) + + // Make a TOO user. + tooOfficeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: tooOfficeUser.User.Roles, + OfficeUserID: tooOfficeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + orderFetcher := NewOrderFetcher(waf) + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), tooOfficeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{Status: []string{string(models.MoveStatusSUBMITTED)}}) + suite.FatalNoError(err) + suite.Equal(0, moveCount) + suite.Len(moves, 0) +} + +func (suite *OrderServiceSuite) TestListOrdersForTOOWithPPMWithOneDeletedShipmentButOtherExists() { + postalCode := "50309" + deletedAt := time.Now() + waf := entitlements.NewWeightAllotmentFetcher() + move := factory.BuildMove(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + Status: models.MoveStatusAPPROVED, + }, + }, + }, nil) + // This shipment is created first, but later deleted + ppmShipment1 := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: models.PPMShipment{ + CreatedAt: time.Now(), + }, + }, + { + Model: models.Address{ + PostalCode: postalCode, + }, + Type: &factory.Addresses.PickupAddress, + }, + }, nil) + // This shipment is created after the first one, but not deleted + factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: models.PPMShipment{ + CreatedAt: time.Now().Add(time.Minute * time.Duration(1)), + }, + }, + { + Model: models.Address{ + PostalCode: postalCode, + }, + Type: &factory.Addresses.PickupAddress, + }, + }, nil) + factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusSubmitted, + DeletedAt: &deletedAt, + }, + }, + { + Model: ppmShipment1, + LinkOnly: true, + }, + }, nil) + + // Make a TOO user and the postal code to GBLOC link. + tooOfficeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: tooOfficeUser.User.Roles, + OfficeUserID: tooOfficeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + orderFetcher := NewOrderFetcher(waf) + moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), tooOfficeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) + suite.FatalNoError(err) + suite.Equal(1, moveCount) + suite.Len(moves, 1) +} + +func (suite *OrderServiceSuite) TestListAllOrderLocations() { + waf := entitlements.NewWeightAllotmentFetcher() + suite.Run("returns a list of all order locations in the current users queue", func() { + orderFetcher := NewOrderFetcher(waf) + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeServicesCounselor}) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + params := services.ListOrderParams{} + moves, err := orderFetcher.ListAllOrderLocations(suite.AppContextWithSessionForTest(&session), officeUser.ID, ¶ms) + + suite.FatalNoError(err) + suite.Equal(0, len(moves)) + }) +} + +func (suite *OrderServiceSuite) TestListAllOrderLocationsWithViewAsGBLOCParam() { + waf := entitlements.NewWeightAllotmentFetcher() + suite.Run("returns a list of all order locations in the current users queue", func() { + orderFetcher := NewOrderFetcher(waf) + officeUserFetcher := officeuserservice.NewOfficeUserFetcherPop() + movesContainOriginDutyLocation := func(moves models.Moves, keyword string) func() (success bool) { + return func() (success bool) { + for _, record := range moves { + if strings.Contains(record.Orders.OriginDutyLocation.Name, keyword) { + return true + } + } + return false + } + } + + // Create SC office user with a default transportation office in the AGFM GBLOC + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ { - Model: dutyLocationA, - LinkOnly: true, - Type: &factory.DutyLocations.NewDutyLocation, + Model: models.TransportationOffice{ + Name: "Fort Punxsutawney", + Gbloc: "AGFM", + }, }, + }, []roles.RoleType{roles.RoleTypeServicesCounselor}) + // Add a secondary GBLOC to the above office user, this should default to KKFA + factory.BuildAlternateTransportationOfficeAssignment(suite.DB(), []factory.Customization{ { - Model: closeoutOffice, + Model: models.OfficeUser{ + ID: officeUser.ID, + }, LinkOnly: true, - Type: &factory.TransportationOffices.CloseoutOffice, }, - }) - dutyLocationB := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + }, nil) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + // Create two default moves with shipment, should be in KKFA and have the status SUBMITTED + KKFAMove1 := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + KKFAMove2 := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + + // Create third move with the same origin duty location as one of the above + KKFAMove3 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ { Model: models.DutyLocation{ - Name: "B", + ID: KKFAMove2.Orders.OriginDutyLocation.ID, }, - }, - }, nil) - ppmShipmentB := factory.BuildPPMShipmentThatNeedsCloseout(suite.DB(), nil, []factory.Customization{ - { - Model: dutyLocationB, - LinkOnly: true, - Type: &factory.DutyLocations.NewDutyLocation, - }, - { - Model: closeoutOffice, + Type: &factory.DutyLocations.OriginDutyLocation, LinkOnly: true, - Type: &factory.TransportationOffices.CloseoutOffice, }, - }) + }, nil) - // Sort by destination duty location (ascending) - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ - NeedsPPMCloseout: models.BoolPointer(true), - Sort: models.StringPointer("destinationDutyLocation"), - Order: models.StringPointer("asc"), - }) + officeUser, _ = officeUserFetcher.FetchOfficeUserByIDWithTransportationOfficeAssignments(suite.AppContextForTest(), officeUser.ID) - suite.FatalNoError(err) - suite.Equal(2, len(moves)) - suite.Equal(ppmShipmentA.Shipment.MoveTaskOrder.Locator, moves[0].Locator) - suite.Equal(ppmShipmentB.Shipment.MoveTaskOrder.Locator, moves[1].Locator) + // Confirm office user has the desired transportation office assignments + suite.Equal("AGFM", officeUser.TransportationOffice.Gbloc) + if officeUser.TransportationOfficeAssignments[0].TransportationOffice.Gbloc == "AGFM" { + suite.Equal("AGFM", officeUser.TransportationOfficeAssignments[0].TransportationOffice.Gbloc) + suite.Equal(true, *officeUser.TransportationOfficeAssignments[0].PrimaryOffice) + suite.Equal("KKFA", officeUser.TransportationOfficeAssignments[1].TransportationOffice.Gbloc) + suite.Equal(false, *officeUser.TransportationOfficeAssignments[1].PrimaryOffice) + } else { + suite.Equal("KKFA", officeUser.TransportationOfficeAssignments[0].TransportationOffice.Gbloc) + suite.Equal(false, *officeUser.TransportationOfficeAssignments[0].PrimaryOffice) + suite.Equal("AGFM", officeUser.TransportationOfficeAssignments[1].TransportationOffice.Gbloc) + suite.Equal(true, *officeUser.TransportationOfficeAssignments[1].PrimaryOffice) + } - // Sort by destination duty location (descending) - moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ - NeedsPPMCloseout: models.BoolPointer(true), - Sort: models.StringPointer("destinationDutyLocation"), - Order: models.StringPointer("desc"), - }) + // Confirm the factory created moves have the desired GBLOCS, 3x KKFA, + suite.Equal("KKFA", *KKFAMove1.Orders.OriginDutyLocationGBLOC) + suite.Equal("KKFA", *KKFAMove2.Orders.OriginDutyLocationGBLOC) + suite.Equal("KKFA", *KKFAMove3.Orders.OriginDutyLocationGBLOC) + + // Fetch and check secondary GBLOC + KKFA := "KKFA" + params := services.ListOrderParams{ + ViewAsGBLOC: &KKFA, + } + KKFAmoves, err := orderFetcher.ListAllOrderLocations(suite.AppContextWithSessionForTest(&session), officeUser.ID, ¶ms) suite.FatalNoError(err) - suite.Equal(2, len(moves)) - suite.Equal(ppmShipmentB.Shipment.MoveTaskOrder.Locator, moves[0].Locator) - suite.Equal(ppmShipmentA.Shipment.MoveTaskOrder.Locator, moves[1].Locator) + // This value should be updated to 3 if ListAllOrderLocations is updated to return distinct locations + suite.Equal(3, len(KKFAmoves)) + + suite.Equal("KKFA", *KKFAmoves[0].Orders.OriginDutyLocationGBLOC) + suite.Equal("KKFA", *KKFAmoves[1].Orders.OriginDutyLocationGBLOC) + suite.Equal("KKFA", *KKFAmoves[2].Orders.OriginDutyLocationGBLOC) + + suite.Condition(movesContainOriginDutyLocation(KKFAmoves, KKFAMove1.Orders.OriginDutyLocation.Name), "Should contain first KKFA move's origin duty location") + suite.Condition(movesContainOriginDutyLocation(KKFAmoves, KKFAMove2.Orders.OriginDutyLocation.Name), "Should contain second KKFA move's origin duty location") + suite.Condition(movesContainOriginDutyLocation(KKFAmoves, KKFAMove3.Orders.OriginDutyLocation.Name), "Should contain third KKFA move's origin duty location") }) +} - suite.Run("Sort by PPM type (full or partial)", func() { - officeUser := setupTestData() - closeoutOffice := factory.BuildTransportationOffice(suite.DB(), []factory.Customization{ +func (suite *OrderServiceSuite) TestOriginDutyLocationFilter() { + var session auth.Session + waf := entitlements.NewWeightAllotmentFetcher() + var expectedMove models.Move + var officeUser models.OfficeUser + orderFetcher := NewOrderFetcher(waf) + setupTestData := func() (models.OfficeUser, models.Move, auth.Session) { + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + move := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + return officeUser, move, session + } + + suite.Run("Returns orders matching full originDutyLocation name filter", func() { + officeUser, expectedMove, session = setupTestData() + locationName := expectedMove.Orders.OriginDutyLocation.Name + expectedMoves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{OriginDutyLocation: strings.Split(locationName, " ")}) + suite.NoError(err) + suite.Equal(1, len(expectedMoves)) + suite.Equal(locationName, string(expectedMoves[0].Orders.OriginDutyLocation.Name)) + }) + + suite.Run("Returns orders matching partial originDutyLocation name filter", func() { + officeUser, expectedMove, session = setupTestData() + locationName := expectedMove.Orders.OriginDutyLocation.Name + //Split the location name and retrieve a substring (first string) for the search param + partialParamSearch := strings.Split(locationName, " ")[0] + expectedMoves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{OriginDutyLocation: strings.Split(partialParamSearch, " ")}) + suite.NoError(err) + suite.Equal(1, len(expectedMoves)) + suite.Equal(locationName, string(expectedMoves[0].Orders.OriginDutyLocation.Name)) + }) +} + +func (suite *OrderServiceSuite) TestListOrdersFilteredByCustomerName() { + waf := entitlements.NewWeightAllotmentFetcher() + + serviceMemberFirstName := "Margaret" + serviceMemberLastName := "Starlight" + edipi := "9999999998" + var officeUser models.OfficeUser + var session auth.Session + + requestedMoveDate1 := time.Date(testdatagen.GHCTestYear, 05, 20, 0, 0, 0, 0, time.UTC) + requestedMoveDate2 := time.Date(testdatagen.GHCTestYear, 07, 03, 0, 0, 0, 0, time.UTC) + + suite.PreloadData(func() { + factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ { - Model: models.TransportationOffice{Gbloc: "KKFA"}, + Model: models.Move{ + Status: models.MoveStatusAPPROVED, + Locator: "AA1235", + }, + }, + { + Model: models.MTOShipment{ + RequestedPickupDate: &requestedMoveDate1, + }, }, }, nil) - ppmShipmentPartial := factory.BuildPPMShipmentThatNeedsCloseout(suite.DB(), nil, []factory.Customization{ + factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ { Model: models.Move{ - PPMType: models.StringPointer("Partial"), + Locator: "TTZ125", }, }, { - Model: closeoutOffice, - LinkOnly: true, - Type: &factory.TransportationOffices.CloseoutOffice, + Model: models.ServiceMember{ + FirstName: &serviceMemberFirstName, + Edipi: &edipi, + }, }, - }) - ppmShipmentFull := factory.BuildPPMShipmentThatNeedsCloseout(suite.DB(), nil, []factory.Customization{ { - Model: models.Move{ - PPMType: models.StringPointer("FULL"), + Model: models.MTOShipment{ + RequestedPickupDate: &requestedMoveDate2, }, }, + }, nil) + factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ { - Model: closeoutOffice, - LinkOnly: true, - Type: &factory.TransportationOffices.CloseoutOffice, + Model: models.ServiceMember{ // Leo Zephyer + LastName: &serviceMemberLastName, + }, }, - }) + }, nil) + officeUser = factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + session = auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: officeUser.User.Roles, + OfficeUserID: officeUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + }) - // Sort by PPM type (ascending) - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ - NeedsPPMCloseout: models.BoolPointer(true), - Sort: models.StringPointer("ppmType"), - Order: models.StringPointer("asc"), - }) + orderFetcher := NewOrderFetcher(waf) - suite.FatalNoError(err) + suite.Run("list moves by customer name - full name (last, first)", func() { + // Search "Spacemen, Margaret" + params := services.ListOrderParams{CustomerName: models.StringPointer("Spacemen, Margaret"), Sort: models.StringPointer("customerName"), Order: models.StringPointer("asc")} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(1, len(moves)) + suite.Equal("Spacemen, Margaret", *moves[0].Orders.ServiceMember.LastName+", "+*moves[0].Orders.ServiceMember.FirstName) + }) + + suite.Run("list moves by customer name - full name (first last)", func() { + // Search "Margaret Spacemen" + params := services.ListOrderParams{CustomerName: models.StringPointer("Margaret Spacemen"), Sort: models.StringPointer("customerName"), Order: models.StringPointer("asc")} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(1, len(moves)) + suite.Equal("Spacemen, Margaret", *moves[0].Orders.ServiceMember.LastName+", "+*moves[0].Orders.ServiceMember.FirstName) + }) + + suite.Run("list moves by customer name - partial last (multiple)", func() { + // Search "space" + params := services.ListOrderParams{CustomerName: models.StringPointer("space"), Sort: models.StringPointer("customerName"), Order: models.StringPointer("asc")} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(2, len(moves)) + suite.Equal("Spacemen, Leo", *moves[0].Orders.ServiceMember.LastName+", "+*moves[0].Orders.ServiceMember.FirstName) + suite.Equal("Spacemen, Margaret", *moves[1].Orders.ServiceMember.LastName+", "+*moves[1].Orders.ServiceMember.FirstName) + }) + + suite.Run("list moves by customer name - partial last (single)", func() { + // Search "Light" + params := services.ListOrderParams{CustomerName: models.StringPointer("Light"), Sort: models.StringPointer("customerName"), Order: models.StringPointer("asc")} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(1, len(moves)) + suite.Equal("Starlight, Leo", *moves[0].Orders.ServiceMember.LastName+", "+*moves[0].Orders.ServiceMember.FirstName) + }) + + suite.Run("list moves by customer name - partial first", func() { + // Search "leo" + params := services.ListOrderParams{CustomerName: models.StringPointer("leo"), Sort: models.StringPointer("customerName"), Order: models.StringPointer("asc")} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) suite.Equal(2, len(moves)) - suite.Equal(ppmShipmentFull.Shipment.MoveTaskOrder.Locator, moves[0].Locator) - suite.Equal(ppmShipmentPartial.Shipment.MoveTaskOrder.Locator, moves[1].Locator) - - // Sort by PPM type (descending) - moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ - NeedsPPMCloseout: models.BoolPointer(true), - Sort: models.StringPointer("ppmType"), - Order: models.StringPointer("desc"), - }) + suite.Equal("Spacemen, Leo", *moves[0].Orders.ServiceMember.LastName+", "+*moves[0].Orders.ServiceMember.FirstName) + suite.Equal("Starlight, Leo", *moves[1].Orders.ServiceMember.LastName+", "+*moves[1].Orders.ServiceMember.FirstName) + }) - suite.FatalNoError(err) + suite.Run("list moves by customer name - partial matching within first or last", func() { + // Search "ar" + params := services.ListOrderParams{CustomerName: models.StringPointer("ar"), Sort: models.StringPointer("customerName"), Order: models.StringPointer("asc")} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) suite.Equal(2, len(moves)) - suite.Equal(ppmShipmentPartial.Shipment.MoveTaskOrder.Locator, moves[0].Locator) - suite.Equal(ppmShipmentFull.Shipment.MoveTaskOrder.Locator, moves[1].Locator) + suite.Equal("Spacemen, Margaret", *moves[0].Orders.ServiceMember.LastName+", "+*moves[0].Orders.ServiceMember.FirstName) + suite.Equal("Starlight, Leo", *moves[1].Orders.ServiceMember.LastName+", "+*moves[1].Orders.ServiceMember.FirstName) }) - suite.Run("Sort by PPM status", func() { - officeUser := setupTestData() - closeoutOffice := factory.BuildTransportationOffice(suite.DB(), []factory.Customization{ - { - Model: models.TransportationOffice{Gbloc: "KKFA"}, - }, - }, nil) - ppmShipmentNeedsCloseout := getPPMShipmentWithCloseoutOfficeNeedsCloseout(suite, closeoutOffice) - // Sort by PPM type (ascending) - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ - NeedsPPMCloseout: models.BoolPointer(true), - Sort: models.StringPointer("ppmStatus"), - Order: models.StringPointer("asc"), - }) + suite.Run("list moves by customer name - empty", func() { + // Search "johnny" + params := services.ListOrderParams{CustomerName: models.StringPointer("johnny"), Sort: models.StringPointer("customerName"), Order: models.StringPointer("asc")} + moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) + suite.NoError(err) + suite.Equal(0, len(moves)) + }) +} - suite.FatalNoError(err) - suite.Equal(1, len(moves)) - suite.Equal(ppmShipmentNeedsCloseout.Status, moves[0].MTOShipments[0].PPMShipment.Status) +func (suite *OrderServiceSuite) TestListDestinationRequestsOrders() { + army := models.AffiliationARMY + airForce := models.AffiliationAIRFORCE + spaceForce := models.AffiliationSPACEFORCE + usmc := models.AffiliationMARINES - // Sort by PPM type (descending) - moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{ - NeedsPPMCloseout: models.BoolPointer(true), - Sort: models.StringPointer("ppmStatus"), - Order: models.StringPointer("desc"), - }) + setupTestData := func(officeUserGBLOC string) (models.OfficeUser, auth.Session) { - suite.FatalNoError(err) - suite.Equal(1, len(moves)) - suite.Equal(ppmShipmentNeedsCloseout.Status, moves[0].MTOShipments[0].PPMShipment.Status) - }) -} + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + { + Model: models.TransportationOffice{ + Gbloc: officeUserGBLOC, + }, + }, + }, []roles.RoleType{roles.RoleTypeTOO}) -func (suite *OrderServiceSuite) TestListOrdersNeedingServicesCounselingWithGBLOCSortFilter() { - waf := entitlements.NewWeightAllotmentFetcher() - suite.Run("Filter by origin GBLOC", func() { + factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), "99501", officeUser.TransportationOffice.Gbloc) - // TESTCASE SCENARIO - // Under test: OrderFetcher.ListOrders function - // Mocked: None - // Set up: We create 2 moves with different GBLOCs, KKFA and ZANY. Both moves require service counseling - // We create an office user with the GBLOC KKFA - // Then we request a list of moves sorted by GBLOC, ascending for service counseling - // Expected outcome: - // We expect only the move that matches the counselors GBLOC - aka the KKFA move. + fetcher := &mocks.OfficeUserGblocFetcher{} + fetcher.On("FetchGblocForOfficeUser", + mock.AnythingOfType("*appcontext.appContext"), + officeUser.ID, + ).Return(officeUserGBLOC, nil) - // Create a services counselor (default GBLOC is KKFA) - officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeServicesCounselor}) session := auth.Session{ ApplicationName: auth.OfficeApp, Roles: officeUser.User.Roles, @@ -1626,684 +3055,461 @@ func (suite *OrderServiceSuite) TestListOrdersNeedingServicesCounselingWithGBLOC AccessToken: "fakeAccessToken", } - // Create a move with Origin KKFA, needs service couseling - kkfaMove := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + return officeUser, session + } + + buildMoveKKFA := func() (models.Move, models.MTOShipment) { + postalCode := "90210" + factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), "90210", "KKFA") + + // setting up two moves, each with requested destination SIT service items + destinationAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{PostalCode: postalCode}, + }, + }, nil) + + move := factory.BuildAvailableToPrimeMove(suite.DB(), []factory.Customization{ { Model: models.Move{ - Status: models.MoveStatusNeedsServiceCounseling, + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), }, + }}, nil) + + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusApproved, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: destinationAddress, + LinkOnly: true, }, }, nil) - // Create data for a second Origin ZANY - dutyLocationAddress2 := factory.BuildAddress(suite.DB(), []factory.Customization{ + + return move, shipment + } + + buildMoveZone2AK := func(branch models.ServiceMemberAffiliation) (models.Move, models.MTOShipment) { + // Create a USAF move in Alaska Zone II + // this is a hard coded uuid that is a us_post_region_cities_id within AK Zone II + zone2UUID, err := uuid.FromString("66768964-e0de-41f3-b9be-7ef32e4ae2b4") + suite.FatalNoError(err) + destinationAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ { Model: models.Address{ - StreetAddress1: "Anchor 1212", - City: "Fort Eisenhower", - State: "GA", - PostalCode: "89898", + City: "Anchorage", + State: "AK", + PostalCode: "99501", + UsPostRegionCityID: &zone2UUID, }, }, }, nil) - factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), dutyLocationAddress2.PostalCode, "ZANY") - originDutyLocation2 := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + // setting up two moves, each with requested destination SIT service items + move := factory.BuildAvailableToPrimeMove(suite.DB(), []factory.Customization{ { - Model: models.DutyLocation{ - Name: "Fort Sam Snap", + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), }, }, { - Model: dutyLocationAddress2, - LinkOnly: true, + Model: models.ServiceMember{ + Affiliation: &branch, + }, }, }, nil) - // Create a second move from the ZANY gbloc - factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ { - Model: models.Move{ - Status: models.MoveStatusNeedsServiceCounseling, - Locator: "ZZ1234", + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusApproved, }, }, { - Model: originDutyLocation2, + Model: move, + LinkOnly: true, + }, + { + Model: destinationAddress, LinkOnly: true, - Type: &factory.DutyLocations.OriginDutyLocation, }, }, nil) - // Setup and run the function under test requesting status NEEDS SERVICE COUNSELING - orderFetcher := NewOrderFetcher(waf) - statuses := []string{"NEEDS SERVICE COUNSELING"} - // Sort by origin GBLOC, filter by status - params := services.ListOrderParams{Sort: models.StringPointer("originGBLOC"), Order: models.StringPointer("asc"), Status: statuses} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeServicesCounselor, ¶ms) - - // Expect only LKNQ move to be returned - suite.NoError(err) - suite.Equal(1, len(moves)) - suite.Equal(kkfaMove.ID, moves[0].ID) - }) -} -func (suite *OrderServiceSuite) TestListOrdersForTOOWithNTSRelease() { - // Make an NTS-Release shipment (and a move). Should not have a pickup address. - factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ - { - Model: models.MTOShipment{ - ShipmentType: models.MTOShipmentTypeHHGOutOfNTS, - }, - }, - }, nil) - waf := entitlements.NewWeightAllotmentFetcher() - // Make a TOO user and the postal code to GBLOC link. - tooOfficeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) - session := auth.Session{ - ApplicationName: auth.OfficeApp, - Roles: tooOfficeUser.User.Roles, - OfficeUserID: tooOfficeUser.ID, - IDToken: "fake_token", - AccessToken: "fakeAccessToken", + return move, shipment } - orderFetcher := NewOrderFetcher(waf) - moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), tooOfficeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) - - suite.FatalNoError(err) - suite.Equal(1, moveCount) - suite.Len(moves, 1) -} - -func (suite *OrderServiceSuite) TestListOrdersForTOOWithPPM() { - postalCode := "50309" - partialPPMType := models.MovePPMTypePARTIAL - waf := entitlements.NewWeightAllotmentFetcher() - ppmShipment := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ - { - Model: models.Order{ - ID: uuid.UUID{uuid.V4}, - }, - }, - { - Model: models.Move{ - Status: models.MoveStatusAPPROVED, - PPMType: &partialPPMType, - }, - }, - { - Model: models.Address{ - PostalCode: postalCode, + buildMoveZone4AK := func(branch models.ServiceMemberAffiliation) (models.Move, models.MTOShipment) { + // Create a USAF move in Alaska Zone II + // this is a hard coded uuid that is a us_post_region_cities_id within AK Zone II + zone4UUID, err := uuid.FromString("78a6f230-9a3a-46ed-aa48-2e3decfe70ff") + suite.FatalNoError(err) + destinationAddress := factory.BuildAddress(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + City: "Anchorage", + State: "AK", + PostalCode: "99501", + UsPostRegionCityID: &zone4UUID, + }, }, - Type: &factory.Addresses.PickupAddress, - }, - }, nil) - // Make a TOO user. - tooOfficeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) - session := auth.Session{ - ApplicationName: auth.OfficeApp, - Roles: tooOfficeUser.User.Roles, - OfficeUserID: tooOfficeUser.ID, - IDToken: "fake_token", - AccessToken: "fakeAccessToken", - } - - // GBLOC for the below doesn't really matter, it just means the query for the moves passes the inner join in ListOrders - factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), ppmShipment.PickupAddress.PostalCode, tooOfficeUser.TransportationOffice.Gbloc) - - orderFetcher := NewOrderFetcher(waf) - moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), tooOfficeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) - suite.FatalNoError(err) - suite.Equal(1, moveCount) - suite.Len(moves, 1) -} - -func (suite *OrderServiceSuite) TestListOrdersWithViewAsGBLOCParam() { - var hqOfficeUser models.OfficeUser - var hqOfficeUserAGFM models.OfficeUser - waf := entitlements.NewWeightAllotmentFetcher() - requestedMoveDate1 := time.Date(testdatagen.GHCTestYear, 02, 20, 0, 0, 0, 0, time.UTC) - requestedMoveDate2 := time.Date(testdatagen.GHCTestYear, 03, 03, 0, 0, 0, 0, time.UTC) - - setupTestData := func() (models.Move, models.Move, models.MTOShipment, auth.Session, auth.Session) { - // CREATE EXPECTED MOVES - expectedMove1 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ - { // Default New Duty Location name is Fort Eisenhower + }, nil) + // setting up two moves, each with requested destination SIT service items + move := factory.BuildAvailableToPrimeMove(suite.DB(), []factory.Customization{ + { Model: models.Move{ - Status: models.MoveStatusAPPROVED, - Locator: "AA1234", + Status: models.MoveStatusAPPROVALSREQUESTED, + Show: models.BoolPointer(true), }, }, { - Model: models.MTOShipment{ - RequestedPickupDate: &requestedMoveDate1, + Model: models.ServiceMember{ + Affiliation: &branch, }, }, }, nil) - expectedMove2 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ { - Model: models.Move{ - Locator: "TTZ123", + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusApproved, }, }, { - Model: models.MTOShipment{ - RequestedPickupDate: &requestedMoveDate2, - }, + Model: move, + LinkOnly: true, + }, + { + Model: destinationAddress, + LinkOnly: true, }, }, nil) - factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), "06001", "AGFM") + return move, shipment + } - expectedShipment3 := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + waf := entitlements.NewWeightAllotmentFetcher() + orderFetcher := NewOrderFetcher(waf) + + suite.Run("returns moves for KKFA GBLOC when destination address is in KKFA GBLOC", func() { + officeUser, session := setupTestData("KKFA") + // setting up two moves, each with requested destination SIT service items + move, shipment := buildMoveKKFA() + + // destination service item in SUBMITTED status + factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ { - Model: models.TransportationOffice{ - Name: "Fort Punxsutawney", - Gbloc: "AGFM", + Model: models.ReService{ + Code: models.ReServiceCodeDDFSIT, }, }, { - Model: models.MTOShipment{ - Status: models.MTOShipmentStatusSubmitted, - }, + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, }, { - Model: models.Address{ - PostalCode: "06001", + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusSubmitted, }, - Type: &factory.Addresses.PickupAddress, }, }, nil) - hqOfficeUser = factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeHQ}) - hqSession := auth.Session{ - ApplicationName: auth.OfficeApp, - Roles: hqOfficeUser.User.Roles, - OfficeUserID: hqOfficeUser.ID, - IDToken: "fake_token", - AccessToken: "fakeAccessToken", - } + move2, shipment2 := buildMoveKKFA() - hqOfficeUserAGFM = factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + // destination shuttle + factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ { - Model: models.TransportationOffice{ - Name: "Scott AFB", - Gbloc: "AGFM", + Model: models.ReService{ + Code: models.ReServiceCodeDDSHUT, }, }, - }, []roles.RoleType{roles.RoleTypeHQ}) - hqSessionAGFM := auth.Session{ - ApplicationName: auth.OfficeApp, - Roles: hqOfficeUserAGFM.User.Roles, - OfficeUserID: hqOfficeUserAGFM.ID, - IDToken: "fake_token", - AccessToken: "fakeAccessToken", - } - - return expectedMove1, expectedMove2, expectedShipment3, hqSession, hqSessionAGFM - } - - orderFetcher := NewOrderFetcher(waf) - - suite.Run("Sort by locator code", func() { - expectedMove1, expectedMove2, expectedShipment3, hqSession, hqSessionAGFM := setupTestData() - - // Request as an HQ user with their default GBLOC, KKFA - params := services.ListOrderParams{Sort: models.StringPointer("locator"), Order: models.StringPointer("asc")} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&hqSession), hqOfficeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(2, len(moves)) - suite.Equal(expectedMove1.Locator, moves[0].Locator) - suite.Equal(expectedMove2.Locator, moves[1].Locator) - - // Expect the same results with a ViewAsGBLOC that equals the user's default GBLOC, KKFA - params = services.ListOrderParams{Sort: models.StringPointer("locator"), Order: models.StringPointer("asc"), ViewAsGBLOC: models.StringPointer("KKFA")} - moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&hqSession), hqOfficeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(2, len(moves)) - suite.Equal(expectedMove1.Locator, moves[0].Locator) - suite.Equal(expectedMove2.Locator, moves[1].Locator) - - // Expect the AGFM move when using the ViewAsGBLOC param set to AGFM - params = services.ListOrderParams{ViewAsGBLOC: models.StringPointer("AGFM")} - moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&hqSession), hqOfficeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(1, len(moves)) - suite.Equal(expectedShipment3.ID, moves[0].MTOShipments[0].ID) - - // Expect the same results without a ViewAsGBLOC for a user whose default GBLOC is AGFM - params = services.ListOrderParams{} - moves, _, err = orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&hqSessionAGFM), hqOfficeUserAGFM.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(1, len(moves)) - suite.Equal(expectedShipment3.ID, moves[0].MTOShipments[0].ID) - }) -} - -func (suite *OrderServiceSuite) TestListOrdersForTOOWithPPMWithDeletedShipment() { - postalCode := "50309" - deletedAt := time.Now() - waf := entitlements.NewWeightAllotmentFetcher() - move := factory.BuildMove(suite.DB(), []factory.Customization{ - { - Model: models.Move{ - Status: models.MoveStatusSUBMITTED, + { + Model: move2, + LinkOnly: true, }, - }, - }, nil) - ppmShipment := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ - { - Model: models.Address{ - PostalCode: postalCode, + { + Model: shipment2, + LinkOnly: true, }, - Type: &factory.Addresses.PickupAddress, - }, - }, nil) - factory.BuildMTOShipment(suite.DB(), []factory.Customization{ - { - Model: move, - LinkOnly: true, - }, - { - Model: models.MTOShipment{ - Status: models.MTOShipmentStatusSubmitted, - DeletedAt: &deletedAt, + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusSubmitted, + }, }, - }, - { - Model: ppmShipment, - LinkOnly: true, - }, - }, nil) - - // Make a TOO user. - tooOfficeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) - session := auth.Session{ - ApplicationName: auth.OfficeApp, - Roles: tooOfficeUser.User.Roles, - OfficeUserID: tooOfficeUser.ID, - IDToken: "fake_token", - AccessToken: "fakeAccessToken", - } - - orderFetcher := NewOrderFetcher(waf) - moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), tooOfficeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{Status: []string{string(models.MoveStatusSUBMITTED)}}) - suite.FatalNoError(err) - suite.Equal(0, moveCount) - suite.Len(moves, 0) -} + }, nil) -func (suite *OrderServiceSuite) TestListOrdersForTOOWithPPMWithOneDeletedShipmentButOtherExists() { - postalCode := "50309" - deletedAt := time.Now() - waf := entitlements.NewWeightAllotmentFetcher() - move := factory.BuildMove(suite.DB(), []factory.Customization{ - { - Model: models.Move{ - Status: models.MoveStatusAPPROVED, + move3, shipment3 := buildMoveKKFA() + factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeMS, + }, }, - }, - }, nil) - // This shipment is created first, but later deleted - ppmShipment1 := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ - { - Model: move, - LinkOnly: true, - }, - { - Model: models.PPMShipment{ - CreatedAt: time.Now(), + { + Model: move3, + LinkOnly: true, }, - }, - { - Model: models.Address{ - PostalCode: postalCode, + { + Model: shipment3, + LinkOnly: true, }, - Type: &factory.Addresses.PickupAddress, - }, - }, nil) - // This shipment is created after the first one, but not deleted - factory.BuildPPMShipment(suite.DB(), []factory.Customization{ - { - Model: move, - LinkOnly: true, - }, - { - Model: models.PPMShipment{ - CreatedAt: time.Now().Add(time.Minute * time.Duration(1)), + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + }, }, - }, - { - Model: models.Address{ - PostalCode: postalCode, + }, nil) + factory.BuildShipmentAddressUpdate(suite.DB(), []factory.Customization{ + { + Model: shipment3, + LinkOnly: true, }, - Type: &factory.Addresses.PickupAddress, - }, - }, nil) - factory.BuildMTOShipment(suite.DB(), []factory.Customization{ - { - Model: move, - LinkOnly: true, - }, - { - Model: models.MTOShipment{ - Status: models.MTOShipmentStatusSubmitted, - DeletedAt: &deletedAt, + { + Model: move3, + LinkOnly: true, }, - }, - { - Model: ppmShipment1, - LinkOnly: true, - }, - }, nil) - - // Make a TOO user and the postal code to GBLOC link. - tooOfficeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) - session := auth.Session{ - ApplicationName: auth.OfficeApp, - Roles: tooOfficeUser.User.Roles, - OfficeUserID: tooOfficeUser.ID, - IDToken: "fake_token", - AccessToken: "fakeAccessToken", - } + }, []factory.Trait{factory.GetTraitShipmentAddressUpdateRequested}) - orderFetcher := NewOrderFetcher(waf) - moves, moveCount, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), tooOfficeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}) - suite.FatalNoError(err) - suite.Equal(1, moveCount) - suite.Len(moves, 1) -} + move4, shipment4 := buildMoveKKFA() + // build the destination SIT service items and update their status to SUBMITTED + oneMonthLater := time.Now().AddDate(0, 1, 0) + factory.BuildDestSITServiceItems(suite.DB(), move4, shipment4, &oneMonthLater, nil) -func (suite *OrderServiceSuite) TestListAllOrderLocations() { - waf := entitlements.NewWeightAllotmentFetcher() - suite.Run("returns a list of all order locations in the current users queue", func() { - orderFetcher := NewOrderFetcher(waf) - officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeServicesCounselor}) - session := auth.Session{ - ApplicationName: auth.OfficeApp, - Roles: officeUser.User.Roles, - OfficeUserID: officeUser.ID, - IDToken: "fake_token", - AccessToken: "fakeAccessToken", - } + // build the SIT extension update + factory.BuildSITDurationUpdate(suite.DB(), []factory.Customization{ + { + Model: move4, + LinkOnly: true, + }, + { + Model: shipment4, + LinkOnly: true, + }, + { + Model: models.SITDurationUpdate{ + Status: models.SITExtensionStatusPending, + ContractorRemarks: models.StringPointer("gimme some more plz"), + }, + }, + }, nil) - params := services.ListOrderParams{} - moves, err := orderFetcher.ListAllOrderLocations(suite.AppContextWithSessionForTest(&session), officeUser.ID, ¶ms) + moves, moveCount, err := orderFetcher.ListDestinationRequestsOrders( + suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}, + ) suite.FatalNoError(err) - suite.Equal(0, len(moves)) + suite.Equal(4, moveCount) + suite.Len(moves, 4) }) -} -func (suite *OrderServiceSuite) TestListOrdersFilteredByCustomerName() { - serviceMemberFirstName := "Margaret" - serviceMemberLastName := "Starlight" - edipi := "9999999998" - var officeUser models.OfficeUser - var session auth.Session - waf := entitlements.NewWeightAllotmentFetcher() - requestedMoveDate1 := time.Date(testdatagen.GHCTestYear, 05, 20, 0, 0, 0, 0, time.UTC) - requestedMoveDate2 := time.Date(testdatagen.GHCTestYear, 07, 03, 0, 0, 0, 0, time.UTC) + suite.Run("returns moves for MBFL GBLOC including USAF/SF in Alaska Zone II", func() { + officeUser, session := setupTestData("MBFL") - setupData := func() { - factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + // setting up two moves, each with requested destination SIT service items + // a move associated with an air force customer containing AK Zone II shipment + move, shipment := buildMoveZone2AK(airForce) + + // destination service item in SUBMITTED status + factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ { - Model: models.Move{ - Status: models.MoveStatusAPPROVED, - Locator: "AA1235", + Model: models.ReService{ + Code: models.ReServiceCodeDDFSIT, }, }, { - Model: models.MTOShipment{ - RequestedPickupDate: &requestedMoveDate1, + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusSubmitted, }, }, }, nil) - factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + + // Create a move outside Alaska Zone II (Zone IV in this case) + move2, shipment2 := buildMoveZone4AK(spaceForce) + + // destination service item in SUBMITTED status + factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ { - Model: models.Move{ - Locator: "TTZ125", + Model: models.ReService{ + Code: models.ReServiceCodeDDFSIT, }, }, { - Model: models.ServiceMember{ - FirstName: &serviceMemberFirstName, - Edipi: &edipi, - }, + Model: move2, + LinkOnly: true, }, { - Model: models.MTOShipment{ - RequestedPickupDate: &requestedMoveDate2, - }, + Model: shipment2, + LinkOnly: true, }, - }, nil) - factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ { - Model: models.ServiceMember{ // Leo Zephyer - LastName: &serviceMemberLastName, + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusSubmitted, }, }, }, nil) - officeUser = factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) - session = auth.Session{ - ApplicationName: auth.OfficeApp, - Roles: officeUser.User.Roles, - OfficeUserID: officeUser.ID, - IDToken: "fake_token", - AccessToken: "fakeAccessToken", - } - } - orderFetcher := NewOrderFetcher(waf) + params := services.ListOrderParams{Status: []string{string(models.MoveStatusAPPROVALSREQUESTED)}} + moves, moveCount, err := orderFetcher.ListDestinationRequestsOrders( + suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms, + ) - suite.Run("list moves by customer name - full name (last, first)", func() { - setupData() - // Search "Spacemen, Margaret" - params := services.ListOrderParams{CustomerName: models.StringPointer("Spacemen, Margaret"), Sort: models.StringPointer("customerName"), Order: models.StringPointer("asc")} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(1, len(moves)) - suite.Equal("Spacemen, Margaret", *moves[0].Orders.ServiceMember.LastName+", "+*moves[0].Orders.ServiceMember.FirstName) + // we should get both moves back because one is in Zone II & the other is within the postal code GBLOC + suite.FatalNoError(err) + suite.Equal(2, moveCount) + suite.Len(moves, 2) }) - suite.Run("list moves by customer name - full name (first last)", func() { - setupData() - // Search "Margaret Spacemen" - params := services.ListOrderParams{CustomerName: models.StringPointer("Margaret Spacemen"), Sort: models.StringPointer("customerName"), Order: models.StringPointer("asc")} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(1, len(moves)) - suite.Equal("Spacemen, Margaret", *moves[0].Orders.ServiceMember.LastName+", "+*moves[0].Orders.ServiceMember.FirstName) - }) + suite.Run("returns moves for JEAT GBLOC excluding USAF/SF in Alaska Zone II", func() { + officeUser, session := setupTestData("JEAT") - suite.Run("list moves by customer name - partial last (multiple)", func() { - setupData() - // Search "space" - params := services.ListOrderParams{CustomerName: models.StringPointer("space"), Sort: models.StringPointer("customerName"), Order: models.StringPointer("asc")} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(2, len(moves)) - suite.Equal("Spacemen, Leo", *moves[0].Orders.ServiceMember.LastName+", "+*moves[0].Orders.ServiceMember.FirstName) - suite.Equal("Spacemen, Margaret", *moves[1].Orders.ServiceMember.LastName+", "+*moves[1].Orders.ServiceMember.FirstName) - }) + // Create a move in Zone II, but not an air force or space force service member + move, shipment := buildMoveZone4AK(army) - suite.Run("list moves by customer name - partial last (single)", func() { - setupData() - // Search "Light" - params := services.ListOrderParams{CustomerName: models.StringPointer("Light"), Sort: models.StringPointer("customerName"), Order: models.StringPointer("asc")} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(1, len(moves)) - suite.Equal("Starlight, Leo", *moves[0].Orders.ServiceMember.LastName+", "+*moves[0].Orders.ServiceMember.FirstName) - }) + // destination service item in SUBMITTED status + factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeDDFSIT, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusSubmitted, + }, + }, + }, nil) - suite.Run("list moves by customer name - partial first", func() { - setupData() - // Search "leo" - params := services.ListOrderParams{CustomerName: models.StringPointer("leo"), Sort: models.StringPointer("customerName"), Order: models.StringPointer("asc")} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(2, len(moves)) - suite.Equal("Spacemen, Leo", *moves[0].Orders.ServiceMember.LastName+", "+*moves[0].Orders.ServiceMember.FirstName) - suite.Equal("Starlight, Leo", *moves[1].Orders.ServiceMember.LastName+", "+*moves[1].Orders.ServiceMember.FirstName) - }) + moves, moveCount, err := orderFetcher.ListDestinationRequestsOrders( + suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}, + ) - suite.Run("list moves by customer name - partial matching within first or last", func() { - setupData() - // Search "ar" - params := services.ListOrderParams{CustomerName: models.StringPointer("ar"), Sort: models.StringPointer("customerName"), Order: models.StringPointer("asc")} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(2, len(moves)) - suite.Equal("Spacemen, Margaret", *moves[0].Orders.ServiceMember.LastName+", "+*moves[0].Orders.ServiceMember.FirstName) - suite.Equal("Starlight, Leo", *moves[1].Orders.ServiceMember.LastName+", "+*moves[1].Orders.ServiceMember.FirstName) + suite.FatalNoError(err) + suite.Equal(1, moveCount) + suite.Len(moves, 1) }) - suite.Run("list moves by customer name - empty", func() { - setupData() - // Search "johnny" - params := services.ListOrderParams{CustomerName: models.StringPointer("johnny"), Sort: models.StringPointer("customerName"), Order: models.StringPointer("asc")} - moves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, ¶ms) - suite.NoError(err) - suite.Equal(0, len(moves)) - }) -} + suite.Run("returns moves for USMC GBLOC when moves belong to USMC servicemembers", func() { + officeUser, session := setupTestData("USMC") -func (suite *OrderServiceSuite) TestListAllOrderLocationsWithViewAsGBLOCParam() { - waf := entitlements.NewWeightAllotmentFetcher() - suite.Run("returns a list of all order locations in the current users queue", func() { - orderFetcher := NewOrderFetcher(waf) - officeUserFetcher := officeuserservice.NewOfficeUserFetcherPop() - movesContainOriginDutyLocation := func(moves models.Moves, keyword string) func() (success bool) { - return func() (success bool) { - for _, record := range moves { - if strings.Contains(record.Orders.OriginDutyLocation.Name, keyword) { - return true - } - } - return false - } - } + // setting up two moves, each with requested destination SIT service items + // both will be USMC moves, one in Zone II AK and the other not + move, shipment := buildMoveZone2AK(usmc) - // Create SC office user with a default transportation office in the AGFM GBLOC - officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{ + // destination service item in SUBMITTED status + factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ { - Model: models.TransportationOffice{ - Name: "Fort Punxsutawney", - Gbloc: "AGFM", + Model: models.ReService{ + Code: models.ReServiceCodeDDFSIT, }, }, - }, []roles.RoleType{roles.RoleTypeServicesCounselor}) - // Add a secondary GBLOC to the above office user, this should default to KKFA - factory.BuildAlternateTransportationOfficeAssignment(suite.DB(), []factory.Customization{ { - Model: models.OfficeUser{ - ID: officeUser.ID, - }, + Model: move, LinkOnly: true, }, + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusSubmitted, + }, + }, }, nil) - session := auth.Session{ - ApplicationName: auth.OfficeApp, - Roles: officeUser.User.Roles, - OfficeUserID: officeUser.ID, - IDToken: "fake_token", - AccessToken: "fakeAccessToken", - } - // Create two default moves with shipment, should be in KKFA and have the status SUBMITTED - KKFAMove1 := factory.BuildMoveWithShipment(suite.DB(), nil, nil) - KKFAMove2 := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + // this one won't be in Zone II + move2, shipment2 := buildMoveZone4AK(usmc) - // Create third move with the same origin duty location as one of the above - KKFAMove3 := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + // destination shuttle + factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ { - Model: models.DutyLocation{ - ID: KKFAMove2.Orders.OriginDutyLocation.ID, + Model: models.ReService{ + Code: models.ReServiceCodeDDSHUT, }, - Type: &factory.DutyLocations.OriginDutyLocation, + }, + { + Model: move2, + LinkOnly: true, + }, + { + Model: shipment2, LinkOnly: true, }, + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusSubmitted, + }, + }, }, nil) - officeUser, _ = officeUserFetcher.FetchOfficeUserByIDWithTransportationOfficeAssignments(suite.AppContextForTest(), officeUser.ID) - - // Confirm office user has the desired transportation office assignments - suite.Equal("AGFM", officeUser.TransportationOffice.Gbloc) - if officeUser.TransportationOfficeAssignments[0].TransportationOffice.Gbloc == "AGFM" { - suite.Equal("AGFM", officeUser.TransportationOfficeAssignments[0].TransportationOffice.Gbloc) - suite.Equal(true, *officeUser.TransportationOfficeAssignments[0].PrimaryOffice) - suite.Equal("KKFA", officeUser.TransportationOfficeAssignments[1].TransportationOffice.Gbloc) - suite.Equal(false, *officeUser.TransportationOfficeAssignments[1].PrimaryOffice) - } else { - suite.Equal("KKFA", officeUser.TransportationOfficeAssignments[0].TransportationOffice.Gbloc) - suite.Equal(false, *officeUser.TransportationOfficeAssignments[0].PrimaryOffice) - suite.Equal("AGFM", officeUser.TransportationOfficeAssignments[1].TransportationOffice.Gbloc) - suite.Equal(true, *officeUser.TransportationOfficeAssignments[1].PrimaryOffice) - } - - // Confirm the factory created moves have the desired GBLOCS, 3x KKFA, - suite.Equal("KKFA", *KKFAMove1.Orders.OriginDutyLocationGBLOC) - suite.Equal("KKFA", *KKFAMove2.Orders.OriginDutyLocationGBLOC) - suite.Equal("KKFA", *KKFAMove3.Orders.OriginDutyLocationGBLOC) + move3, shipment3 := buildMoveZone4AK(usmc) + // we need to create a service item and attach it to the move/shipment + // else the query will exclude the move since it doesn't use LEFT JOINs + factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeMS, + }, + }, + { + Model: move3, + LinkOnly: true, + }, + { + Model: shipment3, + LinkOnly: true, + }, + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + }, + }, + }, nil) + factory.BuildShipmentAddressUpdate(suite.DB(), []factory.Customization{ + { + Model: shipment3, + LinkOnly: true, + }, + { + Model: move3, + LinkOnly: true, + }, + }, []factory.Trait{factory.GetTraitShipmentAddressUpdateRequested}) - // Fetch and check secondary GBLOC - KKFA := "KKFA" - params := services.ListOrderParams{ - ViewAsGBLOC: &KKFA, - } - KKFAmoves, err := orderFetcher.ListAllOrderLocations(suite.AppContextWithSessionForTest(&session), officeUser.ID, ¶ms) + moves, moveCount, err := orderFetcher.ListDestinationRequestsOrders( + suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{}, + ) + // we should get three moves back since they're USMC moves and zone doesn't matter suite.FatalNoError(err) - // This value should be updated to 3 if ListAllOrderLocations is updated to return distinct locations - suite.Equal(3, len(KKFAmoves)) - - suite.Equal("KKFA", *KKFAmoves[0].Orders.OriginDutyLocationGBLOC) - suite.Equal("KKFA", *KKFAmoves[1].Orders.OriginDutyLocationGBLOC) - suite.Equal("KKFA", *KKFAmoves[2].Orders.OriginDutyLocationGBLOC) - - suite.Condition(movesContainOriginDutyLocation(KKFAmoves, KKFAMove1.Orders.OriginDutyLocation.Name), "Should contain first KKFA move's origin duty location") - suite.Condition(movesContainOriginDutyLocation(KKFAmoves, KKFAMove2.Orders.OriginDutyLocation.Name), "Should contain second KKFA move's origin duty location") - suite.Condition(movesContainOriginDutyLocation(KKFAmoves, KKFAMove3.Orders.OriginDutyLocation.Name), "Should contain third KKFA move's origin duty location") - }) -} - -func (suite *OrderServiceSuite) TestOriginDutyLocationFilter() { - var session auth.Session - waf := entitlements.NewWeightAllotmentFetcher() - var expectedMove models.Move - var officeUser models.OfficeUser - orderFetcher := NewOrderFetcher(waf) - setupTestData := func() (models.OfficeUser, models.Move, auth.Session) { - officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) - session := auth.Session{ - ApplicationName: auth.OfficeApp, - Roles: officeUser.User.Roles, - OfficeUserID: officeUser.ID, - IDToken: "fake_token", - AccessToken: "fakeAccessToken", - } - move := factory.BuildMoveWithShipment(suite.DB(), nil, nil) - return officeUser, move, session - } - - suite.Run("Returns orders matching full originDutyLocation name filter", func() { - officeUser, expectedMove, session = setupTestData() - locationName := expectedMove.Orders.OriginDutyLocation.Name - expectedMoves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{OriginDutyLocation: strings.Split(locationName, " ")}) - suite.NoError(err) - suite.Equal(1, len(expectedMoves)) - suite.Equal(locationName, string(expectedMoves[0].Orders.OriginDutyLocation.Name)) - }) - - suite.Run("Returns orders matching partial originDutyLocation name filter", func() { - officeUser, expectedMove, session = setupTestData() - locationName := expectedMove.Orders.OriginDutyLocation.Name - //Split the location name and retrieve a substring (first string) for the search param - partialParamSearch := strings.Split(locationName, " ")[0] - expectedMoves, _, err := orderFetcher.ListOrders(suite.AppContextWithSessionForTest(&session), officeUser.ID, roles.RoleTypeTOO, &services.ListOrderParams{OriginDutyLocation: strings.Split(partialParamSearch, " ")}) - suite.NoError(err) - suite.Equal(1, len(expectedMoves)) - suite.Equal(locationName, string(expectedMoves[0].Orders.OriginDutyLocation.Name)) + suite.Equal(3, moveCount) + suite.Len(moves, 3) }) } diff --git a/pkg/services/order/order_updater.go b/pkg/services/order/order_updater.go index 8929d047684..3dab6939b5e 100644 --- a/pkg/services/order/order_updater.go +++ b/pkg/services/order/order_updater.go @@ -268,6 +268,10 @@ func orderFromTOOPayload(appCtx appcontext.AppContext, existingOrder models.Orde order.AmendedOrdersAcknowledgedAt = &acknowledgedAt } + if payload.DependentsAuthorized != nil { + order.Entitlement.DependentsAuthorized = payload.DependentsAuthorized + } + if payload.Grade != nil { order.Grade = (*internalmessages.OrderPayGrade)(payload.Grade) // Calculate new DBWeightAuthorized based on the new grade @@ -405,6 +409,10 @@ func orderFromCounselingPayload(appCtx appcontext.AppContext, existingOrder mode order.OrdersType = internalmessages.OrdersType(*payload.OrdersType) } + if payload.DependentsAuthorized != nil { + order.Entitlement.DependentsAuthorized = payload.DependentsAuthorized + } + if payload.Grade != nil { order.Grade = (*internalmessages.OrderPayGrade)(payload.Grade) // Calculate new DBWeightAuthorized based on the new grade @@ -462,7 +470,7 @@ func allowanceFromTOOPayload(appCtx appcontext.AppContext, existingOrder models. } weight := weightAllotment.TotalWeightSelf // Payload does not have this information, retrieve dependents from the existing order - if existingOrder.HasDependents && *payload.DependentsAuthorized { + if existingOrder.HasDependents && *order.Entitlement.DependentsAuthorized { // Only utilize dependent weight authorized if dependents are both present and authorized weight = weightAllotment.TotalWeightSelfPlusDependents } @@ -472,10 +480,6 @@ func allowanceFromTOOPayload(appCtx appcontext.AppContext, existingOrder models. order.Entitlement.OrganizationalClothingAndIndividualEquipment = *payload.OrganizationalClothingAndIndividualEquipment } - if payload.DependentsAuthorized != nil { - order.Entitlement.DependentsAuthorized = payload.DependentsAuthorized - } - if payload.StorageInTransit != nil { newSITAllowance := int(*payload.StorageInTransit) order.Entitlement.StorageInTransit = &newSITAllowance @@ -488,6 +492,8 @@ func allowanceFromTOOPayload(appCtx appcontext.AppContext, existingOrder models. if payload.WeightRestriction != nil { weightRestriction := int(*payload.WeightRestriction) order.Entitlement.WeightRestriction = &weightRestriction + } else { + order.Entitlement.WeightRestriction = nil } if payload.AccompaniedTour != nil { @@ -570,7 +576,7 @@ func allowanceFromCounselingPayload(appCtx appcontext.AppContext, existingOrder } weight := weightAllotment.TotalWeightSelf // Payload does not have this information, retrieve dependents from the existing order - if existingOrder.HasDependents && *payload.DependentsAuthorized { + if existingOrder.HasDependents && *order.Entitlement.DependentsAuthorized { // Only utilize dependent weight authorized if dependents are both present and authorized weight = weightAllotment.TotalWeightSelfPlusDependents } @@ -580,10 +586,6 @@ func allowanceFromCounselingPayload(appCtx appcontext.AppContext, existingOrder order.Entitlement.OrganizationalClothingAndIndividualEquipment = *payload.OrganizationalClothingAndIndividualEquipment } - if payload.DependentsAuthorized != nil { - order.Entitlement.DependentsAuthorized = payload.DependentsAuthorized - } - if payload.StorageInTransit != nil { newSITAllowance := int(*payload.StorageInTransit) order.Entitlement.StorageInTransit = &newSITAllowance @@ -596,6 +598,8 @@ func allowanceFromCounselingPayload(appCtx appcontext.AppContext, existingOrder if payload.WeightRestriction != nil { weightRestriction := int(*payload.WeightRestriction) order.Entitlement.WeightRestriction = &weightRestriction + } else { + order.Entitlement.WeightRestriction = nil } if payload.AccompaniedTour != nil { @@ -631,7 +635,7 @@ func allowanceFromCounselingPayload(appCtx appcontext.AppContext, existingOrder // Recalculate UB allowance of order entitlement if order.Entitlement != nil { - unaccompaniedBaggageAllowance, err := models.GetUBWeightAllowance(appCtx, order.OriginDutyLocation.Address.IsOconus, order.NewDutyLocation.Address.IsOconus, order.ServiceMember.Affiliation, order.Grade, &order.OrdersType, payload.DependentsAuthorized, order.Entitlement.AccompaniedTour, order.Entitlement.DependentsUnderTwelve, order.Entitlement.DependentsTwelveAndOver) + unaccompaniedBaggageAllowance, err := models.GetUBWeightAllowance(appCtx, order.OriginDutyLocation.Address.IsOconus, order.NewDutyLocation.Address.IsOconus, order.ServiceMember.Affiliation, order.Grade, &order.OrdersType, order.Entitlement.DependentsAuthorized, order.Entitlement.AccompaniedTour, order.Entitlement.DependentsUnderTwelve, order.Entitlement.DependentsTwelveAndOver) if err != nil { return models.Order{}, err } diff --git a/pkg/services/order/order_updater_test.go b/pkg/services/order/order_updater_test.go index 9e86e990f95..41a139d1bd2 100644 --- a/pkg/services/order/order_updater_test.go +++ b/pkg/services/order/order_updater_test.go @@ -122,6 +122,7 @@ func (suite *OrderServiceSuite) TestUpdateOrderAsTOO() { ReportByDate: &reportByDate, Tac: handlers.FmtString("E19A"), Sac: nullable.NewString("987654321"), + DependentsAuthorized: models.BoolPointer(true), } updatedOrder, _, err := orderUpdater.UpdateOrderAsTOO(suite.AppContextForTest(), order.ID, payload, eTag) @@ -146,6 +147,7 @@ func (suite *OrderServiceSuite) TestUpdateOrderAsTOO() { suite.Equal(payload.Tac, updatedOrder.TAC) suite.Equal(payload.Sac.Value, updatedOrder.SAC) suite.EqualValues(updatedGbloc.GBLOC, *updatedOrder.OriginDutyLocationGBLOC) + suite.Equal(payload.DependentsAuthorized, updatedOrder.Entitlement.DependentsAuthorized) var moveInDB models.Move err = suite.DB().Find(&moveInDB, move.ID) @@ -451,6 +453,7 @@ func (suite *OrderServiceSuite) TestUpdateOrderAsCounselor() { Tac: handlers.FmtString("E19A"), Sac: nullable.NewString("987654321"), Grade: &grade, + DependentsAuthorized: models.BoolPointer(true), } eTag := etag.GenerateEtag(order.UpdatedAt) @@ -474,6 +477,7 @@ func (suite *OrderServiceSuite) TestUpdateOrderAsCounselor() { suite.EqualValues(body.Tac, updatedOrder.TAC) suite.EqualValues(body.Sac.Value, updatedOrder.SAC) suite.Equal(*updatedOrder.Entitlement.DBAuthorizedWeight, 16000) + suite.Equal(body.DependentsAuthorized, updatedOrder.Entitlement.DependentsAuthorized) }) suite.Run("Updates the PPM actual expense reimbursement when pay grade is civilian", func() { @@ -581,9 +585,8 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsTOO() { eTag := etag.GenerateEtag(order.UpdatedAt) payload := ghcmessages.UpdateAllowancePayload{ - Agency: &affiliation, - DependentsAuthorized: models.BoolPointer(true), - Grade: &grade, + Agency: &affiliation, + Grade: &grade, OrganizationalClothingAndIndividualEquipment: &ocie, ProGearWeight: proGearWeight, ProGearWeightSpouse: proGearWeightSpouse, @@ -598,7 +601,6 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsTOO() { suite.NoError(err) suite.Equal(order.ID.String(), updatedOrder.ID.String()) - suite.Equal(payload.DependentsAuthorized, updatedOrder.Entitlement.DependentsAuthorized) suite.Equal(*payload.ProGearWeight, int64(updatedOrder.Entitlement.ProGearWeight)) suite.Equal(*payload.ProGearWeightSpouse, int64(updatedOrder.Entitlement.ProGearWeightSpouse)) suite.Equal(*payload.RequiredMedicalEquipmentWeight, int64(updatedOrder.Entitlement.RequiredMedicalEquipmentWeight)) @@ -620,9 +622,8 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsTOO() { eTag := etag.GenerateEtag(order.UpdatedAt) payload := ghcmessages.UpdateAllowancePayload{ - Agency: &affiliation, - DependentsAuthorized: models.BoolPointer(true), - Grade: &grade, + Agency: &affiliation, + Grade: &grade, OrganizationalClothingAndIndividualEquipment: &ocie, ProGearWeight: proGearWeight, ProGearWeightSpouse: proGearWeightSpouse, @@ -640,7 +641,6 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsTOO() { suite.NoError(err) suite.Equal(order.ID.String(), updatedOrder.ID.String()) - suite.Equal(payload.DependentsAuthorized, updatedOrder.Entitlement.DependentsAuthorized) suite.Equal(*payload.ProGearWeight, int64(updatedOrder.Entitlement.ProGearWeight)) suite.Equal(*payload.ProGearWeightSpouse, int64(updatedOrder.Entitlement.ProGearWeightSpouse)) suite.Equal(*payload.RequiredMedicalEquipmentWeight, int64(updatedOrder.Entitlement.RequiredMedicalEquipmentWeight)) @@ -651,6 +651,32 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsTOO() { suite.Equal(*payload.DependentsUnderTwelve, int64(*updatedOrder.Entitlement.DependentsUnderTwelve)) }) + suite.Run("Updates the allowance when weightRestriction is null", func() { + moveRouter := move.NewMoveRouter() + orderUpdater := NewOrderUpdater(moveRouter) + order := factory.BuildNeedsServiceCounselingMove(suite.DB(), []factory.Customization{ + { + Model: models.Entitlement{ + WeightRestriction: models.IntPointer(1000), + }, + }, + }, nil).Orders + + eTag := etag.GenerateEtag(order.UpdatedAt) + + payload := ghcmessages.UpdateAllowancePayload{ + WeightRestriction: nil, + } + + updatedOrder, _, err := orderUpdater.UpdateAllowanceAsTOO(suite.AppContextForTest(), order.ID, payload, eTag) + suite.NoError(err) + + var orderInDB models.Order + err = suite.DB().Find(&orderInDB, order.ID) + suite.NoError(err) + suite.Nil(updatedOrder.Entitlement.WeightRestriction) + }) + suite.Run("Updates the allowance when all fields are valid with dependents", func() { moveRouter := move.NewMoveRouter() orderUpdater := NewOrderUpdater(moveRouter) @@ -668,9 +694,8 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsTOO() { eTag := etag.GenerateEtag(order.UpdatedAt) payload := ghcmessages.UpdateAllowancePayload{ - Agency: &affiliation, - DependentsAuthorized: models.BoolPointer(true), - Grade: &grade, + Agency: &affiliation, + Grade: &grade, OrganizationalClothingAndIndividualEquipment: &ocie, ProGearWeight: proGearWeight, ProGearWeightSpouse: proGearWeightSpouse, @@ -685,7 +710,6 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsTOO() { suite.NoError(err) suite.Equal(order.ID.String(), updatedOrder.ID.String()) - suite.Equal(payload.DependentsAuthorized, updatedOrder.Entitlement.DependentsAuthorized) suite.Equal(*payload.ProGearWeight, int64(updatedOrder.Entitlement.ProGearWeight)) suite.Equal(*payload.ProGearWeightSpouse, int64(updatedOrder.Entitlement.ProGearWeightSpouse)) suite.Equal(*payload.RequiredMedicalEquipmentWeight, int64(updatedOrder.Entitlement.RequiredMedicalEquipmentWeight)) @@ -737,9 +761,8 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsCounselor() { eTag := etag.GenerateEtag(order.UpdatedAt) payload := ghcmessages.CounselingUpdateAllowancePayload{ - Agency: &affiliation, - DependentsAuthorized: models.BoolPointer(true), - Grade: &grade, + Agency: &affiliation, + Grade: &grade, OrganizationalClothingAndIndividualEquipment: &ocie, ProGearWeight: proGearWeight, ProGearWeightSpouse: proGearWeightSpouse, @@ -754,7 +777,6 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsCounselor() { suite.NoError(err) suite.Equal(order.ID.String(), updatedOrder.ID.String()) - suite.Equal(payload.DependentsAuthorized, updatedOrder.Entitlement.DependentsAuthorized) suite.Equal(*payload.ProGearWeight, int64(updatedOrder.Entitlement.ProGearWeight)) suite.Equal(*payload.ProGearWeightSpouse, int64(updatedOrder.Entitlement.ProGearWeightSpouse)) suite.Equal(*payload.RequiredMedicalEquipmentWeight, int64(updatedOrder.Entitlement.RequiredMedicalEquipmentWeight)) @@ -779,9 +801,8 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsCounselor() { weightRestriction := models.Int64Pointer(5000) payload := ghcmessages.CounselingUpdateAllowancePayload{ - Agency: &affiliation, - DependentsAuthorized: models.BoolPointer(true), - Grade: &grade, + Agency: &affiliation, + Grade: &grade, OrganizationalClothingAndIndividualEquipment: &ocie, ProGearWeight: proGearWeight, ProGearWeightSpouse: proGearWeightSpouse, @@ -800,7 +821,6 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsCounselor() { suite.NoError(err) suite.Equal(order.ID.String(), updatedOrder.ID.String()) - suite.Equal(payload.DependentsAuthorized, updatedOrder.Entitlement.DependentsAuthorized) suite.Equal(*payload.ProGearWeight, int64(updatedOrder.Entitlement.ProGearWeight)) suite.Equal(*payload.ProGearWeightSpouse, int64(updatedOrder.Entitlement.ProGearWeightSpouse)) suite.Equal(*payload.RequiredMedicalEquipmentWeight, int64(updatedOrder.Entitlement.RequiredMedicalEquipmentWeight)) @@ -810,6 +830,32 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsCounselor() { suite.Equal(*payload.WeightRestriction, int64(*updatedOrder.Entitlement.WeightRestriction)) }) + suite.Run("Updates the allowance when weightRestriction is null", func() { + moveRouter := move.NewMoveRouter() + orderUpdater := NewOrderUpdater(moveRouter) + order := factory.BuildNeedsServiceCounselingMove(suite.DB(), []factory.Customization{ + { + Model: models.Entitlement{ + WeightRestriction: models.IntPointer(1000), + }, + }, + }, nil).Orders + + eTag := etag.GenerateEtag(order.UpdatedAt) + + payload := ghcmessages.CounselingUpdateAllowancePayload{ + WeightRestriction: nil, + } + + updatedOrder, _, err := orderUpdater.UpdateAllowanceAsCounselor(suite.AppContextForTest(), order.ID, payload, eTag) + suite.NoError(err) + + var orderInDB models.Order + err = suite.DB().Find(&orderInDB, order.ID) + suite.NoError(err) + suite.Nil(updatedOrder.Entitlement.WeightRestriction) + }) + suite.Run("Updates the allowance when all fields are valid with dependents present and authorized", func() { moveRouter := move.NewMoveRouter() orderUpdater := NewOrderUpdater(moveRouter) @@ -826,9 +872,8 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsCounselor() { eTag := etag.GenerateEtag(order.UpdatedAt) payload := ghcmessages.CounselingUpdateAllowancePayload{ - Agency: &affiliation, - DependentsAuthorized: models.BoolPointer(true), - Grade: &grade, + Agency: &affiliation, + Grade: &grade, OrganizationalClothingAndIndividualEquipment: &ocie, ProGearWeight: proGearWeight, ProGearWeightSpouse: proGearWeightSpouse, @@ -847,7 +892,6 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsCounselor() { suite.NoError(err) suite.Equal(order.ID.String(), updatedOrder.ID.String()) - suite.Equal(payload.DependentsAuthorized, updatedOrder.Entitlement.DependentsAuthorized) suite.Equal(*payload.ProGearWeight, int64(updatedOrder.Entitlement.ProGearWeight)) suite.Equal(*payload.ProGearWeightSpouse, int64(updatedOrder.Entitlement.ProGearWeightSpouse)) suite.Equal(*payload.RequiredMedicalEquipmentWeight, int64(updatedOrder.Entitlement.RequiredMedicalEquipmentWeight)) @@ -876,9 +920,8 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsCounselor() { eTag := etag.GenerateEtag(orderWithoutDefaults.UpdatedAt) payload := ghcmessages.CounselingUpdateAllowancePayload{ - Agency: &affiliation, - DependentsAuthorized: models.BoolPointer(true), - Grade: &grade, + Agency: &affiliation, + Grade: &grade, OrganizationalClothingAndIndividualEquipment: &ocie, ProGearWeight: proGearWeight, ProGearWeightSpouse: proGearWeightSpouse, @@ -897,7 +940,6 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsCounselor() { suite.NoError(err) suite.Equal(orderWithoutDefaults.ID.String(), updatedOrder.ID.String()) - suite.Equal(payload.DependentsAuthorized, updatedOrder.Entitlement.DependentsAuthorized) suite.Equal(*payload.ProGearWeight, int64(updatedOrder.Entitlement.ProGearWeight)) suite.Equal(*payload.ProGearWeightSpouse, int64(updatedOrder.Entitlement.ProGearWeightSpouse)) suite.Equal(*payload.RequiredMedicalEquipmentWeight, int64(updatedOrder.Entitlement.RequiredMedicalEquipmentWeight)) @@ -928,9 +970,8 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsCounselor() { eTag := etag.GenerateEtag(order.UpdatedAt) payload := ghcmessages.CounselingUpdateAllowancePayload{ - Agency: &affiliation, - DependentsAuthorized: models.BoolPointer(true), - Grade: &grade, + Agency: &affiliation, + Grade: &grade, OrganizationalClothingAndIndividualEquipment: &ocie, ProGearWeight: proGearWeight, ProGearWeightSpouse: proGearWeightSpouse, @@ -965,9 +1006,8 @@ func (suite *OrderServiceSuite) TestUpdateAllowanceAsCounselor() { eTag := etag.GenerateEtag(order.UpdatedAt) payload := ghcmessages.CounselingUpdateAllowancePayload{ - Agency: &affiliation, - DependentsAuthorized: models.BoolPointer(true), - Grade: &grade, + Agency: &affiliation, + Grade: &grade, OrganizationalClothingAndIndividualEquipment: &ocie, ProGearWeight: proGearWeight, ProGearWeightSpouse: proGearWeightSpouse, diff --git a/pkg/services/paperwork/prime_download_user_upload_to_pdf_converter.go b/pkg/services/paperwork/prime_download_user_upload_to_pdf_converter.go index 0a61b8ebe26..504e8af3a00 100644 --- a/pkg/services/paperwork/prime_download_user_upload_to_pdf_converter.go +++ b/pkg/services/paperwork/prime_download_user_upload_to_pdf_converter.go @@ -117,14 +117,11 @@ func (g *moveUserUploadToPDFDownloader) GenerateDownloadMoveUserUploadPDF(appCtx // Build orderUploadDocType for document func (g *moveUserUploadToPDFDownloader) buildPdfBatchInfo(appCtx appcontext.AppContext, uploadDocType services.MoveOrderUploadType, documentID uuid.UUID) (*pdfBatchInfo, error) { - document, err := models.FetchDocumentWithNoRestrictions(appCtx.DB(), appCtx.Session(), documentID, true) + document, err := models.FetchDocumentWithNoRestrictions(appCtx.DB(), appCtx.Session(), documentID) if err != nil { return nil, errors.Wrap(err, fmt.Sprintf("error fetching document domain by id: %s", documentID)) } - // filter out deleted uploads from userUploads - document.UserUploads = document.UserUploads.FilterDeleted() - var pdfFileNames []string var pageCounts []int // Document has one or more uploads. Create PDF file for each. 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_address_update/shipment_address_update_requester.go b/pkg/services/shipment_address_update/shipment_address_update_requester.go index 1c2e93b198c..8f4b75973e6 100644 --- a/pkg/services/shipment_address_update/shipment_address_update_requester.go +++ b/pkg/services/shipment_address_update/shipment_address_update_requester.go @@ -281,6 +281,7 @@ func (f *shipmentAddressUpdateRequester) RequestShipmentDeliveryAddressUpdate(ap if eTag != etag.GenerateEtag(shipment.UpdatedAt) { return nil, apperror.NewPreconditionFailedError(shipmentID, nil) } + isInternationalShipment := shipment.MarketCode == models.MarketCodeInternational shipmentHasApprovedDestSIT := f.doesShipmentContainApprovedDestinationSIT(shipment) @@ -499,7 +500,7 @@ func (f *shipmentAddressUpdateRequester) ReviewShipmentAddressChange(appCtx appc if tooApprovalStatus == models.ShipmentAddressUpdateStatusApproved { queryBuilder := query.NewQueryBuilder() - serviceItemUpdater := mtoserviceitem.NewMTOServiceItemUpdater(f.planner, queryBuilder, f.moveRouter, f.shipmentFetcher, f.addressCreator, f.portLocationFetcher) + serviceItemUpdater := mtoserviceitem.NewMTOServiceItemUpdater(f.planner, queryBuilder, f.moveRouter, f.shipmentFetcher, f.addressCreator, f.portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) serviceItemCreator := mtoserviceitem.NewMTOServiceItemCreator(f.planner, queryBuilder, f.moveRouter, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticPackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticShorthaulPricer(), ghcrateengine.NewDomesticOriginPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) addressUpdate.Status = models.ShipmentAddressUpdateStatusApproved @@ -523,7 +524,7 @@ func (f *shipmentAddressUpdateRequester) ReviewShipmentAddressChange(appCtx appc } var shipmentDetails models.MTOShipment - err = appCtx.DB().EagerPreload("MoveTaskOrder", "MTOServiceItems.ReService").Find(&shipmentDetails, shipmentID) + err = appCtx.DB().EagerPreload("MoveTaskOrder", "MTOServiceItems.ReService", "MTOServiceItems.SITDestinationOriginalAddress", "MTOServiceItems.SITDestinationFinalAddress").Find(&shipmentDetails, shipmentID) if err != nil { if err == sql.ErrNoRows { return nil, apperror.NewNotFoundError(shipmentID, "looking for shipment") @@ -531,6 +532,33 @@ func (f *shipmentAddressUpdateRequester) ReviewShipmentAddressChange(appCtx appc return nil, apperror.NewQueryError("MTOShipment", err, "") } + shipmentHasApprovedDestSIT := f.doesShipmentContainApprovedDestinationSIT(shipmentDetails) + + for i, serviceItem := range shipmentDetails.MTOServiceItems { + if shipment.MarketCode != models.MarketCodeInternational && shipment.PrimeEstimatedWeight != nil || shipment.MarketCode != models.MarketCodeInternational && shipment.PrimeActualWeight != nil { + var updatedServiceItem *models.MTOServiceItem + if serviceItem.ReService.Code == models.ReServiceCodeDDP || serviceItem.ReService.Code == models.ReServiceCodeDUPK { + updatedServiceItem, err = serviceItemUpdater.UpdateMTOServiceItemPricingEstimate(appCtx, &serviceItem, shipment, etag.GenerateEtag(serviceItem.UpdatedAt)) + if err != nil { + return nil, apperror.NewUpdateError(serviceItem.ReServiceID, err.Error()) + } + } + + if !shipmentHasApprovedDestSIT { + if serviceItem.ReService.Code == models.ReServiceCodeDLH || serviceItem.ReService.Code == models.ReServiceCodeFSC { + updatedServiceItem, err = serviceItemUpdater.UpdateMTOServiceItemPricingEstimate(appCtx, &serviceItem, shipment, etag.GenerateEtag(serviceItem.UpdatedAt)) + if err != nil { + return nil, apperror.NewUpdateError(serviceItem.ReServiceID, err.Error()) + } + } + } + + if updatedServiceItem != nil { + shipmentDetails.MTOServiceItems[i] = *updatedServiceItem + } + } + } + // If the pricing type has changed then we automatically reject the DLH or DSH service item on the shipment since it is now inaccurate var approvedPaymentRequestsExistsForServiceItem bool if haulPricingTypeHasChanged && len(shipment.MTOServiceItems) > 0 && !isInternationalShipment { @@ -546,8 +574,6 @@ func (f *shipmentAddressUpdateRequester) ReviewShipmentAddressChange(appCtx appc return nil, apperror.NewQueryError("ServiceItemPaymentRequests", err, "") } - shipmentHasApprovedDestSIT := f.doesShipmentContainApprovedDestinationSIT(shipmentDetails) - // do NOT regenerate any service items if the following conditions exist: // payment has already been approved for DLH or DSH service item // destination SIT is on shipment and any of the service items have an appproved status diff --git a/pkg/services/shipment_address_update/shipment_address_update_requester_test.go b/pkg/services/shipment_address_update/shipment_address_update_requester_test.go index 223cf820988..ad1c2cd1ad9 100644 --- a/pkg/services/shipment_address_update/shipment_address_update_requester_test.go +++ b/pkg/services/shipment_address_update/shipment_address_update_requester_test.go @@ -15,8 +15,44 @@ import ( "github.com/transcom/mymove/pkg/services/address" moveservices "github.com/transcom/mymove/pkg/services/move" "github.com/transcom/mymove/pkg/testdatagen" + "github.com/transcom/mymove/pkg/unit" ) +func (suite *ShipmentAddressUpdateServiceSuite) setupServiceItemData() { + startDate := time.Date(2020, time.January, 1, 12, 0, 0, 0, time.UTC) + endDate := time.Date(2020, time.December, 31, 12, 0, 0, 0, time.UTC) + + testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ + ReContractYear: models.ReContractYear{ + StartDate: startDate, + EndDate: endDate, + }, + }) + + originalDomesticServiceArea := testdatagen.FetchOrMakeReDomesticServiceArea(suite.AppContextForTest().DB(), testdatagen.Assertions{ + ReDomesticServiceArea: models.ReDomesticServiceArea{ + ServiceArea: "004", + ServicesSchedule: 2, + }, + ReContract: testdatagen.FetchOrMakeReContract(suite.AppContextForTest().DB(), testdatagen.Assertions{}), + }) + + testdatagen.FetchOrMakeReDomesticLinehaulPrice(suite.DB(), testdatagen.Assertions{ + ReDomesticLinehaulPrice: models.ReDomesticLinehaulPrice{ + Contract: originalDomesticServiceArea.Contract, + ContractID: originalDomesticServiceArea.ContractID, + DomesticServiceArea: originalDomesticServiceArea, + DomesticServiceAreaID: originalDomesticServiceArea.ID, + WeightLower: unit.Pound(500), + WeightUpper: unit.Pound(9999), + MilesLower: 500, + MilesUpper: 9999, + PriceMillicents: unit.Millicents(606800), + IsPeakPeriod: false, + }, + }) +} + func (suite *ShipmentAddressUpdateServiceSuite) TestCreateApprovedShipmentAddressUpdate() { setupTestData := func() models.Move { testdatagen.FetchOrMakeReContractYear(suite.DB(), testdatagen.Assertions{ @@ -760,6 +796,8 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestTOOApprovedShipmentAddressUp suite.Run("TOO approves address change", func() { + suite.setupServiceItemData() + addressChange := factory.BuildShipmentAddressUpdate(suite.DB(), nil, []factory.Trait{ factory.GetTraitAvailableToPrimeMove, }) @@ -1460,6 +1498,9 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestTOOApprovedShipmentAddressUp false, ).Return(2500, nil).Once() move := setupTestData() + + suite.setupServiceItemData() + shipment := factory.BuildMTOShipmentWithMove(&move, suite.DB(), []factory.Customization{ { Model: models.Address{ @@ -1528,6 +1569,9 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestTOOApprovedShipmentAddressUp false, ).Return(2500, nil).Once() move := setupTestData() + + suite.setupServiceItemData() + shipment := factory.BuildMTOShipmentWithMove(&move, suite.DB(), []factory.Customization{ { Model: models.Address{ @@ -1583,6 +1627,9 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestTOOApprovedShipmentAddressUp suite.Run("Service items are not rejected when pricing type does not change post TOO approval", func() { move := setupTestData() + + suite.setupServiceItemData() + shipment := factory.BuildMTOShipmentWithMove(&move, suite.DB(), nil, nil) //Generate service items to test their statuses upon approval @@ -1636,6 +1683,9 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestTOOApprovedShipmentAddressUp false, ).Return(2500, nil).Once() move := setupTestData() + + suite.setupServiceItemData() + shipment := factory.BuildMTOShipmentWithMove(&move, suite.DB(), []factory.Customization{ { Model: models.Address{ @@ -1705,6 +1755,9 @@ func (suite *ShipmentAddressUpdateServiceSuite) TestTOOApprovedShipmentAddressUp PostalCode: "90210", } move := setupTestData() + + suite.setupServiceItemData() + shipment := factory.BuildMTOShipmentWithMove(&move, suite.DB(), []factory.Customization{ { Model: models.Address{ diff --git a/pkg/services/sit_entry_date_update/sit_entry_date_updater.go b/pkg/services/sit_entry_date_update/sit_entry_date_updater.go index 61bc78bb988..54012f13bb0 100644 --- a/pkg/services/sit_entry_date_update/sit_entry_date_updater.go +++ b/pkg/services/sit_entry_date_update/sit_entry_date_updater.go @@ -2,6 +2,7 @@ package sitentrydateupdate import ( "database/sql" + "fmt" "time" "github.com/transcom/mymove/pkg/appcontext" @@ -52,9 +53,10 @@ func (p sitEntryDateUpdater) UpdateSitEntryDate(appCtx appcontext.AppContext, s return nil, apperror.NewQueryError("Shipment", err, "") } - // the service code can either be DOFSIT or DDFSIT + // the service code can either be DOFSIT/DDFSIT or IOFSIT/IDFSIT serviceItemCode := serviceItem.ReService.Code - if serviceItemCode != models.ReServiceCodeDOFSIT && serviceItemCode != models.ReServiceCodeDDFSIT { + if serviceItemCode != models.ReServiceCodeDOFSIT && serviceItemCode != models.ReServiceCodeDDFSIT && + serviceItemCode != models.ReServiceCodeIOFSIT && serviceItemCode != models.ReServiceCodeIDFSIT { return nil, apperror.NewUnprocessableEntityError(string(serviceItemCode) + "You cannot change the SIT entry date of this service item.") } @@ -62,16 +64,16 @@ func (p sitEntryDateUpdater) UpdateSitEntryDate(appCtx appcontext.AppContext, s // then looking for the sister service item of add'l days // once found, we'll set the value of variable to that service item // so now we have the 1st day of SIT service item & the add'l days SIT service item - if serviceItemCode == models.ReServiceCodeDOFSIT { + if serviceItemCode == models.ReServiceCodeDOFSIT || serviceItemCode == models.ReServiceCodeIOFSIT { for _, si := range shipment.MTOServiceItems { - if si.ReService.Code == models.ReServiceCodeDOASIT { + if si.ReService.Code == models.ReServiceCodeDOASIT || si.ReService.Code == models.ReServiceCodeIOASIT { serviceItemAdditionalDays = si break } } - } else if serviceItemCode == models.ReServiceCodeDDFSIT { + } else if serviceItemCode == models.ReServiceCodeDDFSIT || serviceItemCode == models.ReServiceCodeIDFSIT { for _, si := range shipment.MTOServiceItems { - if si.ReService.Code == models.ReServiceCodeDDASIT { + if si.ReService.Code == models.ReServiceCodeDDASIT || si.ReService.Code == models.ReServiceCodeIDASIT { serviceItemAdditionalDays = si break } @@ -85,12 +87,18 @@ func (p sitEntryDateUpdater) UpdateSitEntryDate(appCtx appcontext.AppContext, s // updating sister service item to have the next day for SIT entry date if s.SITEntryDate == nil { return nil, apperror.NewUnprocessableEntityError("You must provide the SIT entry date in the request") - } else if s.SITEntryDate != nil { - serviceItem.SITEntryDate = s.SITEntryDate - dayAfter := s.SITEntryDate.Add(24 * time.Hour) - serviceItemAdditionalDays.SITEntryDate = &dayAfter } + // The new SIT entry date must be before SIT departure date + if serviceItem.SITDepartureDate != nil && !s.SITEntryDate.Before(*serviceItem.SITDepartureDate) { + return nil, apperror.NewUnprocessableEntityError(fmt.Sprintf("the SIT Entry Date (%s) must be before the SIT Departure Date (%s)", + s.SITEntryDate.Format("2006-01-02"), serviceItem.SITDepartureDate.Format("2006-01-02"))) + } + + serviceItem.SITEntryDate = s.SITEntryDate + dayAfter := s.SITEntryDate.Add(24 * time.Hour) + serviceItemAdditionalDays.SITEntryDate = &dayAfter + // Make the update to both service items and create a InvalidInputError if there were validation issues transactionError := appCtx.NewTransaction(func(txnCtx appcontext.AppContext) error { diff --git a/pkg/services/sit_entry_date_update/sit_entry_date_updater_test.go b/pkg/services/sit_entry_date_update/sit_entry_date_updater_test.go index a6f45b1dcdc..a5e79799f3c 100644 --- a/pkg/services/sit_entry_date_update/sit_entry_date_updater_test.go +++ b/pkg/services/sit_entry_date_update/sit_entry_date_updater_test.go @@ -1,6 +1,7 @@ package sitentrydateupdate import ( + "fmt" "time" "github.com/gofrs/uuid" @@ -48,6 +49,44 @@ func (suite *UpdateSitEntryDateServiceSuite) TestUpdateSitEntryDate() { return ddfServiceItem, ddaServiceItem } + setupInternationalModels := func() (models.MTOServiceItem, models.MTOServiceItem) { + move := factory.BuildMove(suite.DB(), nil, nil) + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + MarketCode: models.MarketCodeInternational, + }, + }, + }, nil) + idfServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeIDFSIT, + }, + }, + }, nil) + idaServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeIDASIT, + }, + }, + }, nil) + return idfServiceItem, idaServiceItem + } + // Test not found error suite.Run("Not Found Error", func() { ddfServiceItem, _ := setupModels() @@ -66,6 +105,23 @@ func (suite *UpdateSitEntryDateServiceSuite) TestUpdateSitEntryDate() { suite.IsType(apperror.NotFoundError{}, err) }) + suite.Run("Not Found Error - international", func() { + idfServiceItem, _ := setupInternationalModels() + notFoundServiceItem := models.SITEntryDateUpdate{ + ID: idfServiceItem.ID, + SITEntryDate: idfServiceItem.SITEntryDate, + } + notFoundUUID, err := uuid.NewV4() + suite.NoError(err) + notFoundServiceItem.ID = notFoundUUID + + updatedServiceItem, err := updater.UpdateSitEntryDate(suite.AppContextForTest(), ¬FoundServiceItem) + + suite.Nil(updatedServiceItem) + suite.Error(err) + suite.IsType(apperror.NotFoundError{}, err) + }) + // Test successful update of both service items suite.Run("Successful update of service items", func() { ddfServiceItem, ddaServiceItem := setupModels() @@ -88,4 +144,188 @@ func (suite *UpdateSitEntryDateServiceSuite) TestUpdateSitEntryDate() { suite.Equal(ddaServiceItem.SITEntryDate.Local(), newSitEntryDateNextDay.Local()) }) + suite.Run("Successful update of service items - international", func() { + idfServiceItem, idaServiceItem := setupInternationalModels() + updatedServiceItem := models.SITEntryDateUpdate{ + ID: idfServiceItem.ID, + SITEntryDate: idfServiceItem.SITEntryDate, + } + newSitEntryDate := time.Date(2020, time.December, 02, 0, 0, 0, 0, time.UTC) + newSitEntryDateNextDay := newSitEntryDate.Add(24 * time.Hour) + + updatedServiceItem.SITEntryDate = &newSitEntryDate + idaServiceItem.SITEntryDate = &newSitEntryDateNextDay + + changedServiceItem, err := updater.UpdateSitEntryDate(suite.AppContextForTest(), &updatedServiceItem) + + suite.NoError(err) + suite.NotNil(updatedServiceItem) + suite.Equal(idfServiceItem.ID, updatedServiceItem.ID) + suite.Equal(updatedServiceItem.SITEntryDate.Local(), changedServiceItem.SITEntryDate.Local()) + suite.Equal(idaServiceItem.SITEntryDate.Local(), newSitEntryDateNextDay.Local()) + }) + + suite.Run("Fails to update when DOFSIT entry date is after DOFSIT departure date", func() { + today := models.TimePointer(time.Now()) + tomorrow := models.TimePointer(time.Now()) + move := factory.BuildMove(suite.DB(), nil, nil) + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + }, nil) + dofsitServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.MTOServiceItem{ + SITEntryDate: today, + SITDepartureDate: tomorrow, + }, + }, + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeDOFSIT, + }, + }, + }, nil) + updatedServiceItem := models.SITEntryDateUpdate{ + ID: dofsitServiceItem.ID, + SITEntryDate: models.TimePointer(tomorrow.AddDate(0, 0, 1)), + } + _, err := updater.UpdateSitEntryDate(suite.AppContextForTest(), &updatedServiceItem) + suite.Error(err) + expectedError := fmt.Sprintf( + "the SIT Entry Date (%s) must be before the SIT Departure Date (%s)", + updatedServiceItem.SITEntryDate.Format("2006-01-02"), + dofsitServiceItem.SITDepartureDate.Format("2006-01-02"), + ) + suite.Contains(err.Error(), expectedError) + }) + + suite.Run("Fails to update when DOFSIT entry date is the same as DOFSIT departure date", func() { + today := models.TimePointer(time.Now()) + tomorrow := models.TimePointer(time.Now()) + move := factory.BuildMove(suite.DB(), nil, nil) + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + }, nil) + dofsitServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.MTOServiceItem{ + SITEntryDate: today, + SITDepartureDate: tomorrow, + }, + }, + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeDOFSIT, + }, + }, + }, nil) + updatedServiceItem := models.SITEntryDateUpdate{ + ID: dofsitServiceItem.ID, + SITEntryDate: tomorrow, + } + _, err := updater.UpdateSitEntryDate(suite.AppContextForTest(), &updatedServiceItem) + suite.Error(err) + expectedError := fmt.Sprintf( + "the SIT Entry Date (%s) must be before the SIT Departure Date (%s)", + updatedServiceItem.SITEntryDate.Format("2006-01-02"), + dofsitServiceItem.SITDepartureDate.Format("2006-01-02"), + ) + suite.Contains(err.Error(), expectedError) + }) + + suite.Run("Fails to update when DDFSIT entry date is after DDFSIT departure date", func() { + today := models.TimePointer(time.Now()) + tomorrow := models.TimePointer(time.Now()) + move := factory.BuildMove(suite.DB(), nil, nil) + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + }, nil) + ddfsitServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.MTOServiceItem{ + SITEntryDate: today, + SITDepartureDate: tomorrow, + }, + }, + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeDDFSIT, + }, + }, + }, nil) + updatedServiceItem := models.SITEntryDateUpdate{ + ID: ddfsitServiceItem.ID, + SITEntryDate: models.TimePointer(tomorrow.AddDate(0, 0, 1)), + } + _, err := updater.UpdateSitEntryDate(suite.AppContextForTest(), &updatedServiceItem) + suite.Error(err) + expectedError := fmt.Sprintf( + "the SIT Entry Date (%s) must be before the SIT Departure Date (%s)", + updatedServiceItem.SITEntryDate.Format("2006-01-02"), + ddfsitServiceItem.SITDepartureDate.Format("2006-01-02"), + ) + suite.Contains(err.Error(), expectedError) + }) + + suite.Run("Fails to update when DDFSIT entry date is the same as DDFSIT departure date", func() { + today := models.TimePointer(time.Now()) + tomorrow := models.TimePointer(time.Now()) + move := factory.BuildMove(suite.DB(), nil, nil) + shipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: move, + LinkOnly: true, + }, + }, nil) + ddfsitServiceItem := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{ + { + Model: models.MTOServiceItem{ + SITEntryDate: today, + SITDepartureDate: tomorrow, + }, + }, + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeDDFSIT, + }, + }, + }, nil) + updatedServiceItem := models.SITEntryDateUpdate{ + ID: ddfsitServiceItem.ID, + SITEntryDate: tomorrow, + } + _, err := updater.UpdateSitEntryDate(suite.AppContextForTest(), &updatedServiceItem) + suite.Error(err) + expectedError := fmt.Sprintf( + "the SIT Entry Date (%s) must be before the SIT Departure Date (%s)", + updatedServiceItem.SITEntryDate.Format("2006-01-02"), + ddfsitServiceItem.SITDepartureDate.Format("2006-01-02"), + ) + suite.Contains(err.Error(), expectedError) + }) } diff --git a/pkg/services/sit_extension/sit_extension_denier.go b/pkg/services/sit_extension/sit_extension_denier.go index 07cd9477c16..9f019a0c47c 100644 --- a/pkg/services/sit_extension/sit_extension_denier.go +++ b/pkg/services/sit_extension/sit_extension_denier.go @@ -15,6 +15,7 @@ import ( routemocks "github.com/transcom/mymove/pkg/route/mocks" "github.com/transcom/mymove/pkg/services" "github.com/transcom/mymove/pkg/services/address" + "github.com/transcom/mymove/pkg/services/ghcrateengine" mtoserviceitem "github.com/transcom/mymove/pkg/services/mto_service_item" mtoshipment "github.com/transcom/mymove/pkg/services/mto_shipment" portlocation "github.com/transcom/mymove/pkg/services/port_location" @@ -35,7 +36,7 @@ func NewSITExtensionDenier(moveRouter services.MoveRouter) services.SITExtension mock.Anything, false, ).Return(400, nil) - return &sitExtensionDenier{moveRouter, mtoserviceitem.NewMTOServiceItemUpdater(planner, query.NewQueryBuilder(), moveRouter, mtoshipment.NewMTOShipmentFetcher(), address.NewAddressCreator(), portlocation.NewPortLocationFetcher())} + return &sitExtensionDenier{moveRouter, mtoserviceitem.NewMTOServiceItemUpdater(planner, query.NewQueryBuilder(), moveRouter, mtoshipment.NewMTOShipmentFetcher(), address.NewAddressCreator(), portlocation.NewPortLocationFetcher(), ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer())} } // DenySITExtension denies the SIT Extension diff --git a/pkg/services/transportation_office.go b/pkg/services/transportation_office.go index 9ec90364ba0..7255991f376 100644 --- a/pkg/services/transportation_office.go +++ b/pkg/services/transportation_office.go @@ -9,7 +9,7 @@ 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, 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 8ccd09694f3..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 diff --git a/pkg/services/transportation_office/transportation_office_fetcher_test.go b/pkg/services/transportation_office/transportation_office_fetcher_test.go index 59cb98150ae..532ab017ede 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) @@ -329,7 +329,7 @@ func (suite *TransportationOfficeServiceSuite) Test_Oconus_AK_FindCounselingOffi ServiceMemberID: serviceMember.ID, }) suite.Nil(err) - departmentIndictor, err := findOconusGblocDepartmentIndicator(appCtx, dutylocation, appCtx.Session().ServiceMemberID) + departmentIndictor, err := findOconusGblocDepartmentIndicator(appCtx, dutylocation, serviceMember.ID) suite.NotNil(departmentIndictor) suite.Nil(err) suite.NotNil(departmentIndictor.DepartmentIndicator) diff --git a/pkg/storage/filesystem.go b/pkg/storage/filesystem.go index 259fd4ee8ab..f6e43583420 100644 --- a/pkg/storage/filesystem.go +++ b/pkg/storage/filesystem.go @@ -116,6 +116,8 @@ func (fs *Filesystem) Fetch(key string) (io.ReadCloser, error) { // Tags returns the tags for a specified key func (fs *Filesystem) Tags(_ string) (map[string]string, error) { tags := make(map[string]string) + // Assume anti-virus complete + tags["av-status"] = "CLEAN" return tags, nil } diff --git a/pkg/storage/filesystem_test.go b/pkg/storage/filesystem_test.go index 27ecc5e951c..9c37b9204c8 100644 --- a/pkg/storage/filesystem_test.go +++ b/pkg/storage/filesystem_test.go @@ -1,6 +1,8 @@ package storage import ( + "io" + "strings" "testing" ) @@ -21,3 +23,62 @@ func TestFilesystemPresignedURL(t *testing.T) { t.Errorf("wrong presigned url: expected %s, got %s", expected, url) } } + +func TestFilesystemReturnsSuccessful(t *testing.T) { + fsParams := FilesystemParams{ + root: "./", + webRoot: "https://example.text/files", + } + filesystem := NewFilesystem(fsParams) + if filesystem == nil { + t.Fatal("could not create new filesystem") + } + + storeValue := strings.NewReader("anyValue") + _, err := filesystem.Store("anyKey", storeValue, "", nil) + if err != nil { + t.Fatalf("could not store in filesystem: %s", err) + } + + retReader, err := filesystem.Fetch("anyKey") + if err != nil { + t.Fatalf("could not fetch from filesystem: %s", err) + } + + err = filesystem.Delete("anyKey") + if err != nil { + t.Fatalf("could not delete on filesystem: %s", err) + } + + retValue, err := io.ReadAll(retReader) + if strings.Compare(string(retValue[:]), "anyValue") != 0 { + t.Fatalf("could not fetch from filesystem: %s", err) + } + + fileSystem := filesystem.FileSystem() + if fileSystem == nil { + t.Fatal("could not retrieve filesystem from filesystem") + } + + tempFileSystem := filesystem.TempFileSystem() + if tempFileSystem == nil { + t.Fatal("could not retrieve filesystem from filesystem") + } +} + +func TestFilesystemTags(t *testing.T) { + fsParams := FilesystemParams{ + root: "/home/username", + webRoot: "https://example.text/files", + } + fs := NewFilesystem(fsParams) + + tags, err := fs.Tags("anyKey") + if err != nil { + t.Fatalf("could not get tags: %s", err) + } + + if tag, exists := tags["av-status"]; exists && strings.Compare(tag, "CLEAN") != 0 { + t.Fatal("tag 'av-status' should return CLEAN") + } +} diff --git a/pkg/storage/memory.go b/pkg/storage/memory.go index 2f06ed6b96e..4e171e40e9d 100644 --- a/pkg/storage/memory.go +++ b/pkg/storage/memory.go @@ -116,6 +116,8 @@ func (fs *Memory) Fetch(key string) (io.ReadCloser, error) { // Tags returns the tags for a specified key func (fs *Memory) Tags(_ string) (map[string]string, error) { tags := make(map[string]string) + // Assume anti-virus complete + tags["av-status"] = "CLEAN" return tags, nil } diff --git a/pkg/storage/memory_test.go b/pkg/storage/memory_test.go index 59384c5acee..bdf3133e9c8 100644 --- a/pkg/storage/memory_test.go +++ b/pkg/storage/memory_test.go @@ -1,6 +1,8 @@ package storage import ( + "io" + "strings" "testing" ) @@ -21,3 +23,62 @@ func TestMemoryPresignedURL(t *testing.T) { t.Errorf("wrong presigned url: expected %s, got %s", expected, url) } } + +func TestMemoryReturnsSuccessful(t *testing.T) { + fsParams := MemoryParams{ + root: "/home/username", + webRoot: "https://example.text/files", + } + memory := NewMemory(fsParams) + if memory == nil { + t.Fatal("could not create new memory") + } + + storeValue := strings.NewReader("anyValue") + _, err := memory.Store("anyKey", storeValue, "", nil) + if err != nil { + t.Fatalf("could not store in memory: %s", err) + } + + retReader, err := memory.Fetch("anyKey") + if err != nil { + t.Fatalf("could not fetch from memory: %s", err) + } + + err = memory.Delete("anyKey") + if err != nil { + t.Fatalf("could not delete on memory: %s", err) + } + + retValue, err := io.ReadAll(retReader) + if strings.Compare(string(retValue[:]), "anyValue") != 0 { + t.Fatalf("could not fetch from memory: %s", err) + } + + fileSystem := memory.FileSystem() + if fileSystem == nil { + t.Fatal("could not retrieve filesystem from memory") + } + + tempFileSystem := memory.TempFileSystem() + if tempFileSystem == nil { + t.Fatal("could not retrieve filesystem from memory") + } +} + +func TestMemoryTags(t *testing.T) { + fsParams := MemoryParams{ + root: "/home/username", + webRoot: "https://example.text/files", + } + fs := NewMemory(fsParams) + + tags, err := fs.Tags("anyKey") + if err != nil { + t.Fatalf("could not get tags: %s", err) + } + + if tag, exists := tags["av-status"]; exists && strings.Compare(tag, "CLEAN") != 0 { + t.Fatal("tag 'av-status' should return CLEAN") + } +} diff --git a/pkg/storage/test/s3.go b/pkg/storage/test/s3.go index 97d06e7733d..56fbac83564 100644 --- a/pkg/storage/test/s3.go +++ b/pkg/storage/test/s3.go @@ -18,6 +18,7 @@ type FakeS3Storage struct { willSucceed bool fs *afero.Afero tempFs *afero.Afero + EmptyTags bool // Used for testing only } // Delete removes a file. @@ -95,7 +96,11 @@ func (fake *FakeS3Storage) TempFileSystem() *afero.Afero { // Tags returns the tags for a specified key func (fake *FakeS3Storage) Tags(_ string) (map[string]string, error) { tags := map[string]string{ - "tagName": "tagValue", + "av-status": "CLEAN", // Assume anti-virus run + } + if fake.EmptyTags { + tags = map[string]string{} + fake.EmptyTags = false // Reset after initial return, so future calls (tests) have filled tags } return tags, nil } diff --git a/pkg/storage/test/s3_test.go b/pkg/storage/test/s3_test.go new file mode 100644 index 00000000000..3c2f63bbeff --- /dev/null +++ b/pkg/storage/test/s3_test.go @@ -0,0 +1,101 @@ +package test + +import ( + "errors" + "io" + "strings" + "testing" +) + +// Tests all functions of FakeS3Storage +func TestFakeS3ReturnsSuccessful(t *testing.T) { + fakeS3 := NewFakeS3Storage(true) + if fakeS3 == nil { + t.Fatal("could not create new fakeS3") + } + + storeValue := strings.NewReader("anyValue") + _, err := fakeS3.Store("anyKey", storeValue, "", nil) + if err != nil { + t.Fatalf("could not store in fakeS3: %s", err) + } + + retReader, err := fakeS3.Fetch("anyKey") + if err != nil { + t.Fatalf("could not fetch from fakeS3: %s", err) + } + + err = fakeS3.Delete("anyKey") + if err != nil { + t.Fatalf("could not delete on fakeS3: %s", err) + } + + retValue, err := io.ReadAll(retReader) + if strings.Compare(string(retValue[:]), "anyValue") != 0 { + t.Fatalf("could not fetch from fakeS3: %s", err) + } + + fileSystem := fakeS3.FileSystem() + if fileSystem == nil { + t.Fatal("could not retrieve filesystem from fakeS3") + } + + tempFileSystem := fakeS3.TempFileSystem() + if tempFileSystem == nil { + t.Fatal("could not retrieve filesystem from fakeS3") + } + + tags, err := fakeS3.Tags("anyKey") + if err != nil { + t.Fatalf("could not fetch from fakeS3: %s", err) + } + if len(tags) != 1 { + t.Fatal("return tags must have av-status key assigned for fakeS3") + } + + presignedUrl, err := fakeS3.PresignedURL("anyKey", "anyContentType", "anyFileName") + if err != nil { + t.Fatal("could not retrieve presignedUrl from fakeS3") + } + + if strings.Compare(presignedUrl, "https://example.com/dir/anyKey?response-content-disposition=attachment%3B+filename%3D%22anyFileName%22&response-content-type=anyContentType&signed=test") != 0 { + t.Fatalf("could not retrieve proper presignedUrl from fakeS3 %s", presignedUrl) + } +} + +// Test for willSucceed false +func TestFakeS3WillNotSucceed(t *testing.T) { + fakeS3 := NewFakeS3Storage(false) + if fakeS3 == nil { + t.Fatalf("could not create new fakeS3") + } + + storeValue := strings.NewReader("anyValue") + _, err := fakeS3.Store("anyKey", storeValue, "", nil) + if err == nil || errors.Is(err, errors.New("failed to push")) { + t.Fatalf("should not be able to store when willSucceed false: %s", err) + } + + _, err = fakeS3.Fetch("anyKey") + if err == nil || errors.Is(err, errors.New("failed to fetch file")) { + t.Fatalf("should not find file on Fetch for willSucceed false: %s", err) + } +} + +// Tests empty tag returns empty tags on FakeS3Storage +func TestFakeS3ReturnsEmptyTags(t *testing.T) { + fakeS3 := NewFakeS3Storage(true) + if fakeS3 == nil { + t.Fatal("could not create new fakeS3") + } + + fakeS3.EmptyTags = true + + tags, err := fakeS3.Tags("anyKey") + if err != nil { + t.Fatalf("could not fetch from fakeS3: %s", err) + } + if len(tags) != 0 { + t.Fatal("return tags must be empty for FakeS3 when EmptyTags set to true") + } +} diff --git a/pkg/testdatagen/scenario/shared.go b/pkg/testdatagen/scenario/shared.go index 3eec4a8f5ee..14112a79af8 100644 --- a/pkg/testdatagen/scenario/shared.go +++ b/pkg/testdatagen/scenario/shared.go @@ -4296,7 +4296,7 @@ func createHHGWithOriginSITServiceItems( mock.Anything, false, ).Return(400, nil) - serviceItemUpdator := mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher) + serviceItemUpdator := mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) var originFirstDaySIT models.MTOServiceItem var originAdditionalDaySIT models.MTOServiceItem @@ -4566,7 +4566,7 @@ func createHHGWithDestinationSITServiceItems(appCtx appcontext.AppContext, prime mock.Anything, false, ).Return(400, nil) - serviceItemUpdator := mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher) + serviceItemUpdator := mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) var destinationFirstDaySIT models.MTOServiceItem var destinationAdditionalDaySIT models.MTOServiceItem @@ -5011,7 +5011,7 @@ func createHHGWithPaymentServiceItems( } destEntryDate := actualPickupDate - destDepDate := actualPickupDate + destDepDate := actualPickupDate.AddDate(0, 0, 1) destSITAddress := factory.BuildAddress(db, nil, nil) destSIT := factory.BuildMTOServiceItem(nil, []factory.Customization{ { @@ -5053,7 +5053,7 @@ func createHHGWithPaymentServiceItems( mock.Anything, false, ).Return(400, nil) - serviceItemUpdater := mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher) + serviceItemUpdater := mtoserviceitem.NewMTOServiceItemUpdater(planner, queryBuilder, moveRouter, shipmentFetcher, addressCreator, portLocationFetcher, ghcrateengine.NewDomesticUnpackPricer(), ghcrateengine.NewDomesticLinehaulPricer(), ghcrateengine.NewDomesticDestinationPricer(), ghcrateengine.NewFuelSurchargePricer()) var originFirstDaySIT models.MTOServiceItem var originAdditionalDaySIT models.MTOServiceItem @@ -10467,7 +10467,8 @@ func CreateNeedsServicesCounseling(appCtx appcontext.AppContext, ordersType inte requestedPickupDate = submittedAt.Add(30 * 24 * time.Hour) requestedDeliveryDate = requestedPickupDate.Add(7 * 24 * time.Hour) - regularMTOShipment := factory.BuildMTOShipment(db, []factory.Customization{ + + factory.BuildMTOShipment(db, []factory.Customization{ { Model: move, LinkOnly: true, @@ -10499,29 +10500,6 @@ func CreateNeedsServicesCounseling(appCtx appcontext.AppContext, ordersType inte }, }, nil) - if shipmentType == models.MTOShipmentTypeMobileHome { - factory.BuildMobileHomeShipment(appCtx.DB(), []factory.Customization{ - { - Model: models.MobileHome{ - Year: models.IntPointer(2000), - Make: models.StringPointer("Boat Make"), - Model: models.StringPointer("Boat Model"), - LengthInInches: models.IntPointer(300), - WidthInInches: models.IntPointer(108), - HeightInInches: models.IntPointer(72), - }, - }, - { - Model: move, - LinkOnly: true, - }, - { - Model: regularMTOShipment, - LinkOnly: true, - }, - }, nil) - } - return move } diff --git a/pkg/testdatagen/testharness/dispatch.go b/pkg/testdatagen/testharness/dispatch.go index 0ed11caf74a..d008fd696c6 100644 --- a/pkg/testdatagen/testharness/dispatch.go +++ b/pkg/testdatagen/testharness/dispatch.go @@ -104,6 +104,9 @@ var actionDispatcher = map[string]actionFunc{ "HHGMoveWithIntlCratingServiceItemsTOO": func(appCtx appcontext.AppContext) testHarnessResponse { return MakeHHGMoveWithIntlCratingServiceItemsTOO(appCtx) }, + "HHGMoveWithIntlShuttleServiceItemsTOO": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeHHGMoveWithIntlShuttleServiceItemsTOO(appCtx) + }, "HHGMoveForTOOAfterActualPickupDate": func(appCtx appcontext.AppContext) testHarnessResponse { return MakeHHGMoveForTOOAfterActualPickupDate(appCtx) }, @@ -272,6 +275,298 @@ var actionDispatcher = map[string]actionFunc{ "InternationalHHGMoveWithServiceItemsandPaymentRequestsForTIO": func(appCtx appcontext.AppContext) testHarnessResponse { return MakeBasicInternationalHHGMoveWithServiceItemsandPaymentRequestsForTIO(appCtx) }, + "IntlHHGMoveWithCratingUncratingServiceItemsAndPaymentRequestsForTIO": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveWithCratingUncratingServiceItemsAndPaymentRequestsForTIO(appCtx) + }, + // basic iHHG move with CONUS -> AK needing TOO approval + "IntlHHGMoveDestAKZone1Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestAKZone1Army(appCtx) + }, + "IntlHHGMoveDestAKZone2Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestAKZone2Army(appCtx) + }, + "IntlHHGMoveDestAKZone1AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestAKZone1AirForce(appCtx) + }, + "IntlHHGMoveDestAKZone2AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestAKZone2AirForce(appCtx) + }, + "IntlHHGMoveDestAKZone1SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestAKZone1SpaceForce(appCtx) + }, + "IntlHHGMoveDestAKZone2SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestAKZone2SpaceForce(appCtx) + }, + "IntlHHGMoveDestAKZone1USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestAKZone1USMC(appCtx) + }, + "IntlHHGMoveDestAKZone2USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestAKZone2USMC(appCtx) + }, + // basic iHHG move with AK -> CONUS needing TOO approval + "IntlHHGMovePickupAKZone1Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMovePickupAKZone1Army(appCtx) + }, + "IntlHHGMovePickupAKZone2Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMovePickupAKZone2Army(appCtx) + }, + "IntlHHGMovePickupAKZone1AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMovePickupAKZone1AirForce(appCtx) + }, + "IntlHHGMovePickupAKZone2AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMovePickupAKZone2AirForce(appCtx) + }, + "IntlHHGMovePickupAKZone1SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMovePickupAKZone1SpaceForce(appCtx) + }, + "IntlHHGMovePickupAKZone2SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMovePickupAKZone2SpaceForce(appCtx) + }, + "IntlHHGMovePickupAKZone1USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMovePickupAKZone1USMC(appCtx) + }, + "IntlHHGMovePickupAKZone2USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMovePickupAKZone2USMC(appCtx) + }, + // iHHG with international origin SIT in SUBMITTED status + "IntlHHGMoveOriginSITRequestedAKZone1Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginSITRequestedAKZone1Army(appCtx) + }, + "IntlHHGMoveOriginSITRequestedAKZone2Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginSITRequestedAKZone2Army(appCtx) + }, + "IntlHHGMoveOriginSITRequestedAKZone1AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginSITRequestedAKZone1AirForce(appCtx) + }, + "IntlHHGMoveOriginSITRequestedAKZone2AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginSITRequestedAKZone2AirForce(appCtx) + }, + "IntlHHGMoveOriginSITRequestedAKZone1SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginSITRequestedAKZone1SpaceForce(appCtx) + }, + "IntlHHGMoveOriginSITRequestedAKZone2SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginSITRequestedAKZone2SpaceForce(appCtx) + }, + "IntlHHGMoveOriginSITRequestedAKZone1USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginSITRequestedAKZone1USMC(appCtx) + }, + "IntlHHGMoveOriginSITRequestedAKZone2USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginSITRequestedAKZone2USMC(appCtx) + }, + // iHHG with international destination SIT in SUBMITTED status + "IntlHHGMoveDestSITRequestedAKZone1Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestSITRequestedAKZone1Army(appCtx) + }, + "IntlHHGMoveDestSITRequestedAKZone2Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestSITRequestedAKZone2Army(appCtx) + }, + "IntlHHGMoveDestSITRequestedAKZone1AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestSITRequestedAKZone1AirForce(appCtx) + }, + "IntlHHGMoveDestSITRequestedAKZone2AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestSITRequestedAKZone2AirForce(appCtx) + }, + "IntlHHGMoveDestSITRequestedAKZone1SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestSITRequestedAKZone1SpaceForce(appCtx) + }, + "IntlHHGMoveDestSITRequestedAKZone2SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestSITRequestedAKZone2SpaceForce(appCtx) + }, + "IntlHHGMoveDestSITRequestedAKZone1USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestSITRequestedAKZone1USMC(appCtx) + }, + "IntlHHGMoveDestSITRequestedAKZone2USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestSITRequestedAKZone2USMC(appCtx) + }, + // iHHG with BOTH international origin & destination SIT in SUBMITTED status + "IntlHHGMoveBothSITRequestedAKZone1Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveBothSITRequestedAKZone1Army(appCtx) + }, + "IntlHHGMoveBothSITRequestedAKZone2Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveBothSITRequestedAKZone2Army(appCtx) + }, + "IntlHHGMoveBothSITRequestedAKZone1AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveBothSITRequestedAKZone1AirForce(appCtx) + }, + "IntlHHGMoveBothSITRequestedAKZone2AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveBothSITRequestedAKZone2AirForce(appCtx) + }, + "IntlHHGMoveBothSITRequestedAKZone1SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveBothSITRequestedAKZone1SpaceForce(appCtx) + }, + "IntlHHGMoveBothSITRequestedAKZone2SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveBothSITRequestedAKZone2SpaceForce(appCtx) + }, + "IntlHHGMoveBothSITRequestedAKZone1USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveBothSITRequestedAKZone1USMC(appCtx) + }, + "IntlHHGMoveBothSITRequestedAKZone2USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveBothSITRequestedAKZone2USMC(appCtx) + }, + // iHHG with international origin shuttle in SUBMITTED status + "IntlHHGMoveOriginShuttleRequestedAKZone1Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginShuttleRequestedAKZone1Army(appCtx) + }, + "IntlHHGMoveOriginShuttleRequestedAKZone2Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginShuttleRequestedAKZone2Army(appCtx) + }, + "IntlHHGMoveOriginShuttleRequestedAKZone1AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginShuttleRequestedAKZone1AirForce(appCtx) + }, + "IntlHHGMoveOriginShuttleRequestedAKZone2AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginShuttleRequestedAKZone2AirForce(appCtx) + }, + "IntlHHGMoveOriginShuttleRequestedAKZone1SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginShuttleRequestedAKZone1SpaceForce(appCtx) + }, + "IntlHHGMoveOriginShuttleRequestedAKZone2SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginShuttleRequestedAKZone2SpaceForce(appCtx) + }, + "IntlHHGMoveOriginShuttleRequestedAKZone1USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginShuttleRequestedAKZone1USMC(appCtx) + }, + "IntlHHGMoveOriginShuttleRequestedAKZone2USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginShuttleRequestedAKZone2USMC(appCtx) + }, + // iHHG with international destination shuttle in SUBMITTED status + "IntlHHGMoveDestShuttleRequestedAKZone1Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestShuttleRequestedAKZone1Army(appCtx) + }, + "IntlHHGMoveDestShuttleRequestedAKZone2Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestShuttleRequestedAKZone2Army(appCtx) + }, + "IntlHHGMoveDestShuttleRequestedAKZone1AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestShuttleRequestedAKZone1AirForce(appCtx) + }, + "IntlHHGMoveDestShuttleRequestedAKZone2AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestShuttleRequestedAKZone2AirForce(appCtx) + }, + "IntlHHGMoveDestShuttleRequestedAKZone1SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestShuttleRequestedAKZone1SpaceForce(appCtx) + }, + "IntlHHGMoveDestShuttleRequestedAKZone2SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestShuttleRequestedAKZone2SpaceForce(appCtx) + }, + "IntlHHGMoveDestShuttleRequestedAKZone1USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestShuttleRequestedAKZone1USMC(appCtx) + }, + "IntlHHGMoveDestShuttleRequestedAKZone2USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestShuttleRequestedAKZone2USMC(appCtx) + }, + // iHHG with BOTH international origin & destination shuttle in SUBMITTED status + "IntlHHGMoveBothShuttleRequestedAKZone1Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveBothShuttleRequestedAKZone1Army(appCtx) + }, + "IntlHHGMoveBothShuttleRequestedAKZone2Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveBothShuttleRequestedAKZone2Army(appCtx) + }, + "IntlHHGMoveBothShuttleRequestedAKZone1AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveBothShuttleRequestedAKZone1AirForce(appCtx) + }, + "IntlHHGMoveBothShuttleRequestedAKZone2AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveBothShuttleRequestedAKZone2AirForce(appCtx) + }, + "IntlHHGMoveBothShuttleRequestedAKZone1SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveBothShuttleRequestedAKZone1SpaceForce(appCtx) + }, + "IntlHHGMoveBothShuttleRequestedAKZone2SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveBothShuttleRequestedAKZone2SpaceForce(appCtx) + }, + "IntlHHGMoveBothShuttleRequestedAKZone1USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveBothShuttleRequestedAKZone1USMC(appCtx) + }, + "IntlHHGMoveBothShuttleRequestedAKZone2USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveBothShuttleRequestedAKZone2USMC(appCtx) + }, + // iHHG with a destination address request in REQUESTED status + "IntlHHGMoveDestAddressRequestedAKZone1Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestAddressRequestedAKZone1Army(appCtx) + }, + "IntlHHGMoveDestAddressRequestedAKZone2Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestAddressRequestedAKZone2Army(appCtx) + }, + "IntlHHGMoveDestAddressRequestedAKZone1AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestAddressRequestedAKZone1AirForce(appCtx) + }, + "IntlHHGMoveDestAddressRequestedAKZone2AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestAddressRequestedAKZone2AirForce(appCtx) + }, + "IntlHHGMoveDestAddressRequestedAKZone1SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestAddressRequestedAKZone1SpaceForce(appCtx) + }, + "IntlHHGMoveDestAddressRequestedAKZone2SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestAddressRequestedAKZone2SpaceForce(appCtx) + }, + "IntlHHGMoveDestAddressRequestedAKZone1USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestAddressRequestedAKZone1USMC(appCtx) + }, + "IntlHHGMoveDestAddressRequestedAKZone2USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestAddressRequestedAKZone2USMC(appCtx) + }, + // iHHG with a PENDING SIT extension request containing origin SIT + "IntlHHGMoveOriginSITExtensionRequestedAKZone1Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginSITExtensionRequestedAKZone1Army(appCtx) + }, + "IntlHHGMoveOriginSITExtensionRequestedAKZone2Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginSITExtensionRequestedAKZone2Army(appCtx) + }, + "IntlHHGMoveOriginSITExtensionRequestedAKZone1AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginSITExtensionRequestedAKZone1AirForce(appCtx) + }, + "IntlHHGMoveOriginSITExtensionRequestedAKZone2AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginSITExtensionRequestedAKZone2AirForce(appCtx) + }, + "IntlHHGMoveOriginSITExtensionRequestedAKZone1SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginSITExtensionRequestedAKZone1SpaceForce(appCtx) + }, + "IntlHHGMoveOriginSITExtensionRequestedAKZone2SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginSITExtensionRequestedAKZone2SpaceForce(appCtx) + }, + "IntlHHGMoveOriginSITExtensionRequestedAKZone1USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginSITExtensionRequestedAKZone1USMC(appCtx) + }, + "IntlHHGMoveOriginSITExtensionRequestedAKZone2USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveOriginSITExtensionRequestedAKZone2USMC(appCtx) + }, + // iHHG with a PENDING SIT extension request containing destination SIT + "IntlHHGMoveDestSITExtensionRequestedAKZone1Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestSITExtensionRequestedAKZone1Army(appCtx) + }, + "IntlHHGMoveDestSITExtensionRequestedAKZone2Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestSITExtensionRequestedAKZone2Army(appCtx) + }, + "IntlHHGMoveDestSITExtensionRequestedAKZone1AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestSITExtensionRequestedAKZone1AirForce(appCtx) + }, + "IntlHHGMoveDestSITExtensionRequestedAKZone2AirForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestSITExtensionRequestedAKZone2AirForce(appCtx) + }, + "IntlHHGMoveDestSITExtensionRequestedAKZone1SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestSITExtensionRequestedAKZone1SpaceForce(appCtx) + }, + "IntlHHGMoveDestSITExtensionRequestedAKZone2SpaceForce": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestSITExtensionRequestedAKZone2SpaceForce(appCtx) + }, + "IntlHHGMoveDestSITExtensionRequestedAKZone1USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestSITExtensionRequestedAKZone1USMC(appCtx) + }, + "IntlHHGMoveDestSITExtensionRequestedAKZone2USMC": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveDestSITExtensionRequestedAKZone2USMC(appCtx) + }, + // iHHG with a PENDING excess weight notification + "IntlHHGMoveExcessWeightAKZone1Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveExcessWeightAKZone1Army(appCtx) + }, + "IntlHHGMoveExcessWeightAKZone2Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlHHGMoveExcessWeightAKZone2Army(appCtx) + }, + // iUB with a PENDING excess UB weight notification + "IntlUBMoveExcessWeightAKZone1Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlUBMoveExcessWeightAKZone1Army(appCtx) + }, + "IntlUBMoveExcessWeightAKZone2Army": func(appCtx appcontext.AppContext) testHarnessResponse { + return MakeIntlUBMoveExcessWeightAKZone2Army(appCtx) + }, } func Actions() []string { diff --git a/pkg/testdatagen/testharness/make_move.go b/pkg/testdatagen/testharness/make_move.go index 3e2f5fed100..5c8909b983b 100644 --- a/pkg/testdatagen/testharness/make_move.go +++ b/pkg/testdatagen/testharness/make_move.go @@ -729,6 +729,197 @@ func MakeHHGMoveWithIntlCratingServiceItemsTOO(appCtx appcontext.AppContext) mod return *newmove } +// MakeHHGMoveWithIntlShuttleServiceItemsTOO is a function +// that creates an HHG move with international service items +// from the Prime for review by the TOO +func MakeHHGMoveWithIntlShuttleServiceItemsTOO(appCtx appcontext.AppContext) models.Move { + userUploader := newUserUploader(appCtx) + primeUploader := newPrimeUploader(appCtx) + userInfo := newUserInfo("customer") + + user := factory.BuildUser(appCtx.DB(), []factory.Customization{ + { + Model: models.User{ + OktaEmail: userInfo.email, + Active: true, + }, + }, + }, nil) + customer := factory.BuildExtendedServiceMember(appCtx.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ + PersonalEmail: &userInfo.email, + FirstName: &userInfo.firstName, + LastName: &userInfo.lastName, + CacValidated: true, + }, + }, + { + Model: user, + LinkOnly: true, + }, + }, nil) + dependentsAuthorized := true + entitlements := factory.BuildEntitlement(appCtx.DB(), []factory.Customization{ + { + Model: models.Entitlement{ + DependentsAuthorized: &dependentsAuthorized, + }, + }, + }, nil) + orders := factory.BuildOrder(appCtx.DB(), []factory.Customization{ + { + Model: customer, + LinkOnly: true, + }, + { + Model: entitlements, + LinkOnly: true, + }, + { + Model: models.UserUpload{}, + ExtendedParams: &factory.UserUploadExtendedParams{ + UserUploader: userUploader, + AppContext: appCtx, + }, + }, + }, nil) + mto := factory.BuildMove(appCtx.DB(), []factory.Customization{ + { + Model: orders, + LinkOnly: true, + }, + { + Model: models.Move{ + Status: models.MoveStatusSUBMITTED, + }, + }, + }, nil) + estimatedWeight := unit.Pound(1400) + actualWeight := unit.Pound(2000) + actualPickupDate := time.Now().AddDate(0, 0, 1) + + MTOShipment := factory.BuildMTOShipment(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + PrimeEstimatedWeight: &estimatedWeight, + PrimeActualWeight: &actualWeight, + ShipmentType: models.MTOShipmentTypeHHG, + Status: models.MTOShipmentStatusSubmitted, + ActualPickupDate: &actualPickupDate, + }, + }, + { + Model: mto, + LinkOnly: true, + }, + }, nil) + + agentUserInfo := newUserInfo("agent") + factory.BuildMTOAgent(appCtx.DB(), []factory.Customization{ + { + Model: MTOShipment, + LinkOnly: true, + }, + { + Model: models.MTOAgent{ + FirstName: &agentUserInfo.firstName, + LastName: &agentUserInfo.lastName, + Email: &agentUserInfo.email, + MTOAgentType: models.MTOAgentReleasing, + }, + }, + }, nil) + + paymentRequest := factory.BuildPaymentRequest(appCtx.DB(), []factory.Customization{ + { + Model: models.PaymentRequest{ + IsFinal: false, + Status: models.PaymentRequestStatusPending, + }, + }, + { + Model: mto, + LinkOnly: true, + }, + }, nil) + + _ = factory.BuildMTOServiceItem(appCtx.DB(), []factory.Customization{ + { + Model: mto, + LinkOnly: true, + }, + { + Model: MTOShipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + ID: uuid.FromStringOrNil("22fc07ed-be15-4f50-b941-cbd38153b378"), // IDSHUT - International Destination Shuttle + }, + }, + }, nil) + + _ = factory.BuildMTOServiceItem(appCtx.DB(), []factory.Customization{ + { + Model: mto, + LinkOnly: true, + }, + { + Model: MTOShipment, + LinkOnly: true, + }, + { + Model: models.ReService{ + ID: uuid.FromStringOrNil("624a97c5-dfbf-4da9-a6e9-526b4f95af8d"), // IOSHUT - International Origin Shuttle + }, + }, + }, nil) + + factory.BuildPrimeUpload(appCtx.DB(), []factory.Customization{ + { + Model: paymentRequest, + LinkOnly: true, + }, + }, nil) + posImage := factory.BuildProofOfServiceDoc(appCtx.DB(), []factory.Customization{ + { + Model: paymentRequest, + LinkOnly: true, + }, + }, nil) + primeContractor := uuid.FromStringOrNil("5db13bb4-6d29-4bdb-bc81-262f4513ecf6") + + // Creates custom test.jpg prime upload + file := testdatagen.Fixture("test.jpg") + _, verrs, err := primeUploader.CreatePrimeUploadForDocument(appCtx, &posImage.ID, primeContractor, uploader.File{File: file}, uploader.AllowedTypesPaymentRequest) + if verrs.HasAny() || err != nil { + appCtx.Logger().Error("errors encountered saving test.jpg prime upload", zap.Error(err)) + } + + // Creates custom test.png prime upload + file = testdatagen.Fixture("test.png") + _, verrs, err = primeUploader.CreatePrimeUploadForDocument(appCtx, &posImage.ID, primeContractor, uploader.File{File: file}, uploader.AllowedTypesPaymentRequest) + if verrs.HasAny() || err != nil { + appCtx.Logger().Error("errors encountered saving test.png prime upload", zap.Error(err)) + } + + // re-fetch the move so that we ensure we have exactly what is in + // the db + newmove, err := models.FetchMove(appCtx.DB(), &auth.Session{}, mto.ID) + if err != nil { + log.Panic(fmt.Errorf("failed to fetch move: %w", err)) + } + + // load payment requests so tests can confirm + err = appCtx.DB().Load(newmove, "PaymentRequests") + if err != nil { + log.Panic(fmt.Errorf("failed to fetch move payment requestse: %w", err)) + } + + return *newmove +} + // MakeHHGMoveForTOOAfterActualPickupDate is a function // that creates an HHG move with an actual pickup date in the past for diversion testing // copied almost verbatim from e2ebasic createHHGMoveWithServiceItemsAndPaymentRequestsAndFiles @@ -8916,6 +9107,7 @@ func MakeBasicInternationalHHGMoveWithServiceItemsandPaymentRequestsForTIO(appCt ihpkCost := unit.Cents(298800) ihupkCost := unit.Cents(33280) poefscCost := unit.Cents(25000) + idshutCost := unit.Cents(623) // Create Customer userInfo := newUserInfo("customer") @@ -9250,6 +9442,47 @@ func MakeBasicInternationalHHGMoveWithServiceItemsandPaymentRequestsForTIO(appCt }, }, nil) + // Shuttling service item + approvedAtTime := time.Now() + idshut := factory.BuildMTOServiceItem(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + ApprovedAt: &approvedAtTime, + EstimatedWeight: &estimatedWeight, + ActualWeight: &actualWeight, + }, + }, + { + Model: mto, + LinkOnly: true, + }, + { + Model: mtoShipmentHHG, + LinkOnly: true, + }, + { + Model: models.ReService{ + ID: uuid.FromStringOrNil("22fc07ed-be15-4f50-b941-cbd38153b378"), // IDSHUT - International Destination Shuttle + }, + }, + }, nil) + + factory.BuildPaymentServiceItemWithParams(appCtx.DB(), models.ReServiceCodeIDSHUT, + basicPaymentServiceItemParams, []factory.Customization{ + { + Model: models.PaymentServiceItem{ + PriceCents: &idshutCost, + }, + }, { + Model: paymentRequestHHG, + LinkOnly: true, + }, { + Model: idshut, + LinkOnly: true, + }, + }, nil) + basicPortFuelSurchargePaymentServiceItemParams := []factory.CreatePaymentServiceItemParams{ { Key: models.ServiceItemParamNameContractCode, @@ -9380,3 +9613,2078 @@ func MakeBasicInternationalHHGMoveWithServiceItemsandPaymentRequestsForTIO(appCt return *newmove } + +// MakeIntlHHGMoveWithCratingUncratingServiceItemsAndPaymentRequestsForTIO creates an iHHG move +// that has been approved by TOO & prime has requested payment for intl crating and uncrating service items +func MakeIntlHHGMoveWithCratingUncratingServiceItemsAndPaymentRequestsForTIO(appCtx appcontext.AppContext) models.Move { + userUploader := newUserUploader(appCtx) + + // Create Customer + userInfo := newUserInfo("customer") + customer := factory.BuildExtendedServiceMember(appCtx.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ + PersonalEmail: &userInfo.email, + FirstName: &userInfo.firstName, + LastName: &userInfo.lastName, + CacValidated: true, + }, + }, + }, nil) + + // address setup + addressAK := factory.BuildAddress(appCtx.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "123 Cold St", + City: "Anchorage", + State: "AK", + PostalCode: "99505", + }, + }, + }, nil) + destDutyLocationAK := factory.BuildDutyLocation(appCtx.DB(), []factory.Customization{ + { + Model: addressAK, + LinkOnly: true, + }, + }, nil) + + // orders setup using AK destination duty location + orders := factory.BuildOrder(appCtx.DB(), []factory.Customization{ + { + Model: customer, + LinkOnly: true, + }, + { + Model: models.UserUpload{}, + ExtendedParams: &factory.UserUploadExtendedParams{ + UserUploader: userUploader, + AppContext: appCtx, + }, + }, + { + Model: models.Order{ + NewDutyLocationID: destDutyLocationAK.ID, + }, + }, + }, nil) + + mto := factory.BuildMove(appCtx.DB(), []factory.Customization{ + { + Model: orders, + LinkOnly: true, + }, + { + Model: models.Move{ + AvailableToPrimeAt: models.TimePointer(time.Now()), + }, + }, + }, nil) + + shipmentPickupAddress := factory.BuildAddress(appCtx.DB(), []factory.Customization{ + { + Model: models.Address{ + // This is a postal code that maps to the default office user gbloc KKFA in the PostalCodeToGBLOC table + PostalCode: "85004", + }, + }, + }, nil) + alaskaDestAddress := factory.BuildAddress(appCtx.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "123 Cold St", + City: "Anchorage", + State: "AK", + PostalCode: "99505", + IsOconus: models.BoolPointer(true), + }, + }, + }, nil) + + estimatedWeight := unit.Pound(2000) + actualWeight := unit.Pound(2000) + mtoShipmentHHG := factory.BuildMTOShipment(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + PrimeEstimatedWeight: &estimatedWeight, + PrimeActualWeight: &actualWeight, + ShipmentType: models.MTOShipmentTypeHHG, + ApprovedDate: models.TimePointer(time.Now()), + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: shipmentPickupAddress, + LinkOnly: true, + Type: &factory.Addresses.PickupAddress, + }, + { + Model: alaskaDestAddress, + LinkOnly: true, + Type: &factory.Addresses.DeliveryAddress, + }, + { + Model: mto, + LinkOnly: true, + }, + }, nil) + + // Create Releasing Agent + agentUserInfo := newUserInfo("agent") + factory.BuildMTOAgent(appCtx.DB(), []factory.Customization{ + { + Model: mtoShipmentHHG, + LinkOnly: true, + }, + { + Model: models.MTOAgent{ + ID: uuid.Must(uuid.NewV4()), + FirstName: &agentUserInfo.firstName, + LastName: &agentUserInfo.lastName, + Email: &agentUserInfo.email, + MTOAgentType: models.MTOAgentReleasing, + }, + }, + }, nil) + + paymentRequestHHG := factory.BuildPaymentRequest(appCtx.DB(), []factory.Customization{ + { + Model: models.PaymentRequest{ + IsFinal: false, + Status: models.PaymentRequestStatusPending, + RejectionReason: nil, + }, + }, + { + Model: mto, + LinkOnly: true, + }, + }, nil) + + // for soft deleted proof of service docs + factory.BuildPrimeUpload(appCtx.DB(), []factory.Customization{ + { + Model: paymentRequestHHG, + LinkOnly: true, + }, + }, []factory.Trait{factory.GetTraitPrimeUploadDeleted}) + + currentTime := time.Now() + + cratingPaymentServiceItemParams := []factory.CreatePaymentServiceItemParams{ + { + Key: models.ServiceItemParamNameContractCode, + KeyType: models.ServiceItemParamTypeString, + Value: factory.DefaultContractCode, + }, + { + Key: models.ServiceItemParamNameEscalationCompounded, + KeyType: models.ServiceItemParamTypeString, + Value: strconv.FormatFloat(1.125, 'f', 5, 64), + }, + { + Key: models.ServiceItemParamNamePriceRateOrFactor, + KeyType: models.ServiceItemParamTypeString, + Value: "1.71", + }, + { + Key: models.ServiceItemParamNameCubicFeetBilled, + KeyType: models.ServiceItemParamTypeDecimal, + Value: "4.00", + }, + { + Key: models.ServiceItemParamNameCubicFeetCrating, + KeyType: models.ServiceItemParamTypeDecimal, + Value: "1", + }, + { + Key: models.ServiceItemParamNameReferenceDate, + KeyType: models.ServiceItemParamTypeDate, + Value: currentTime.Format("2006-01-02"), + }, + { + Key: models.ServiceItemParamNameStandaloneCrate, + KeyType: models.ServiceItemParamTypeBoolean, + Value: strconv.FormatBool(true), + }, + { + Key: models.ServiceItemParamNameStandaloneCrateCap, + KeyType: models.ServiceItemParamTypeInteger, + Value: strconv.FormatInt(100000, 10), + }, + { + Key: models.ServiceItemParamNameMarketOrigin, + KeyType: models.ServiceItemParamTypeString, + Value: "O", + }, + { + Key: models.ServiceItemParamNameExternalCrate, + KeyType: models.ServiceItemParamTypeBoolean, + Value: strconv.FormatBool(true), + }, + { + Key: models.ServiceItemParamNameDimensionHeight, + KeyType: models.ServiceItemParamTypeString, + Value: "1", + }, + { + Key: models.ServiceItemParamNameDimensionLength, + KeyType: models.ServiceItemParamTypeString, + Value: "1", + }, + { + Key: models.ServiceItemParamNameDimensionWidth, + KeyType: models.ServiceItemParamTypeString, + Value: "1", + }, + } + desc := "description test" + icrt := factory.BuildMTOServiceItem(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + Description: &desc, + StandaloneCrate: models.BoolPointer(true), + ExternalCrate: models.BoolPointer(true), + }, + }, + { + Model: mto, + LinkOnly: true, + }, + { + Model: mtoShipmentHHG, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeICRT, + }, + }, + }, nil) + + factory.BuildPaymentServiceItemWithParams(appCtx.DB(), models.ReServiceCodeICRT, + cratingPaymentServiceItemParams, []factory.Customization{ + { + Model: mto, + LinkOnly: true, + }, + { + Model: mtoShipmentHHG, + LinkOnly: true, + }, + { + Model: paymentRequestHHG, + LinkOnly: true, + }, + { + Model: icrt, + LinkOnly: true, + }, + }, nil) + + iucrt := factory.BuildMTOServiceItem(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + Description: &desc, + }, + }, + { + Model: mto, + LinkOnly: true, + }, + { + Model: mtoShipmentHHG, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeIUCRT, + }, + }, + }, nil) + + unCratingPaymentServiceItemParams := []factory.CreatePaymentServiceItemParams{ + { + Key: models.ServiceItemParamNameContractCode, + KeyType: models.ServiceItemParamTypeString, + Value: factory.DefaultContractCode, + }, + { + Key: models.ServiceItemParamNameEscalationCompounded, + KeyType: models.ServiceItemParamTypeString, + Value: strconv.FormatFloat(1.125, 'f', 5, 64), + }, + { + Key: models.ServiceItemParamNamePriceRateOrFactor, + KeyType: models.ServiceItemParamTypeString, + Value: "1.71", + }, + { + Key: models.ServiceItemParamNameCubicFeetBilled, + KeyType: models.ServiceItemParamTypeDecimal, + Value: "8", + }, + { + Key: models.ServiceItemParamNameReferenceDate, + KeyType: models.ServiceItemParamTypeDate, + Value: currentTime.Format("2006-01-02"), + }, + { + Key: models.ServiceItemParamNameMarketDest, + KeyType: models.ServiceItemParamTypeString, + Value: "O", + }, + { + Key: models.ServiceItemParamNameDimensionHeight, + KeyType: models.ServiceItemParamTypeString, + Value: "2", + }, + { + Key: models.ServiceItemParamNameDimensionLength, + KeyType: models.ServiceItemParamTypeString, + Value: "2", + }, + { + Key: models.ServiceItemParamNameDimensionWidth, + KeyType: models.ServiceItemParamTypeString, + Value: "2", + }, + } + + factory.BuildPaymentServiceItemWithParams(appCtx.DB(), models.ReServiceCodeIUCRT, + unCratingPaymentServiceItemParams, []factory.Customization{ + { + Model: mto, + LinkOnly: true, + }, + { + Model: mtoShipmentHHG, + LinkOnly: true, + }, + { + Model: paymentRequestHHG, + LinkOnly: true, + }, + { + Model: iucrt, + LinkOnly: true, + }, + }, nil) + + // re-fetch the move so that we ensure we have exactly what is in + // the db + newmove, err := models.FetchMove(appCtx.DB(), &auth.Session{}, mto.ID) + if err != nil { + log.Panic(fmt.Errorf("failed to fetch move: %w", err)) + } + + // load payment requests so tests can confirm + err = appCtx.DB().Load(newmove, "PaymentRequests") + if err != nil { + log.Panic(fmt.Errorf("failed to fetch move payment requestse: %w", err)) + } + + return *newmove +} + +// makeIntlHHGMoveCONUSToAKSubmitted creates an international HHG move +// with the given affiliation and destination address +// basic iHHG move that will require TOO approval +func makeIntlHHGMoveCONUSToAKSubmitted( + appCtx appcontext.AppContext, + affiliation models.ServiceMemberAffiliation, + streetAddress, city, state, postalCode string, +) models.Move { + userUploader := newUserUploader(appCtx) + userInfo := newUserInfo("customer") + + user := factory.BuildUser(appCtx.DB(), []factory.Customization{ + { + Model: models.User{ + OktaEmail: userInfo.email, + Active: true, + }, + }, + }, nil) + + customer := factory.BuildExtendedServiceMember(appCtx.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ + PersonalEmail: &userInfo.email, + FirstName: &userInfo.firstName, + LastName: &userInfo.lastName, + CacValidated: true, + Affiliation: &affiliation, + }, + }, + { + Model: user, + LinkOnly: true, + }, + }, nil) + + dependentsAuthorized := true + sitDaysAllowance := 90 + entitlements := factory.BuildEntitlement(appCtx.DB(), []factory.Customization{ + { + Model: models.Entitlement{ + DependentsAuthorized: &dependentsAuthorized, + StorageInTransit: &sitDaysAllowance, + }, + }, + }, nil) + + orders := factory.BuildOrder(appCtx.DB(), []factory.Customization{ + { + Model: customer, + LinkOnly: true, + }, + { + Model: entitlements, + LinkOnly: true, + }, + { + Model: models.UserUpload{}, + ExtendedParams: &factory.UserUploadExtendedParams{ + UserUploader: userUploader, + AppContext: appCtx, + }, + }, + }, nil) + + now := time.Now() + move := factory.BuildMove(appCtx.DB(), []factory.Customization{ + { + Model: orders, + LinkOnly: true, + }, + { + Model: models.Move{ + Status: models.MoveStatusServiceCounselingCompleted, + AvailableToPrimeAt: &now, + }, + }, + }, nil) + + requestedPickupDate := now.AddDate(0, 3, 0) + requestedDeliveryDate := requestedPickupDate.AddDate(0, 1, 0) + + // build the destination address using the passed-in parameters. + address := factory.BuildAddress(appCtx.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: streetAddress, + City: city, + State: state, + PostalCode: postalCode, + IsOconus: models.BoolPointer(true), + }, + }, + }, nil) + + shipment := factory.BuildMTOShipment(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + ShipmentType: models.MTOShipmentTypeHHG, + Status: models.MTOShipmentStatusSubmitted, + RequestedPickupDate: &requestedPickupDate, + RequestedDeliveryDate: &requestedDeliveryDate, + SITDaysAllowance: &sitDaysAllowance, + DestinationAddressID: &address.ID, + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + agentUserInfo := newUserInfo("agent") + factory.BuildMTOAgent(appCtx.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.MTOAgent{ + FirstName: &agentUserInfo.firstName, + LastName: &agentUserInfo.lastName, + Email: &agentUserInfo.email, + MTOAgentType: models.MTOAgentReleasing, + }, + }, + }, nil) + + return move +} + +// these create an iHHG move with selected affiliation, destination of either Anchorage, AK (Zone I) or Fairbanks, AK (Zone II) +// contains an HHG shipment in SUBMITTED status that requires TOO approval +func MakeIntlHHGMoveDestAKZone1Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveCONUSToAKSubmitted(appCtx, models.AffiliationARMY, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestAKZone2Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveCONUSToAKSubmitted(appCtx, models.AffiliationARMY, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveDestAKZone1AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveCONUSToAKSubmitted(appCtx, models.AffiliationAIRFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestAKZone2AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveCONUSToAKSubmitted(appCtx, models.AffiliationAIRFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveDestAKZone1SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveCONUSToAKSubmitted(appCtx, models.AffiliationSPACEFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestAKZone2SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveCONUSToAKSubmitted(appCtx, models.AffiliationSPACEFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveDestAKZone1USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveCONUSToAKSubmitted(appCtx, models.AffiliationMARINES, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestAKZone2USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveCONUSToAKSubmitted(appCtx, models.AffiliationMARINES, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +// makeIntlHHGMoveAKToCONUSSubmitted creates an international HHG move +// with the given affiliation and pickup address +// basic iHHG move that will require TOO approval +func makeIntlHHGMoveAKToCONUSSubmitted( + appCtx appcontext.AppContext, + affiliation models.ServiceMemberAffiliation, + streetAddress, city, state, postalCode string, +) models.Move { + userUploader := newUserUploader(appCtx) + userInfo := newUserInfo("customer") + + user := factory.BuildUser(appCtx.DB(), []factory.Customization{ + { + Model: models.User{ + OktaEmail: userInfo.email, + Active: true, + }, + }, + }, nil) + + customer := factory.BuildExtendedServiceMember(appCtx.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ + PersonalEmail: &userInfo.email, + FirstName: &userInfo.firstName, + LastName: &userInfo.lastName, + CacValidated: true, + Affiliation: &affiliation, + }, + }, + { + Model: user, + LinkOnly: true, + }, + }, nil) + + dependentsAuthorized := true + sitDaysAllowance := 90 + entitlements := factory.BuildEntitlement(appCtx.DB(), []factory.Customization{ + { + Model: models.Entitlement{ + DependentsAuthorized: &dependentsAuthorized, + StorageInTransit: &sitDaysAllowance, + }, + }, + }, nil) + + orders := factory.BuildOrder(appCtx.DB(), []factory.Customization{ + { + Model: customer, + LinkOnly: true, + }, + { + Model: entitlements, + LinkOnly: true, + }, + { + Model: models.UserUpload{}, + ExtendedParams: &factory.UserUploadExtendedParams{ + UserUploader: userUploader, + AppContext: appCtx, + }, + }, + }, nil) + + now := time.Now() + move := factory.BuildMove(appCtx.DB(), []factory.Customization{ + { + Model: orders, + LinkOnly: true, + }, + { + Model: models.Move{ + Status: models.MoveStatusServiceCounselingCompleted, + AvailableToPrimeAt: &now, + }, + }, + }, nil) + + requestedPickupDate := now.AddDate(0, 3, 0) + requestedDeliveryDate := requestedPickupDate.AddDate(0, 1, 0) + + // build the pickup address using the passed-in parameters. + address := factory.BuildAddress(appCtx.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: streetAddress, + City: city, + State: state, + PostalCode: postalCode, + IsOconus: models.BoolPointer(true), + }, + }, + }, nil) + + shipment := factory.BuildMTOShipment(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + ShipmentType: models.MTOShipmentTypeHHG, + Status: models.MTOShipmentStatusSubmitted, + RequestedPickupDate: &requestedPickupDate, + RequestedDeliveryDate: &requestedDeliveryDate, + SITDaysAllowance: &sitDaysAllowance, + PickupAddressID: &address.ID, + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + agentUserInfo := newUserInfo("agent") + factory.BuildMTOAgent(appCtx.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.MTOAgent{ + FirstName: &agentUserInfo.firstName, + LastName: &agentUserInfo.lastName, + Email: &agentUserInfo.email, + MTOAgentType: models.MTOAgentReleasing, + }, + }, + }, nil) + + return move +} + +// these create an iHHG move with selected affiliation, pickup of either Anchorage, AK (Zone I) or Fairbanks, AK (Zone II) +// contains an HHG shipment in SUBMITTED status that requires TOO approval +func MakeIntlHHGMovePickupAKZone1Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveAKToCONUSSubmitted(appCtx, models.AffiliationARMY, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMovePickupAKZone2Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveAKToCONUSSubmitted(appCtx, models.AffiliationARMY, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMovePickupAKZone1AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveAKToCONUSSubmitted(appCtx, models.AffiliationAIRFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMovePickupAKZone2AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveAKToCONUSSubmitted(appCtx, models.AffiliationAIRFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMovePickupAKZone1SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveAKToCONUSSubmitted(appCtx, models.AffiliationSPACEFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMovePickupAKZone2SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveAKToCONUSSubmitted(appCtx, models.AffiliationSPACEFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMovePickupAKZone1USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveAKToCONUSSubmitted(appCtx, models.AffiliationMARINES, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMovePickupAKZone2USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveAKToCONUSSubmitted(appCtx, models.AffiliationMARINES, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +// makeIntlHHGMoveWithSITRequested creates an international HHG move +// with the given affiliation and destination address +// parameters determine if ONLY origin or ONLY dest SIT service items are created +// or BOTH origin & dest are created +func makeIntlHHGMoveWithSITRequested( + appCtx appcontext.AppContext, + isOrigin bool, + isBoth bool, + affiliation models.ServiceMemberAffiliation, + streetAddress, city, state, postalCode string, +) models.Move { + userUploader := newUserUploader(appCtx) + userInfo := newUserInfo("customer") + + user := factory.BuildUser(appCtx.DB(), []factory.Customization{ + { + Model: models.User{ + OktaEmail: userInfo.email, + Active: true, + }, + }, + }, nil) + + customer := factory.BuildExtendedServiceMember(appCtx.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ + PersonalEmail: &userInfo.email, + FirstName: &userInfo.firstName, + LastName: &userInfo.lastName, + CacValidated: true, + Affiliation: &affiliation, + }, + }, + { + Model: user, + LinkOnly: true, + }, + }, nil) + + dependentsAuthorized := true + sitDaysAllowance := 90 + entitlements := factory.BuildEntitlement(appCtx.DB(), []factory.Customization{ + { + Model: models.Entitlement{ + DependentsAuthorized: &dependentsAuthorized, + StorageInTransit: &sitDaysAllowance, + }, + }, + }, nil) + + orders := factory.BuildOrder(appCtx.DB(), []factory.Customization{ + { + Model: customer, + LinkOnly: true, + }, + { + Model: entitlements, + LinkOnly: true, + }, + { + Model: models.UserUpload{}, + ExtendedParams: &factory.UserUploadExtendedParams{ + UserUploader: userUploader, + AppContext: appCtx, + }, + }, + }, nil) + + now := time.Now() + move := factory.BuildMove(appCtx.DB(), []factory.Customization{ + { + Model: orders, + LinkOnly: true, + }, + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + AvailableToPrimeAt: &now, + }, + }, + }, nil) + + estimatedWeight := unit.Pound(2000) + actualWeight := unit.Pound(2000) + requestedPickupDate := now.AddDate(0, 3, 0) + requestedDeliveryDate := requestedPickupDate.AddDate(0, 1, 0) + + // build the destination address using the passed-in parameters. + address := factory.BuildAddress(appCtx.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: streetAddress, + City: city, + State: state, + PostalCode: postalCode, + IsOconus: models.BoolPointer(true), + }, + }, + }, nil) + + shipment := factory.BuildMTOShipment(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + PrimeEstimatedWeight: &estimatedWeight, + PrimeActualWeight: &actualWeight, + ShipmentType: models.MTOShipmentTypeHHG, + Status: models.MTOShipmentStatusApproved, + RequestedPickupDate: &requestedPickupDate, + RequestedDeliveryDate: &requestedDeliveryDate, + SITDaysAllowance: &sitDaysAllowance, + DestinationAddressID: &address.ID, + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + agentUserInfo := newUserInfo("agent") + factory.BuildMTOAgent(appCtx.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.MTOAgent{ + FirstName: &agentUserInfo.firstName, + LastName: &agentUserInfo.lastName, + Email: &agentUserInfo.email, + MTOAgentType: models.MTOAgentReleasing, + }, + }, + }, nil) + + // build the origin/destination SIT service items and update their status to SUBMITTED + oneMonthLater := now.AddDate(0, 1, 0) + var sitItems models.MTOServiceItems + if isBoth { + sitItems = factory.BuildOriginSITServiceItems(appCtx.DB(), move, shipment, &oneMonthLater, nil) + destSitItems := factory.BuildDestSITServiceItems(appCtx.DB(), move, shipment, &oneMonthLater, nil) + sitItems = append(sitItems, destSitItems...) + } else if isOrigin { + sitItems = factory.BuildOriginSITServiceItems(appCtx.DB(), move, shipment, &oneMonthLater, nil) + } else { + sitItems = factory.BuildDestSITServiceItems(appCtx.DB(), move, shipment, &oneMonthLater, nil) + } + for i := range sitItems { + sitItems[i].Status = models.MTOServiceItemStatusSubmitted + if err := appCtx.DB().Update(&sitItems[i]); err != nil { + log.Panic(fmt.Errorf("failed to update sit service item: %w", err)) + } + } + + return move +} + +// these create an iHHG move with selected affiliation, destination of either Anchorage, AK (Zone I) or Fairbanks, AK (Zone II) +// containing all 4 international origin SIT service items in SUBMITTED status +func MakeIntlHHGMoveOriginSITRequestedAKZone1Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, true, false, models.AffiliationARMY, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveOriginSITRequestedAKZone2Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, true, false, models.AffiliationARMY, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveOriginSITRequestedAKZone1AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, true, false, models.AffiliationAIRFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveOriginSITRequestedAKZone2AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, true, false, models.AffiliationAIRFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveOriginSITRequestedAKZone1SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, true, false, models.AffiliationSPACEFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveOriginSITRequestedAKZone2SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, true, false, models.AffiliationSPACEFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveOriginSITRequestedAKZone1USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, true, false, models.AffiliationMARINES, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveOriginSITRequestedAKZone2USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, true, false, models.AffiliationMARINES, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +// these create an iHHG move with selected affiliation, destination of either Anchorage, AK (Zone I) or Fairbanks, AK (Zone II) +// containing all 4 international destination SIT service items in SUBMITTED status +func MakeIntlHHGMoveDestSITRequestedAKZone1Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, false, false, models.AffiliationARMY, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestSITRequestedAKZone2Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, false, false, models.AffiliationARMY, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveDestSITRequestedAKZone1AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, false, false, models.AffiliationAIRFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestSITRequestedAKZone2AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, false, false, models.AffiliationAIRFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveDestSITRequestedAKZone1SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, false, false, models.AffiliationSPACEFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestSITRequestedAKZone2SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, false, false, models.AffiliationSPACEFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveDestSITRequestedAKZone1USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, false, false, models.AffiliationMARINES, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestSITRequestedAKZone2USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, false, false, models.AffiliationMARINES, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +// these create an iHHG move with selected affiliation, destination of either Anchorage, AK (Zone I) or Fairbanks, AK (Zone II) +// containing all 4 international destination SIT service items AND all 4 origin SIT service items in SUBMITTED status +func MakeIntlHHGMoveBothSITRequestedAKZone1Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, false, true, models.AffiliationARMY, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveBothSITRequestedAKZone2Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, false, true, models.AffiliationARMY, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveBothSITRequestedAKZone1AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, false, true, models.AffiliationAIRFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveBothSITRequestedAKZone2AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, false, true, models.AffiliationAIRFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveBothSITRequestedAKZone1SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, false, true, models.AffiliationSPACEFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveBothSITRequestedAKZone2SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, false, true, models.AffiliationSPACEFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveBothSITRequestedAKZone1USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, false, true, models.AffiliationMARINES, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveBothSITRequestedAKZone2USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveWithSITRequested(appCtx, false, true, models.AffiliationMARINES, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +// makeIntlHHGMoveShuttleRequested creates an international HHG move +// with the given affiliation and destination address +// contains either origin, destination, or BOTH origin/destination shuttle in SUBMITTED status +func makeIntlHHGMoveShuttleRequested( + appCtx appcontext.AppContext, + isOrigin bool, + isBoth bool, + affiliation models.ServiceMemberAffiliation, + streetAddress, city, state, postalCode string, +) models.Move { + userUploader := newUserUploader(appCtx) + userInfo := newUserInfo("customer") + + user := factory.BuildUser(appCtx.DB(), []factory.Customization{ + { + Model: models.User{ + OktaEmail: userInfo.email, + Active: true, + }, + }, + }, nil) + + customer := factory.BuildExtendedServiceMember(appCtx.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ + PersonalEmail: &userInfo.email, + FirstName: &userInfo.firstName, + LastName: &userInfo.lastName, + CacValidated: true, + Affiliation: &affiliation, + }, + }, + { + Model: user, + LinkOnly: true, + }, + }, nil) + + dependentsAuthorized := true + sitDaysAllowance := 90 + entitlements := factory.BuildEntitlement(appCtx.DB(), []factory.Customization{ + { + Model: models.Entitlement{ + DependentsAuthorized: &dependentsAuthorized, + StorageInTransit: &sitDaysAllowance, + }, + }, + }, nil) + + orders := factory.BuildOrder(appCtx.DB(), []factory.Customization{ + { + Model: customer, + LinkOnly: true, + }, + { + Model: entitlements, + LinkOnly: true, + }, + { + Model: models.UserUpload{}, + ExtendedParams: &factory.UserUploadExtendedParams{ + UserUploader: userUploader, + AppContext: appCtx, + }, + }, + }, nil) + + now := time.Now() + move := factory.BuildMove(appCtx.DB(), []factory.Customization{ + { + Model: orders, + LinkOnly: true, + }, + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + AvailableToPrimeAt: &now, + }, + }, + }, nil) + + estimatedWeight := unit.Pound(2000) + actualWeight := unit.Pound(2000) + requestedPickupDate := now.AddDate(0, 3, 0) + requestedDeliveryDate := requestedPickupDate.AddDate(0, 1, 0) + + // build the destination address using the passed-in parameters. + address := factory.BuildAddress(appCtx.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: streetAddress, + City: city, + State: state, + PostalCode: postalCode, + IsOconus: models.BoolPointer(true), + }, + }, + }, nil) + + shipment := factory.BuildMTOShipment(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + PrimeEstimatedWeight: &estimatedWeight, + PrimeActualWeight: &actualWeight, + ShipmentType: models.MTOShipmentTypeHHG, + Status: models.MTOShipmentStatusApproved, + RequestedPickupDate: &requestedPickupDate, + RequestedDeliveryDate: &requestedDeliveryDate, + SITDaysAllowance: &sitDaysAllowance, + DestinationAddressID: &address.ID, + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + agentUserInfo := newUserInfo("agent") + factory.BuildMTOAgent(appCtx.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.MTOAgent{ + FirstName: &agentUserInfo.firstName, + LastName: &agentUserInfo.lastName, + Email: &agentUserInfo.email, + MTOAgentType: models.MTOAgentReleasing, + }, + }, + }, nil) + + // build the destination shuttle service item in SUBMITTED status + if isBoth { + factory.BuildMTOServiceItem(appCtx.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeIOSHUT, + }, + }, + { + Model: models.MTOServiceItem{ + Reason: models.StringPointer("internatioanl destination shuttle"), + EstimatedWeight: models.PoundPointer(1000), + ActualWeight: models.PoundPointer(1000), + Status: models.MTOServiceItemStatusSubmitted, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + }, nil) + + factory.BuildMTOServiceItem(appCtx.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeIDSHUT, + }, + }, + { + Model: models.MTOServiceItem{ + Reason: models.StringPointer("internatioanl destination shuttle"), + EstimatedWeight: models.PoundPointer(1000), + ActualWeight: models.PoundPointer(1000), + Status: models.MTOServiceItemStatusSubmitted, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + }, nil) + } else if isOrigin { + factory.BuildMTOServiceItem(appCtx.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeIOSHUT, + }, + }, + { + Model: models.MTOServiceItem{ + Reason: models.StringPointer("internatioanl destination shuttle"), + EstimatedWeight: models.PoundPointer(1000), + ActualWeight: models.PoundPointer(1000), + Status: models.MTOServiceItemStatusSubmitted, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + }, nil) + } else { + factory.BuildMTOServiceItem(appCtx.DB(), []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodeIDSHUT, + }, + }, + { + Model: models.MTOServiceItem{ + Reason: models.StringPointer("internatioanl destination shuttle"), + EstimatedWeight: models.PoundPointer(1000), + ActualWeight: models.PoundPointer(1000), + Status: models.MTOServiceItemStatusSubmitted, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: shipment, + LinkOnly: true, + }, + }, nil) + } + + return move +} + +// these create an iHHG move with selected affiliation, destination of either Anchorage, AK (Zone I) or Fairbanks, AK (Zone II) +// containing an international origin shuttle request in SUBMITTED status +func MakeIntlHHGMoveOriginShuttleRequestedAKZone1Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, true, false, models.AffiliationARMY, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveOriginShuttleRequestedAKZone2Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, true, false, models.AffiliationARMY, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveOriginShuttleRequestedAKZone1AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, true, false, models.AffiliationAIRFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveOriginShuttleRequestedAKZone2AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, true, false, models.AffiliationAIRFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveOriginShuttleRequestedAKZone1SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, true, false, models.AffiliationSPACEFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveOriginShuttleRequestedAKZone2SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, true, false, models.AffiliationSPACEFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveOriginShuttleRequestedAKZone1USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, true, false, models.AffiliationMARINES, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveOriginShuttleRequestedAKZone2USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, true, false, models.AffiliationMARINES, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +// these create an iHHG move with selected affiliation, destination of either Anchorage, AK (Zone I) or Fairbanks, AK (Zone II) +// containing an international destination shuttle request in SUBMITTED status +func MakeIntlHHGMoveDestShuttleRequestedAKZone1Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, false, false, models.AffiliationARMY, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestShuttleRequestedAKZone2Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, false, false, models.AffiliationARMY, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveDestShuttleRequestedAKZone1AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, false, false, models.AffiliationAIRFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestShuttleRequestedAKZone2AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, false, false, models.AffiliationAIRFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveDestShuttleRequestedAKZone1SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, false, false, models.AffiliationSPACEFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestShuttleRequestedAKZone2SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, false, false, models.AffiliationSPACEFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveDestShuttleRequestedAKZone1USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, false, false, models.AffiliationMARINES, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestShuttleRequestedAKZone2USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, false, false, models.AffiliationMARINES, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +// these create an iHHG move with selected affiliation, destination of either Anchorage, AK (Zone I) or Fairbanks, AK (Zone II) +// containing BOTH international origin & destination shuttle requests in SUBMITTED status +func MakeIntlHHGMoveBothShuttleRequestedAKZone1Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, false, true, models.AffiliationARMY, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveBothShuttleRequestedAKZone2Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, false, true, models.AffiliationARMY, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveBothShuttleRequestedAKZone1AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, false, true, models.AffiliationAIRFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveBothShuttleRequestedAKZone2AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, false, true, models.AffiliationAIRFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveBothShuttleRequestedAKZone1SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, false, true, models.AffiliationSPACEFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveBothShuttleRequestedAKZone2SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, false, true, models.AffiliationSPACEFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveBothShuttleRequestedAKZone1USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, false, true, models.AffiliationMARINES, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveBothShuttleRequestedAKZone2USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveShuttleRequested(appCtx, false, true, models.AffiliationMARINES, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +// makeIntlHHGMoveDestAddressRequested creates an international HHG move +// with the given affiliation and destination address +// contains a pending destination address request +func makeIntlHHGMoveDestAddressRequested( + appCtx appcontext.AppContext, + affiliation models.ServiceMemberAffiliation, + streetAddress, city, state, postalCode string, +) models.Move { + userUploader := newUserUploader(appCtx) + userInfo := newUserInfo("customer") + + user := factory.BuildUser(appCtx.DB(), []factory.Customization{ + { + Model: models.User{ + OktaEmail: userInfo.email, + Active: true, + }, + }, + }, nil) + + customer := factory.BuildExtendedServiceMember(appCtx.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ + PersonalEmail: &userInfo.email, + FirstName: &userInfo.firstName, + LastName: &userInfo.lastName, + CacValidated: true, + Affiliation: &affiliation, + }, + }, + { + Model: user, + LinkOnly: true, + }, + }, nil) + + dependentsAuthorized := true + sitDaysAllowance := 90 + entitlements := factory.BuildEntitlement(appCtx.DB(), []factory.Customization{ + { + Model: models.Entitlement{ + DependentsAuthorized: &dependentsAuthorized, + StorageInTransit: &sitDaysAllowance, + }, + }, + }, nil) + + var departmentIndicator *string = nil + if affiliation == models.AffiliationAIRFORCE || affiliation == models.AffiliationSPACEFORCE { + departmentIndicator = models.StringPointer(models.DepartmentIndicatorAIRANDSPACEFORCE.String()) + } else if affiliation == models.AffiliationNAVY || affiliation == models.AffiliationMARINES { + departmentIndicator = models.StringPointer(models.DepartmentIndicatorNAVYANDMARINES.String()) + } else if affiliation == models.AffiliationARMY { + departmentIndicator = models.StringPointer(models.DepartmentIndicatorARMY.String()) + } else if affiliation == models.AffiliationCOASTGUARD { + departmentIndicator = models.StringPointer(models.DepartmentIndicatorCOASTGUARD.String()) + } + + orders := factory.BuildOrder(appCtx.DB(), []factory.Customization{ + { + Model: models.Order{ + DepartmentIndicator: departmentIndicator, + }, + }, + { + Model: customer, + LinkOnly: true, + }, + { + Model: entitlements, + LinkOnly: true, + }, + { + Model: models.UserUpload{}, + ExtendedParams: &factory.UserUploadExtendedParams{ + UserUploader: userUploader, + AppContext: appCtx, + }, + }, + }, nil) + + now := time.Now() + + estimatedWeight := unit.Pound(2000) + actualWeight := unit.Pound(2000) + requestedPickupDate := now.AddDate(0, 3, 0) + requestedDeliveryDate := requestedPickupDate.AddDate(0, 1, 0) + + // build the destination address using the passed-in parameters. + address := factory.BuildAddress(appCtx.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: streetAddress, + City: city, + State: state, + PostalCode: postalCode, + IsOconus: models.BoolPointer(true), + }, + }, + }, nil) + + newDeliveryAddress := factory.BuildAddress(appCtx.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "Another Cold St.", + City: "Juneau", + State: "AK", + PostalCode: "99811", + }, + }, + }, nil) + + // build the shipment destination address update + shipmentAddressUpdate := factory.BuildShipmentAddressUpdate(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + PrimeEstimatedWeight: &estimatedWeight, + PrimeActualWeight: &actualWeight, + ShipmentType: models.MTOShipmentTypeHHG, + Status: models.MTOShipmentStatusApproved, + RequestedPickupDate: &requestedPickupDate, + RequestedDeliveryDate: &requestedDeliveryDate, + SITDaysAllowance: &sitDaysAllowance, + DestinationAddressID: &address.ID, + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + AvailableToPrimeAt: &now, + Show: models.BoolPointer(true), + }, + }, + { + Model: models.ShipmentAddressUpdate{ + Status: models.ShipmentAddressUpdateStatusRequested, + OriginalAddressID: address.ID, + NewAddressID: newDeliveryAddress.ID, + ContractorRemarks: *models.StringPointer("let's move this to another really cold place"), + }, + }, + { + Model: orders, + LinkOnly: true, + }, + }, nil) + + factory.BuildMTOServiceItemBasic(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + MTOShipmentID: &shipmentAddressUpdate.Shipment.ID, + }, + }, + { + Model: shipmentAddressUpdate.Shipment.MoveTaskOrder, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeMS, + }, + }, + }, nil) + + agentUserInfo := newUserInfo("agent") + factory.BuildMTOAgent(appCtx.DB(), []factory.Customization{ + { + Model: shipmentAddressUpdate.Shipment, + LinkOnly: true, + }, + { + Model: models.MTOAgent{ + FirstName: &agentUserInfo.firstName, + LastName: &agentUserInfo.lastName, + Email: &agentUserInfo.email, + MTOAgentType: models.MTOAgentReleasing, + }, + }, + }, nil) + + return shipmentAddressUpdate.Shipment.MoveTaskOrder +} + +// these create an iHHG move with selected affiliation, destination of either Anchorage, AK (Zone I) or Fairbanks, AK (Zone II) +// containing a destination address request that the TOO will be required to review +func MakeIntlHHGMoveDestAddressRequestedAKZone1Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveDestAddressRequested(appCtx, models.AffiliationARMY, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestAddressRequestedAKZone2Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveDestAddressRequested(appCtx, models.AffiliationARMY, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveDestAddressRequestedAKZone1AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveDestAddressRequested(appCtx, models.AffiliationAIRFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestAddressRequestedAKZone2AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveDestAddressRequested(appCtx, models.AffiliationAIRFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveDestAddressRequestedAKZone1SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveDestAddressRequested(appCtx, models.AffiliationSPACEFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestAddressRequestedAKZone2SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveDestAddressRequested(appCtx, models.AffiliationSPACEFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveDestAddressRequestedAKZone1USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveDestAddressRequested(appCtx, models.AffiliationMARINES, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestAddressRequestedAKZone2USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveDestAddressRequested(appCtx, models.AffiliationMARINES, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +// makeIntlHHGMoveSITExtensionRequested creates an international HHG move +// with the given affiliation and destination address +// contains a SIT extension request requiring TOO action +func makeIntlHHGMoveSITExtensionRequested( + appCtx appcontext.AppContext, + isOrigin bool, + affiliation models.ServiceMemberAffiliation, + streetAddress, city, state, postalCode string, +) models.Move { + userUploader := newUserUploader(appCtx) + userInfo := newUserInfo("customer") + + user := factory.BuildUser(appCtx.DB(), []factory.Customization{ + { + Model: models.User{ + OktaEmail: userInfo.email, + Active: true, + }, + }, + }, nil) + + customer := factory.BuildExtendedServiceMember(appCtx.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ + PersonalEmail: &userInfo.email, + FirstName: &userInfo.firstName, + LastName: &userInfo.lastName, + CacValidated: true, + Affiliation: &affiliation, + }, + }, + { + Model: user, + LinkOnly: true, + }, + }, nil) + + dependentsAuthorized := true + sitDaysAllowance := 90 + entitlements := factory.BuildEntitlement(appCtx.DB(), []factory.Customization{ + { + Model: models.Entitlement{ + DependentsAuthorized: &dependentsAuthorized, + StorageInTransit: &sitDaysAllowance, + }, + }, + }, nil) + + orders := factory.BuildOrder(appCtx.DB(), []factory.Customization{ + { + Model: customer, + LinkOnly: true, + }, + { + Model: entitlements, + LinkOnly: true, + }, + { + Model: models.UserUpload{}, + ExtendedParams: &factory.UserUploadExtendedParams{ + UserUploader: userUploader, + AppContext: appCtx, + }, + }, + }, nil) + + now := time.Now() + move := factory.BuildMove(appCtx.DB(), []factory.Customization{ + { + Model: orders, + LinkOnly: true, + }, + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + AvailableToPrimeAt: &now, + }, + }, + }, nil) + + estimatedWeight := unit.Pound(2000) + actualWeight := unit.Pound(2000) + requestedPickupDate := now.AddDate(0, 3, 0) + requestedDeliveryDate := requestedPickupDate.AddDate(0, 1, 0) + + // build the destination address using the passed-in parameters. + address := factory.BuildAddress(appCtx.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: streetAddress, + City: city, + State: state, + PostalCode: postalCode, + IsOconus: models.BoolPointer(true), + }, + }, + }, nil) + + shipment := factory.BuildMTOShipment(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + PrimeEstimatedWeight: &estimatedWeight, + PrimeActualWeight: &actualWeight, + ShipmentType: models.MTOShipmentTypeHHG, + Status: models.MTOShipmentStatusApproved, + RequestedPickupDate: &requestedPickupDate, + RequestedDeliveryDate: &requestedDeliveryDate, + SITDaysAllowance: &sitDaysAllowance, + DestinationAddressID: &address.ID, + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + agentUserInfo := newUserInfo("agent") + factory.BuildMTOAgent(appCtx.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.MTOAgent{ + FirstName: &agentUserInfo.firstName, + LastName: &agentUserInfo.lastName, + Email: &agentUserInfo.email, + MTOAgentType: models.MTOAgentReleasing, + }, + }, + }, nil) + + // build the origin/destination SIT service items + oneMonthLater := now.AddDate(0, 1, 0) + if isOrigin { + factory.BuildOriginSITServiceItems(appCtx.DB(), move, shipment, &oneMonthLater, nil) + } else { + factory.BuildDestSITServiceItems(appCtx.DB(), move, shipment, &oneMonthLater, nil) + } + + // build the SIT extension update in PENDING status + factory.BuildSITDurationUpdate(appCtx.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.SITDurationUpdate{ + Status: models.SITExtensionStatusPending, + ContractorRemarks: models.StringPointer("gimme some more plz"), + }, + }, + }, nil) + + return move +} + +// these create an iHHG move with selected affiliation, destination of either Anchorage, AK (Zone I) or Fairbanks, AK (Zone II) +// containing a SIT extension request for a shipment containing origin SIT only +func MakeIntlHHGMoveOriginSITExtensionRequestedAKZone1Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveSITExtensionRequested(appCtx, true, models.AffiliationARMY, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveOriginSITExtensionRequestedAKZone2Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveSITExtensionRequested(appCtx, true, models.AffiliationARMY, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveOriginSITExtensionRequestedAKZone1AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveSITExtensionRequested(appCtx, true, models.AffiliationAIRFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveOriginSITExtensionRequestedAKZone2AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveSITExtensionRequested(appCtx, true, models.AffiliationAIRFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveOriginSITExtensionRequestedAKZone1SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveSITExtensionRequested(appCtx, true, models.AffiliationSPACEFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveOriginSITExtensionRequestedAKZone2SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveSITExtensionRequested(appCtx, true, models.AffiliationSPACEFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveOriginSITExtensionRequestedAKZone1USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveSITExtensionRequested(appCtx, true, models.AffiliationMARINES, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveOriginSITExtensionRequestedAKZone2USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveSITExtensionRequested(appCtx, true, models.AffiliationMARINES, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +// these create an iHHG move with selected affiliation, destination of either Anchorage, AK (Zone I) or Fairbanks, AK (Zone II) +// containing a SIT extension request for a shipment containing destination SIT only +func MakeIntlHHGMoveDestSITExtensionRequestedAKZone1Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveSITExtensionRequested(appCtx, false, models.AffiliationARMY, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestSITExtensionRequestedAKZone2Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveSITExtensionRequested(appCtx, false, models.AffiliationARMY, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveDestSITExtensionRequestedAKZone1AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveSITExtensionRequested(appCtx, false, models.AffiliationAIRFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestSITExtensionRequestedAKZone2AirForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveSITExtensionRequested(appCtx, false, models.AffiliationAIRFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveDestSITExtensionRequestedAKZone1SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveSITExtensionRequested(appCtx, false, models.AffiliationSPACEFORCE, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestSITExtensionRequestedAKZone2SpaceForce(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveSITExtensionRequested(appCtx, false, models.AffiliationSPACEFORCE, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +func MakeIntlHHGMoveDestSITExtensionRequestedAKZone1USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveSITExtensionRequested(appCtx, false, models.AffiliationMARINES, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveDestSITExtensionRequestedAKZone2USMC(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveSITExtensionRequested(appCtx, false, models.AffiliationMARINES, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +// makeIntlHHGMoveCONUSToAKWithExcessWeight creates an international HHG move +// with the given affiliation and destination address +// contains one approved shipment and an pending at-risk excess weight +func makeIntlHHGMoveCONUSToAKWithExcessWeight( + appCtx appcontext.AppContext, + affiliation models.ServiceMemberAffiliation, + streetAddress, city, state, postalCode string, +) models.Move { + userUploader := newUserUploader(appCtx) + userInfo := newUserInfo("customer") + + user := factory.BuildUser(appCtx.DB(), []factory.Customization{ + { + Model: models.User{ + OktaEmail: userInfo.email, + Active: true, + }, + }, + }, nil) + + customer := factory.BuildExtendedServiceMember(appCtx.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ + PersonalEmail: &userInfo.email, + FirstName: &userInfo.firstName, + LastName: &userInfo.lastName, + CacValidated: true, + Affiliation: &affiliation, + }, + }, + { + Model: user, + LinkOnly: true, + }, + }, nil) + + dependentsAuthorized := true + sitDaysAllowance := 90 + entitlements := factory.BuildEntitlement(appCtx.DB(), []factory.Customization{ + { + Model: models.Entitlement{ + DependentsAuthorized: &dependentsAuthorized, + StorageInTransit: &sitDaysAllowance, + }, + }, + }, nil) + + orders := factory.BuildOrder(appCtx.DB(), []factory.Customization{ + { + Model: customer, + LinkOnly: true, + }, + { + Model: entitlements, + LinkOnly: true, + }, + { + Model: models.UserUpload{}, + ExtendedParams: &factory.UserUploadExtendedParams{ + UserUploader: userUploader, + AppContext: appCtx, + }, + }, + }, nil) + + now := time.Now() + move := factory.BuildAvailableToPrimeMove(appCtx.DB(), []factory.Customization{ + { + Model: orders, + LinkOnly: true, + }, + { + Model: models.Move{ + Status: models.MoveStatusAPPROVALSREQUESTED, + ExcessWeightQualifiedAt: &now, + }, + }, + }, nil) + + requestedPickupDate := now.AddDate(0, 3, 0) + requestedDeliveryDate := requestedPickupDate.AddDate(0, 1, 0) + + // build the destination address using the passed-in parameters. + address := factory.BuildAddress(appCtx.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: streetAddress, + City: city, + State: state, + PostalCode: postalCode, + IsOconus: models.BoolPointer(true), + }, + }, + }, nil) + + shipment := factory.BuildMTOShipment(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + PrimeEstimatedWeight: models.PoundPointer(8000), + ShipmentType: models.MTOShipmentTypeHHG, + Status: models.MTOShipmentStatusApproved, + RequestedPickupDate: &requestedPickupDate, + RequestedDeliveryDate: &requestedDeliveryDate, + SITDaysAllowance: &sitDaysAllowance, + DestinationAddressID: &address.ID, + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + agentUserInfo := newUserInfo("agent") + factory.BuildMTOAgent(appCtx.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.MTOAgent{ + FirstName: &agentUserInfo.firstName, + LastName: &agentUserInfo.lastName, + Email: &agentUserInfo.email, + MTOAgentType: models.MTOAgentReleasing, + }, + }, + }, nil) + + factory.BuildMTOServiceItemBasic(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + MTOShipmentID: &shipment.ID, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeMS, + }, + }, + }, nil) + + return move +} + +// these create an iHHG move with selected affiliation, destination of either Anchorage, AK (Zone I) or Fairbanks, AK (Zone II) +// containing an excess weight alert that requires action from TOO +func MakeIntlHHGMoveExcessWeightAKZone1Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveCONUSToAKWithExcessWeight(appCtx, models.AffiliationARMY, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlHHGMoveExcessWeightAKZone2Army(appCtx appcontext.AppContext) models.Move { + return makeIntlHHGMoveCONUSToAKWithExcessWeight(appCtx, models.AffiliationARMY, "Alaska Zone II St.", "North Pole", "AK", "99705") +} + +// makeIntlUBMoveCONUSToAKWithExcessWeight creates an international UB move +// with the given affiliation and destination address +// contains one approved shipment and an pending at-risk excess weight +func makeIntlUBMoveCONUSToAKWithExcessWeight( + appCtx appcontext.AppContext, + affiliation models.ServiceMemberAffiliation, + streetAddress, city, state, postalCode string, +) models.Move { + userUploader := newUserUploader(appCtx) + userInfo := newUserInfo("customer") + + user := factory.BuildUser(appCtx.DB(), []factory.Customization{ + { + Model: models.User{ + OktaEmail: userInfo.email, + Active: true, + }, + }, + }, nil) + + customer := factory.BuildExtendedServiceMember(appCtx.DB(), []factory.Customization{ + { + Model: models.ServiceMember{ + PersonalEmail: &userInfo.email, + FirstName: &userInfo.firstName, + LastName: &userInfo.lastName, + CacValidated: true, + Affiliation: &affiliation, + }, + }, + { + Model: user, + LinkOnly: true, + }, + }, nil) + + dependentsAuthorized := true + sitDaysAllowance := 90 + entitlements := factory.BuildEntitlement(appCtx.DB(), []factory.Customization{ + { + Model: models.Entitlement{ + DependentsAuthorized: &dependentsAuthorized, + StorageInTransit: &sitDaysAllowance, + }, + }, + }, nil) + + orders := factory.BuildOrder(appCtx.DB(), []factory.Customization{ + { + Model: customer, + LinkOnly: true, + }, + { + Model: entitlements, + LinkOnly: true, + }, + { + Model: models.UserUpload{}, + ExtendedParams: &factory.UserUploadExtendedParams{ + UserUploader: userUploader, + AppContext: appCtx, + }, + }, + }, nil) + + now := time.Now() + move := factory.BuildApprovalsRequestedMove(appCtx.DB(), []factory.Customization{ + { + Model: orders, + LinkOnly: true, + }, + { + Model: models.Move{ + ExcessUnaccompaniedBaggageWeightQualifiedAt: &now, + AvailableToPrimeAt: &now, + }, + }, + }, nil) + + requestedPickupDate := now.AddDate(0, 3, 0) + requestedDeliveryDate := requestedPickupDate.AddDate(0, 1, 0) + + // build the destination address using the passed-in parameters. + address := factory.BuildAddress(appCtx.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: streetAddress, + City: city, + State: state, + PostalCode: postalCode, + IsOconus: models.BoolPointer(true), + }, + }, + }, nil) + + shipment := factory.BuildMTOShipment(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + PrimeEstimatedWeight: models.PoundPointer(2000), + ShipmentType: models.MTOShipmentTypeUnaccompaniedBaggage, + Status: models.MTOShipmentStatusApproved, + RequestedPickupDate: &requestedPickupDate, + RequestedDeliveryDate: &requestedDeliveryDate, + SITDaysAllowance: &sitDaysAllowance, + DestinationAddressID: &address.ID, + MarketCode: models.MarketCodeInternational, + }, + }, + { + Model: move, + LinkOnly: true, + }, + }, nil) + + agentUserInfo := newUserInfo("agent") + factory.BuildMTOAgent(appCtx.DB(), []factory.Customization{ + { + Model: shipment, + LinkOnly: true, + }, + { + Model: models.MTOAgent{ + FirstName: &agentUserInfo.firstName, + LastName: &agentUserInfo.lastName, + Email: &agentUserInfo.email, + MTOAgentType: models.MTOAgentReleasing, + }, + }, + }, nil) + + factory.BuildMTOServiceItemBasic(appCtx.DB(), []factory.Customization{ + { + Model: models.MTOServiceItem{ + Status: models.MTOServiceItemStatusApproved, + MTOShipmentID: &shipment.ID, + }, + }, + { + Model: move, + LinkOnly: true, + }, + { + Model: models.ReService{ + Code: models.ReServiceCodeMS, + }, + }, + }, nil) + + return move +} + +// these create an iHHG move with selected affiliation, destination of either Anchorage, AK (Zone I) or Fairbanks, AK (Zone II) +// containing an excess weight alert that requires action from TOO +func MakeIntlUBMoveExcessWeightAKZone1Army(appCtx appcontext.AppContext) models.Move { + return makeIntlUBMoveCONUSToAKWithExcessWeight(appCtx, models.AffiliationARMY, "Alaska Zone I Ave.", "Anchorage", "AK", "99505") +} + +func MakeIntlUBMoveExcessWeightAKZone2Army(appCtx appcontext.AppContext) models.Move { + return makeIntlUBMoveCONUSToAKWithExcessWeight(appCtx, models.AffiliationARMY, "Alaska Zone II St.", "North Pole", "AK", "99705") +} diff --git a/pkg/unit/millicents_test.go b/pkg/unit/millicents_test.go index 0e7056f5f66..d7af7278abe 100644 --- a/pkg/unit/millicents_test.go +++ b/pkg/unit/millicents_test.go @@ -4,6 +4,16 @@ import ( "testing" ) +func TestMillicents_Int64(t *testing.T) { + millicents := Millicents(250000) + result := millicents.Int64() + + expected := int64(250000) + if result != expected { + t.Errorf("wrong number of Millicents: expected %v, got %v", expected, result) + } +} + func TestMillicents_Float64(t *testing.T) { millicents := Millicents(250000) result := millicents.Float64() diff --git a/playwright/tests/my/milmove/ppms/customerPpmTestFixture.js b/playwright/tests/my/milmove/ppms/customerPpmTestFixture.js index 122f2bced7d..ba4a3caebbf 100644 --- a/playwright/tests/my/milmove/ppms/customerPpmTestFixture.js +++ b/playwright/tests/my/milmove/ppms/customerPpmTestFixture.js @@ -5,6 +5,8 @@ */ // @ts-check +import { act } from '@testing-library/react'; + import { expect, test as customerTest, @@ -354,7 +356,11 @@ export class CustomerPpmPage extends CustomerPage { * returns {Promise} */ async navigateFromWeightTicketPage() { - await this.page.getByRole('button', { name: 'Save & Continue' }).click(); + await act(async () => { + await this.page.getByRole('button', { name: 'Save & Continue' }).click(); + }); + await this.page.waitForTimeout(1000); + await this.page.waitForURL(/\/moves\/[^/]+\/shipments\/[^/]+\/review/); } @@ -748,7 +754,11 @@ export class CustomerPpmPage extends CustomerPage { * returns {Promise} */ async navigateFromCloseoutReviewPageToProGearPage() { - await this.page.getByRole('link', { name: 'Add Pro-gear Weight' }).click(); + await act(async () => { + await this.page.getByRole('link', { name: 'Add Pro-gear Weight' }).click(); + }); + await this.page.waitForTimeout(1000); + await this.page.waitForURL(/\/moves\/[^/]+\/shipments\/[^/]+\/pro-gear/); } @@ -917,9 +927,6 @@ export class CustomerPpmPage extends CustomerPage { async navigateFromCloseoutReviewPageToExpensesPage() { await this.page.getByRole('link', { name: 'Add Expenses' }).waitFor({ state: 'visible' }); await this.page.getByRole('link', { name: 'Add Expenses' }).click(); - - // Retry to confirm the heading is visible - this is an effort to reduce flaky test failures - await this.page.waitForTimeout(1000); await expect(this.page.getByRole('heading', { level: 1, name: 'Expenses' })).toBeVisible({ timeout: 5000 }); } diff --git a/playwright/tests/my/mymove/boat.spec.js b/playwright/tests/my/mymove/boat.spec.js index 912459d0ec0..3a482488eb9 100644 --- a/playwright/tests/my/mymove/boat.spec.js +++ b/playwright/tests/my/mymove/boat.spec.js @@ -125,7 +125,7 @@ test.describe('Boat shipment', () => { ).toBeVisible(); await page.getByTestId('boatConfirmationContinue').click(); - await expect(page.getByText('HHG')).toBeVisible(); + await expect(page.getByTestId('tag')).toHaveText('HHG'); }); test('Is able to delete a boat shipment', async ({ page, customerPage }) => { @@ -236,7 +236,7 @@ test.describe('Boat shipment', () => { await expect( page.getByRole('heading', { name: 'Movers pack and ship it, paid by the government (HHG)' }), ).not.toBeVisible(); - await expect(page.getByText('HHG')).toBeVisible(); + await expect(page.getByTestId('tag')).toHaveText('HHG'); await expect(page.getByText('Movers pack and transport this shipment')).toBeVisible(); await page.getByTestId('wizardNextButton').click(); await customerPage.waitForPage.reviewShipments(); @@ -452,7 +452,7 @@ test.describe('(MultiMove) Boat shipment', () => { ).toBeVisible(); await page.getByTestId('boatConfirmationContinue').click(); - await expect(page.getByText('HHG')).toBeVisible(); + await expect(page.getByTestId('tag')).toHaveText('HHG'); }); test('Is able to delete a boat shipment', async ({ page, customerPage }) => { @@ -569,7 +569,7 @@ test.describe('(MultiMove) Boat shipment', () => { await expect( page.getByRole('heading', { name: 'Movers pack and ship it, paid by the government (HHG)' }), ).not.toBeVisible(); - await expect(page.getByText('HHG')).toBeVisible(); + await expect(page.getByTestId('tag')).toHaveText('HHG'); await expect(page.getByText('Movers pack and transport this shipment')).toBeVisible(); await page.getByTestId('wizardNextButton').click(); await customerPage.waitForPage.reviewShipments(); diff --git a/playwright/tests/my/mymove/mobileHomes.spec.js b/playwright/tests/my/mymove/mobileHomes.spec.js new file mode 100644 index 00000000000..e87642c224d --- /dev/null +++ b/playwright/tests/my/mymove/mobileHomes.spec.js @@ -0,0 +1,141 @@ +import { test, expect } from '../../utils/my/customerTest'; + +const multiMoveEnabled = process.env.FEATURE_FLAG_MULTI_MOVE; + +test.describe('Mobile Home shipment', () => { + test.skip(multiMoveEnabled === 'true', 'Skip if MultiMove workflow is enabled.'); + + test('A customer can create a Mobile Home shipment', async ({ page, customerPage }) => { + // Generate a new onboarded user with orders and log in + const move = await customerPage.testHarness.buildMoveWithOrders(); + const userId = move.Orders.ServiceMember.user_id; + await customerPage.signInAsExistingCustomer(userId); + + // Navigate to create a new shipment + await customerPage.waitForPage.home(); + await page.getByTestId('shipment-selection-btn').click(); + await customerPage.waitForPage.aboutShipments(); + await customerPage.navigateForward(); + await customerPage.waitForPage.selectShipmentType(); + + // Create an Mobile Home shipment + await page.getByText('Move a Mobile Home').click(); + await customerPage.navigateForward(); + + // Fill in form to create Mobile Home shipment + await customerPage.waitForPage.mobileHomeShipment(); + await page.getByLabel('Year').fill('2022'); + await page.getByLabel('Make').fill('make'); + await page.getByLabel('Model').fill('model'); + await page.getByTestId('lengthFeet').fill('22'); + await page.getByTestId('widthFeet').fill('22'); + await page.getByTestId('heightFeet').fill('22'); + await page.getByRole('button', { name: 'Continue' }).click(); + + await expect(page.getByTestId('tag')).toContainText('Mobile Home'); + + await expect(page.getByText('Pickup info')).toBeVisible(); + await page.getByLabel('Preferred pickup date').fill('25 Dec 2022'); + await page.getByLabel('Preferred pickup date').blur(); + await page.getByText('Use my current address').click(); + await page.getByLabel('Preferred delivery date').fill('25 Dec 2022'); + await page.getByLabel('Preferred delivery date').blur(); + await page.getByRole('button', { name: 'Save & Continue' }).click(); + await customerPage.waitForPage.reviewShipments(); + }); +}); + +test.describe('(MultiMove) Mobile Home shipment', () => { + test.skip(multiMoveEnabled === 'false', 'Skip if MultiMove workflow is not enabled.'); + + test('A customer can create a Mobile Home shipment', async ({ page, customerPage }) => { + // Generate a new onboarded user with orders and log in + const move = await customerPage.testHarness.buildMoveWithOrders(); + const userId = move.Orders.ServiceMember.user_id; + await customerPage.signInAsExistingCustomer(userId); + + // Navigate from MM Dashboard to Move + await customerPage.navigateFromMMDashboardToMove(move); + + // Navigate to create a new shipment + await customerPage.waitForPage.home(); + await page.getByTestId('shipment-selection-btn').click(); + await customerPage.waitForPage.aboutShipments(); + await customerPage.navigateForward(); + await customerPage.waitForPage.selectShipmentType(); + + // Create an Mobile Home shipment + await page.getByText('Move a mobile home').click(); + await customerPage.navigateForward(); + + // Fill in form to create Mobile Home shipment + await customerPage.waitForPage.mobileHomeShipment(); + await page.getByLabel('Year').fill('2022'); + await page.getByLabel('Make').fill('make'); + await page.getByLabel('Model').fill('model'); + await page.getByTestId('lengthFeet').fill('22'); + await page.getByTestId('widthFeet').fill('22'); + await page.getByTestId('heightFeet').fill('22'); + await page.getByRole('button', { name: 'Continue' }).click(); + + await expect(page.getByTestId('tag')).toContainText('Mobile Home'); + + await expect(page.getByText('Pickup info')).toBeVisible(); + await page.getByLabel('Preferred pickup date').fill('25 Dec 2022'); + await page.getByLabel('Preferred pickup date').blur(); + await page.getByText('Use my current address').click(); + await page.getByLabel('Preferred delivery date').fill('25 Dec 2022'); + await page.getByLabel('Preferred delivery date').blur(); + await page.getByRole('button', { name: 'Save & Continue' }).click(); + await customerPage.waitForPage.reviewShipments(); + }); + + test('Is able to delete a Mobile Home shipment', async ({ page, customerPage }) => { + // Generate a new onboarded user with orders and log in + const move = await customerPage.testHarness.buildMoveWithOrders(); + const userId = move.Orders.ServiceMember.user_id; + await customerPage.signInAsExistingCustomer(userId); + + // Navigate from MM Dashboard to Move + await customerPage.navigateFromMMDashboardToMove(move); + + // Navigate to create a new shipment + await customerPage.waitForPage.home(); + await page.getByTestId('shipment-selection-btn').click(); + await customerPage.waitForPage.aboutShipments(); + await customerPage.navigateForward(); + await customerPage.waitForPage.selectShipmentType(); + + // Create an Mobile Home shipment + await page.getByText('Move a mobile home').click(); + await customerPage.navigateForward(); + + // Fill in form to create Mobile Home shipment + await customerPage.waitForPage.mobileHomeShipment(); + await page.getByLabel('Year').fill('2022'); + await page.getByLabel('Make').fill('make'); + await page.getByLabel('Model').fill('model'); + await page.getByTestId('lengthFeet').fill('22'); + await page.getByTestId('widthFeet').fill('22'); + await page.getByTestId('heightFeet').fill('22'); + await page.getByRole('button', { name: 'Continue' }).click(); + + await expect(page.getByTestId('tag')).toContainText('Mobile Home'); + + await expect(page.getByText('Pickup info')).toBeVisible(); + await page.getByLabel('Preferred pickup date').fill('25 Dec 2022'); + await page.getByLabel('Preferred pickup date').blur(); + await page.getByText('Use my current address').click(); + await page.getByLabel('Preferred delivery date').fill('25 Dec 2022'); + await page.getByLabel('Preferred delivery date').blur(); + await page.getByRole('button', { name: 'Save & Continue' }).click(); + await customerPage.waitForPage.reviewShipments(); + + await expect(page.getByRole('heading', { name: 'Mobile Home 1' })).toBeVisible(); + await page.getByTestId('deleteShipmentButton').click(); + await expect(page.getByRole('heading', { name: 'Delete this?' })).toBeVisible(); + await page.getByText('Yes, Delete').click(); + + await expect(page.getByRole('heading', { name: 'Mobile Home 1' })).not.toBeVisible(); + }); +}); diff --git a/playwright/tests/office/qaecsr/csrFlows.spec.js b/playwright/tests/office/qaecsr/csrFlows.spec.js index ccdda99fa19..692b5e9bd06 100644 --- a/playwright/tests/office/qaecsr/csrFlows.spec.js +++ b/playwright/tests/office/qaecsr/csrFlows.spec.js @@ -137,6 +137,7 @@ test.describe('Customer Support User Flows', () => { await expect(page.locator('input[name="tac"]')).toBeDisabled(); await expect(page.locator('input[name="sac"]')).toBeDisabled(); await expect(page.locator('select[name="payGrade"]')).toBeDisabled(); + await expect(page.locator('input[name="dependentsAuthorized"]')).toBeDisabled(); // no save button should exist await expect(page.getByRole('button', { name: 'Save' })).toHaveCount(0); }); @@ -160,8 +161,6 @@ test.describe('Customer Support User Flows', () => { // read only authorized weight await expect(page.locator('select[name=agency]')).toBeDisabled(); - await expect(page.locator('select[name=agency]')).toBeDisabled(); - await expect(page.locator('input[name="dependentsAuthorized"]')).toBeDisabled(); // no save button should exist await expect(page.getByRole('button', { name: 'Save' })).toHaveCount(0); diff --git a/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js b/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js index a5aeef1343c..064c7e1d63d 100644 --- a/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js +++ b/playwright/tests/office/servicescounseling/servicesCounselingFlows.spec.js @@ -374,6 +374,8 @@ test.describe('Services counselor user', () => { // Edit the shipment so that the tag disappears await page.locator('[data-testid="ShipmentContainer"] .usa-button').last().click(); await page.locator('select[name="destinationType"]').selectOption({ label: 'Home of selection (HOS)' }); + await page.getByLabel('Requested pickup date').fill('16 Mar 2022'); + await page.locator('[data-testid="submitForm"]').click(); await scPage.waitForLoading(); @@ -413,6 +415,8 @@ test.describe('Services counselor user', () => { await page.getByRole('button', { name: 'Confirm' }).click(); await scPage.waitForPage.moveDetails(); + await expect(page.getByText('PACKET READY FOR DOWNLOAD')).toBeVisible(); + // Navigate to the "View documents" page await expect(page.getByRole('button', { name: /View documents/i })).toBeVisible(); await page.getByRole('button', { name: 'View documents' }).click(); diff --git a/playwright/tests/office/servicescounseling/servicesCounselingTestFixture.js b/playwright/tests/office/servicescounseling/servicesCounselingTestFixture.js index e6abf228149..ad230d3bf86 100644 --- a/playwright/tests/office/servicescounseling/servicesCounselingTestFixture.js +++ b/playwright/tests/office/servicescounseling/servicesCounselingTestFixture.js @@ -171,9 +171,8 @@ export class ServiceCounselorPage extends OfficePage { await this.page.getByLabel('Requested delivery date').fill('20 Mar 2022'); await this.page.getByLabel('Requested delivery date').blur(); - // Delivery location + // Delivery Address const DeliveryLocationLookup = 'MONTGOMERY, AL 36101 (MONTGOMERY)'; - const deliveryLocation = this.page.getByRole('group', { name: 'Delivery Address' }); await deliveryLocation.getByLabel('Address 1').fill('448 Washington Blvd NE'); await deliveryLocation.getByLabel('Address 2').fill('Apt D3'); diff --git a/playwright/tests/office/txo/tioFlowsInternational.spec.js b/playwright/tests/office/txo/tioFlowsInternational.spec.js index 2b97f19078b..cf8ed39c541 100644 --- a/playwright/tests/office/txo/tioFlowsInternational.spec.js +++ b/playwright/tests/office/txo/tioFlowsInternational.spec.js @@ -143,6 +143,17 @@ test.describe('TIO user', () => { await page.getByText('Next').click(); await tioFlowPage.slowDown(); + await expect(page.getByText('International destination shuttle service')).toBeVisible(); + await page.getByText('Show calculations').click(); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Calculations'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Billable weight (cwt)'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Destination price'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Price escalation factor'); + // approve + await tioFlowPage.approveServiceItem(); + await page.getByText('Next').click(); + await tioFlowPage.slowDown(); + await expect(page.getByText('International POE Fuel Surcharge')).toBeVisible(); await page.getByText('Show calculations').click(); await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Calculations'); @@ -159,8 +170,8 @@ test.describe('TIO user', () => { await expect(page.getByText('needs your review')).toHaveCount(0, { timeout: 10000 }); await page.getByText('Complete request').click(); - await expect(page.locator('[data-testid="requested"]')).toContainText('$4,281.48'); - await expect(page.locator('[data-testid="accepted"]')).toContainText('$4,281.48'); + await expect(page.locator('[data-testid="requested"]')).toContainText('$4,287.71'); + await expect(page.locator('[data-testid="accepted"]')).toContainText('$4,287.71'); await expect(page.locator('[data-testid="rejected"]')).toContainText('$0.00'); await page.getByText('Authorize payment').click(); @@ -187,4 +198,94 @@ test.describe('TIO user', () => { // in the TIO queue - only "Payment requested" moves will appear await expect(paymentSection.locator('td', { hasText: 'Reviewed' })).not.toBeVisible(); }); + + test('can review a payment request for international crating/uncrating service items', async ({ + page, + officePage, + }) => { + test.slow(); + const move = + await officePage.testHarness.buildIntlHHGMoveWithCratingUncratingServiceItemsAndPaymentRequestsForTIO(); + await officePage.signInAsNewTIOUser(); + + tioFlowPage = new TioFlowPage(officePage, move, true); + await tioFlowPage.waitForLoading(); + await officePage.tioNavigateToMove(tioFlowPage.moveLocator); + await officePage.page.getByRole('heading', { name: 'Payment Requests', exact: true }).waitFor(); + expect(page.url()).toContain('/payment-requests'); + await expect(page.getByTestId('MovePaymentRequests')).toBeVisible(); + + const prNumber = tioFlowPage.paymentRequest.payment_request_number; + const prHeading = page.getByRole('heading', { name: `Payment Request ${prNumber}` }); + await expect(prHeading).toBeVisible(); + await tioFlowPage.waitForLoading(); + + await page.getByRole('button', { name: 'Review service items' }).click(); + + await page.waitForURL(`**/payment-requests/${tioFlowPage.paymentRequest.id}`); + await tioFlowPage.waitForLoading(); + + // ICRT + await expect(page.getByTestId('ReviewServiceItems')).toBeVisible(); + await expect(page.getByText('International crating')).toBeVisible(); + await page.getByText('Show calculations').click(); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Calculations'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Crating size (cu ft)'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Description'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Dimensions'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Actual size'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('External crate'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Crating price (per cu ft)'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Crating date'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Price escalation factor'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Uncapped request total'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Standalone crate cap'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Minimum crating size applied'); + // approve + await tioFlowPage.approveServiceItem(); + await page.getByTestId('nextServiceItem').click(); + await tioFlowPage.slowDown(); + + // IUCRT + await expect(page.getByText('International uncrating')).toBeVisible(); + await page.getByText('Show calculations').click(); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Calculations'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Crating size (cu ft)'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Description'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Dimensions'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Uncrating price (per cu ft)'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Uncrating date'); + await expect(page.locator('[data-testid="ServiceItemCalculations"]')).toContainText('Price escalation factor'); + // approve + await tioFlowPage.approveServiceItem(); + await page.getByTestId('nextServiceItem').click(); + await tioFlowPage.slowDown(); + + await expect(page.getByText('needs your review')).toHaveCount(0, { timeout: 10000 }); + await page.getByText('Complete request').click(); + + await page.getByText('Authorize payment').click(); + await tioFlowPage.waitForLoading(); + + await tioFlowPage.slowDown(); + expect(page.url()).toContain('/payment-requests'); + + await expect(page.getByTestId('tag')).toBeVisible(); + await expect(page.getByTestId('tag').getByText('Reviewed')).toHaveCount(1); + + // ensure the payment request we approved no longer has the "Review Service Items" button + await expect(page.getByText('Review Service Items')).toHaveCount(0); + + // Go back to queue + await page.locator('a[title="Home"]').click(); + await tioFlowPage.waitForLoading(); + + // search for the moveLocator in case this move doesn't show up on the first page + await page.locator('#locator').fill(tioFlowPage.moveLocator); + await page.locator('#locator').blur(); + const paymentSection = page.locator(`[data-uuid="${tioFlowPage.paymentRequest.id}"]`); + // the payment request that is now in the "Reviewed" status will no longer appear + // in the TIO queue - only "Payment requested" moves will appear + await expect(paymentSection.locator('td', { hasText: 'Reviewed' })).not.toBeVisible(); + }); }); diff --git a/playwright/tests/office/txo/tooFlows.spec.js b/playwright/tests/office/txo/tooFlows.spec.js index dd85150f3fd..412076deb28 100644 --- a/playwright/tests/office/txo/tooFlows.spec.js +++ b/playwright/tests/office/txo/tooFlows.spec.js @@ -626,17 +626,10 @@ test.describe('TOO user', () => { await page.getByRole('button', { name: 'Select task_ordering_officer' }).click(); }); test('weight-based multiplier prioritizes billed weight', async ({ page }) => { - await page.getByRole('row', { name: 'Select...' }).getByTestId('locator').getByTestId('TextBoxFilter').click(); - await page - .getByRole('row', { name: 'Select...' }) - .getByTestId('locator') - .getByTestId('TextBoxFilter') - .fill(moveLoc); - await page - .getByRole('row', { name: 'Select...' }) - .getByTestId('locator') - .getByTestId('TextBoxFilter') - .press('Enter'); + await page.getByRole('link', { name: 'Search' }).click(); + await page.getByTestId('searchText').click(); + await page.getByTestId('searchText').fill(moveLoc); + await page.getByTestId('searchText').press('Enter'); await page.getByTestId('locator-0').click(); await page.getByRole('link', { name: 'Payment requests' }).click(); await page.getByRole('button', { name: 'Review shipment weights' }).click(); diff --git a/playwright/tests/office/txo/tooFlowsInternational.spec.js b/playwright/tests/office/txo/tooFlowsInternational.spec.js index 31fa0741487..2b209b143ec 100644 --- a/playwright/tests/office/txo/tooFlowsInternational.spec.js +++ b/playwright/tests/office/txo/tooFlowsInternational.spec.js @@ -59,7 +59,6 @@ test.describe('TOO user', () => { // Edit the shipment address to AK await page.locator('[data-testid="ShipmentContainer"] .usa-button').first().click(); await page.locator('input[id="delivery.address-location-input"]').fill('99505'); - await page.keyboard.press('Enter'); await page.getByRole('button', { name: 'Save' }).click(); await tooFlowPage.waitForPage.moveDetails(); @@ -89,17 +88,19 @@ test.describe('TOO user', () => { return table.getByRole('rowgroup').nth(1).getByRole('row'); }; + await expect(page.getByText('Requested Service Items', { exact: false })).toBeVisible(); + await expect(page.getByTestId('modal')).not.toBeVisible(); + const requestedServiceItemsTable = page.getByTestId('RequestedServiceItemsTable'); - let requestedServiceItemCount = await getServiceItemsInTable(requestedServiceItemsTable).count(); const approvedServiceItemsTable = page.getByTestId('ApprovedServiceItemsTable'); - let approvedServiceItemCount = await getServiceItemsInTable(approvedServiceItemsTable).count(); const rejectedServiceItemsTable = page.getByTestId('RejectedServiceItemsTable'); - let rejectedServiceItemCount = await getServiceItemsInTable(rejectedServiceItemsTable).count(); - await expect(page.getByText('Requested Service Items', { exact: false })).toBeVisible(); await expect(getServiceItemsInTable(requestedServiceItemsTable).nth(1)).toBeVisible(); + await expect(getServiceItemsInTable(approvedServiceItemsTable).nth(1)).toBeVisible(); - await expect(page.getByTestId('modal')).not.toBeVisible(); + let requestedServiceItemCount = await getServiceItemsInTable(requestedServiceItemsTable).count(); + let approvedServiceItemCount = await getServiceItemsInTable(approvedServiceItemsTable).count(); + let rejectedServiceItemCount = await getServiceItemsInTable(rejectedServiceItemsTable).count(); // Approve a requested service item expect((await getServiceItemsInTable(requestedServiceItemsTable).count()) > 0); @@ -163,5 +164,94 @@ test.describe('TOO user', () => { await expect(getServiceItemsInTable(requestedServiceItemsTable)).toHaveCount(requestedServiceItemCount - 1); }); + + test.skip(alaskaEnabled === 'false', 'Skip if Alaska FF is disabled.'); + test('is able to approve and reject international shuttle service items', async ({ officePage, page }) => { + const move = await officePage.testHarness.buildHHGMoveWithIntlShuttleServiceItemsTOO(); + await officePage.signInAsNewTOOUser(); + tooFlowPage = new TooFlowPage(officePage, move); + await tooFlowPage.waitForLoading(); + await officePage.tooNavigateToMove(tooFlowPage.moveLocator); + + // Edit the shipment address to AK + await page.locator('[data-testid="ShipmentContainer"] .usa-button').first().click(); + await page.locator('input[id="delivery.address-location-input"]').fill('99505'); + + await page.getByRole('button', { name: 'Save' }).click(); + await tooFlowPage.waitForPage.moveDetails(); + + await tooFlowPage.waitForLoading(); + await tooFlowPage.approveAllShipments(); + + await page.getByTestId('MoveTaskOrder-Tab').click(); + await tooFlowPage.waitForLoading(); + expect(page.url()).toContain(`/moves/${tooFlowPage.moveLocator}/mto`); + + // Wait for page to load to deal with flakiness resulting from Service Item tables loading + await tooFlowPage.page.waitForLoadState(); + + // Move Task Order page + await expect(page.getByTestId('ShipmentContainer')).toHaveCount(1); + + /** + * @function + * @description This test approves and rejects service items, which moves them from one table to another + * and expects the counts of each table to increment/decrement by one item each time + * This function gets the service items for a given table to help count them + * @param {import("playwright-core").Locator} table + * @returns {import("playwright-core").Locator} + */ + const getServiceItemsInTable = (table) => { + return table.getByRole('rowgroup').nth(1).getByRole('row'); + }; + + await expect(page.getByText('Requested Service Items', { exact: false })).toBeVisible(); + await expect(page.getByTestId('modal')).not.toBeVisible(); + + const requestedServiceItemsTable = page.getByTestId('RequestedServiceItemsTable'); + const approvedServiceItemsTable = page.getByTestId('ApprovedServiceItemsTable'); + const rejectedServiceItemsTable = page.getByTestId('RejectedServiceItemsTable'); + + await expect(getServiceItemsInTable(requestedServiceItemsTable).nth(1)).toBeVisible(); + await expect(getServiceItemsInTable(approvedServiceItemsTable).nth(1)).toBeVisible(); + + let requestedServiceItemCount = await getServiceItemsInTable(requestedServiceItemsTable).count(); + let approvedServiceItemCount = await getServiceItemsInTable(approvedServiceItemsTable).count(); + let rejectedServiceItemCount = await getServiceItemsInTable(rejectedServiceItemsTable).count(); + + // Approve a requested service item + expect((await getServiceItemsInTable(requestedServiceItemsTable).count()) > 0); + + await requestedServiceItemsTable.getByRole('button', { name: 'Accept' }).first().click(); + await tooFlowPage.waitForLoading(); + + await expect(getServiceItemsInTable(approvedServiceItemsTable)).toHaveCount(approvedServiceItemCount + 1); + approvedServiceItemCount = await getServiceItemsInTable(approvedServiceItemsTable).count(); + + await expect(getServiceItemsInTable(requestedServiceItemsTable)).toHaveCount(requestedServiceItemCount - 1); + requestedServiceItemCount = await getServiceItemsInTable(requestedServiceItemsTable).count(); + + // Reject a requested service item + await expect(page.getByText('Requested Service Items', { exact: false })).toBeVisible(); + expect((await getServiceItemsInTable(requestedServiceItemsTable).count()) > 0); + + await requestedServiceItemsTable.getByRole('button', { name: 'Reject' }).first().click(); + + await expect(page.getByTestId('modal')).toBeVisible(); + const modal = page.getByTestId('modal'); + + await expect(modal.getByRole('button', { name: 'Submit' })).toBeDisabled(); + await modal.getByRole('textbox').fill('my very valid reason'); + await modal.getByRole('button', { name: 'Submit' }).click(); + + await expect(page.getByTestId('modal')).not.toBeVisible(); + + await expect(page.getByText('Rejected Service Items', { exact: false })).toBeVisible(); + await expect(getServiceItemsInTable(rejectedServiceItemsTable)).toHaveCount(rejectedServiceItemCount + 1); + rejectedServiceItemCount = await getServiceItemsInTable(rejectedServiceItemsTable).count(); + + await expect(getServiceItemsInTable(requestedServiceItemsTable)).toHaveCount(requestedServiceItemCount - 1); + requestedServiceItemCount = await getServiceItemsInTable(requestedServiceItemsTable).count(); + }); }); }); diff --git a/playwright/tests/utils/testharness.js b/playwright/tests/utils/testharness.js index cc84f4c61f9..ceae7386107 100644 --- a/playwright/tests/utils/testharness.js +++ b/playwright/tests/utils/testharness.js @@ -299,6 +299,14 @@ export class TestHarness { return this.buildDefault('HHGMoveWithIntlCratingServiceItemsTOO'); } + /** + * Use testharness to build hhg move with international crating service items for TOO + * @returns {Promise} + */ + async buildHHGMoveWithIntlShuttleServiceItemsTOO() { + return this.buildDefault('HHGMoveWithIntlShuttleServiceItemsTOO'); + } + /** * Use testharness to build hhg move for TOO with actualPickupDate in the past * @returns {Promise} @@ -387,6 +395,14 @@ export class TestHarness { return this.buildDefault('InternationalHHGMoveWithServiceItemsandPaymentRequestsForTIO'); } + /** + * Use testharness to build ihhg move for TIO + * @returns {Promise} + */ + async buildIntlHHGMoveWithCratingUncratingServiceItemsAndPaymentRequestsForTIO() { + return this.buildDefault('IntlHHGMoveWithCratingUncratingServiceItemsAndPaymentRequestsForTIO'); + } + /** * Use testharness to build hhg move for QAE * @returns {Promise} @@ -694,5 +710,353 @@ export class TestHarness { async buildOfficeUserWithGSR() { return this.buildDefault('OfficeUserWithGSR'); } + + /** + * Use testharness to build international move with requested origin SIT + * @returns {Promise} + */ + async buildIntlHHGMoveOriginSITRequestedAKZone1Army() { + return this.buildDefault('IntlHHGMoveOriginSITRequestedAKZone1Army'); + } + + async buildIntlHHGMoveOriginSITRequestedAKZone2Army() { + return this.buildDefault('IntlHHGMoveOriginSITRequestedAKZone2Army'); + } + + async buildIntlHHGMoveOriginSITRequestedAKZone1AirForce() { + return this.buildDefault('IntlHHGMoveOriginSITRequestedAKZone1AirForce'); + } + + async buildIntlHHGMoveOriginSITRequestedAKZone2AirForce() { + return this.buildDefault('IntlHHGMoveOriginSITRequestedAKZone2AirForce'); + } + + async buildIntlHHGMoveOriginSITRequestedAKZone1SpaceForce() { + return this.buildDefault('IntlHHGMoveOriginSITRequestedAKZone1SpaceForce'); + } + + async buildIntlHHGMoveOriginSITRequestedAKZone2SpaceForce() { + return this.buildDefault('IntlHHGMoveOriginSITRequestedAKZone2SpaceForce'); + } + + async buildIntlHHGMoveOriginSITRequestedAKZone1USMC() { + return this.buildDefault('IntlHHGMoveOriginSITRequestedAKZone1USMC'); + } + + async buildIntlHHGMoveOriginSITRequestedAKZone2USMC() { + return this.buildDefault('IntlHHGMoveOriginSITRequestedAKZone2USMC'); + } + + /** + * Use testharness to build international move with requested destination SIT + * @returns {Promise} + */ + async buildIntlHHGMoveDestSITRequestedAKZone1Army() { + return this.buildDefault('IntlHHGMoveDestSITRequestedAKZone1Army'); + } + + async buildIntlHHGMoveDestSITRequestedAKZone2Army() { + return this.buildDefault('IntlHHGMoveDestSITRequestedAKZone2Army'); + } + + async buildIntlHHGMoveDestSITRequestedAKZone1AirForce() { + return this.buildDefault('IntlHHGMoveDestSITRequestedAKZone1AirForce'); + } + + async buildIntlHHGMoveDestSITRequestedAKZone2AirForce() { + return this.buildDefault('IntlHHGMoveDestSITRequestedAKZone2AirForce'); + } + + async buildIntlHHGMoveDestSITRequestedAKZone1SpaceForce() { + return this.buildDefault('IntlHHGMoveDestSITRequestedAKZone1SpaceForce'); + } + + async buildIntlHHGMoveDestSITRequestedAKZone2SpaceForce() { + return this.buildDefault('IntlHHGMoveDestSITRequestedAKZone2SpaceForce'); + } + + async buildIntlHHGMoveDestSITRequestedAKZone1USMC() { + return this.buildDefault('IntlHHGMoveDestSITRequestedAKZone1USMC'); + } + + async buildIntlHHGMoveDestSITRequestedAKZone2USMC() { + return this.buildDefault('IntlHHGMoveDestSITRequestedAKZone2USMC'); + } + + /** + * Use testharness to build international move with both requested origin & destination SIT + * @returns {Promise} + */ + async buildIntlHHGMoveBothSITRequestedAKZone1Army() { + return this.buildDefault('IntlHHGMoveBothSITRequestedAKZone1Army'); + } + + async buildIntlHHGMoveBothSITRequestedAKZone2Army() { + return this.buildDefault('IntlHHGMoveBothSITRequestedAKZone2Army'); + } + + async buildIntlHHGMoveBothSITRequestedAKZone1AirForce() { + return this.buildDefault('IntlHHGMoveBothSITRequestedAKZone1AirForce'); + } + + async buildIntlHHGMoveBothSITRequestedAKZone2AirForce() { + return this.buildDefault('IntlHHGMoveBothSITRequestedAKZone2AirForce'); + } + + async buildIntlHHGMoveBothSITRequestedAKZone1SpaceForce() { + return this.buildDefault('IntlHHGMoveBothSITRequestedAKZone1SpaceForce'); + } + + async buildIntlHHGMoveBothSITRequestedAKZone2SpaceForce() { + return this.buildDefault('IntlHHGMoveBothSITRequestedAKZone2SpaceForce'); + } + + async buildIntlHHGMoveBothSITRequestedAKZone1USMC() { + return this.buildDefault('IntlHHGMoveBothSITRequestedAKZone1USMC'); + } + + async buildIntlHHGMoveBothSITRequestedAKZone2USMC() { + return this.buildDefault('IntlHHGMoveBothSITRequestedAKZone2USMC'); + } + + /** + * Use testharness to build international move with requested origin shuttle service + * @returns {Promise} + */ + async buildIntlHHGMoveOriginShuttleRequestedAKZone1Army() { + return this.buildDefault('IntlHHGMoveOriginShuttleRequestedAKZone1Army'); + } + + async buildIntlHHGMoveOriginShuttleRequestedAKZone2Army() { + return this.buildDefault('IntlHHGMoveOriginShuttleRequestedAKZone2Army'); + } + + async buildIntlHHGMoveOriginShuttleRequestedAKZone1AirForce() { + return this.buildDefault('IntlHHGMoveOriginShuttleRequestedAKZone1AirForce'); + } + + async buildIntlHHGMoveOriginShuttleRequestedAKZone2AirForce() { + return this.buildDefault('IntlHHGMoveOriginShuttleRequestedAKZone2AirForce'); + } + + async buildIntlHHGMoveOriginShuttleRequestedAKZone1SpaceForce() { + return this.buildDefault('IntlHHGMoveOriginShuttleRequestedAKZone1SpaceForce'); + } + + async buildIntlHHGMoveOriginShuttleRequestedAKZone2SpaceForce() { + return this.buildDefault('IntlHHGMoveOriginShuttleRequestedAKZone2SpaceForce'); + } + + async buildIntlHHGMoveOriginShuttleRequestedAKZone1USMC() { + return this.buildDefault('IntlHHGMoveOriginShuttleRequestedAKZone1USMC'); + } + + async buildIntlHHGMoveOriginShuttleRequestedAKZone2USMC() { + return this.buildDefault('IntlHHGMoveOriginShuttleRequestedAKZone2USMC'); + } + + /** + * Use testharness to build international move with requested destination shuttle service + * @returns {Promise} + */ + async buildIntlHHGMoveDestShuttleRequestedAKZone1Army() { + return this.buildDefault('IntlHHGMoveDestShuttleRequestedAKZone1Army'); + } + + async buildIntlHHGMoveDestShuttleRequestedAKZone2Army() { + return this.buildDefault('IntlHHGMoveDestShuttleRequestedAKZone2Army'); + } + + async buildIntlHHGMoveDestShuttleRequestedAKZone1AirForce() { + return this.buildDefault('IntlHHGMoveDestShuttleRequestedAKZone1AirForce'); + } + + async buildIntlHHGMoveDestShuttleRequestedAKZone2AirForce() { + return this.buildDefault('IntlHHGMoveDestShuttleRequestedAKZone2AirForce'); + } + + async buildIntlHHGMoveDestShuttleRequestedAKZone1SpaceForce() { + return this.buildDefault('IntlHHGMoveDestShuttleRequestedAKZone1SpaceForce'); + } + + async buildIntlHHGMoveDestShuttleRequestedAKZone2SpaceForce() { + return this.buildDefault('IntlHHGMoveDestShuttleRequestedAKZone2SpaceForce'); + } + + async buildIntlHHGMoveDestShuttleRequestedAKZone1USMC() { + return this.buildDefault('IntlHHGMoveDestShuttleRequestedAKZone1USMC'); + } + + async buildIntlHHGMoveDestShuttleRequestedAKZone2USMC() { + return this.buildDefault('IntlHHGMoveDestShuttleRequestedAKZone2USMC'); + } + + /** + * Use testharness to build international move with both requested origin & destination shuttle service + * @returns {Promise} + */ + async buildIntlHHGMoveBothShuttleRequestedAKZone1Army() { + return this.buildDefault('IntlHHGMoveBothShuttleRequestedAKZone1Army'); + } + + async buildIntlHHGMoveBothShuttleRequestedAKZone2Army() { + return this.buildDefault('IntlHHGMoveBothShuttleRequestedAKZone2Army'); + } + + async buildIntlHHGMoveBothShuttleRequestedAKZone1AirForce() { + return this.buildDefault('IntlHHGMoveBothShuttleRequestedAKZone1AirForce'); + } + + async buildIntlHHGMoveBothShuttleRequestedAKZone2AirForce() { + return this.buildDefault('IntlHHGMoveBothShuttleRequestedAKZone2AirForce'); + } + + async buildIntlHHGMoveBothShuttleRequestedAKZone1SpaceForce() { + return this.buildDefault('IntlHHGMoveBothShuttleRequestedAKZone1SpaceForce'); + } + + async buildIntlHHGMoveBothShuttleRequestedAKZone2SpaceForce() { + return this.buildDefault('IntlHHGMoveBothShuttleRequestedAKZone2SpaceForce'); + } + + async buildIntlHHGMoveBothShuttleRequestedAKZone1USMC() { + return this.buildDefault('IntlHHGMoveBothShuttleRequestedAKZone1USMC'); + } + + async buildIntlHHGMoveBothShuttleRequestedAKZone2USMC() { + return this.buildDefault('IntlHHGMoveBothShuttleRequestedAKZone2USMC'); + } + + /** + * Use testharness to build international move with requested destination address request + * @returns {Promise} + */ + async buildIntlHHGMoveDestAddressRequestedAKZone1Army() { + return this.buildDefault('IntlHHGMoveDestAddressRequestedAKZone1Army'); + } + + async buildIntlHHGMoveDestAddressRequestedAKZone2Army() { + return this.buildDefault('IntlHHGMoveDestAddressRequestedAKZone2Army'); + } + + async buildIntlHHGMoveDestAddressRequestedAKZone1AirForce() { + return this.buildDefault('IntlHHGMoveDestAddressRequestedAKZone1AirForce'); + } + + async buildIntlHHGMoveDestAddressRequestedAKZone2AirForce() { + return this.buildDefault('IntlHHGMoveDestAddressRequestedAKZone2AirForce'); + } + + async buildIntlHHGMoveDestAddressRequestedAKZone1SpaceForce() { + return this.buildDefault('IntlHHGMoveDestAddressRequestedAKZone1SpaceForce'); + } + + async buildIntlHHGMoveDestAddressRequestedAKZone2SpaceForce() { + return this.buildDefault('IntlHHGMoveDestAddressRequestedAKZone2SpaceForce'); + } + + async buildIntlHHGMoveDestAddressRequestedAKZone1USMC() { + return this.buildDefault('IntlHHGMoveDestAddressRequestedAKZone1USMC'); + } + + async buildIntlHHGMoveDestAddressRequestedAKZone2USMC() { + return this.buildDefault('IntlHHGMoveDestAddressRequestedAKZone2USMC'); + } + + /** + * Use testharness to build international move with a pending SIT extension request with origin SIT + * @returns {Promise} + */ + async buildIntlHHGMoveOriginSITExtensionRequestedAKZone1Army() { + return this.buildDefault('IntlHHGMoveOriginSITExtensionRequestedAKZone1Army'); + } + + async buildIntlHHGMoveOriginSITExtensionRequestedAKZone2Army() { + return this.buildDefault('IntlHHGMoveOriginSITExtensionRequestedAKZone2Army'); + } + + async buildIntlHHGMoveOriginSITExtensionRequestedAKZone1AirForce() { + return this.buildDefault('IntlHHGMoveOriginSITExtensionRequestedAKZone1AirForce'); + } + + async buildIntlHHGMoveOriginSITExtensionRequestedAKZone2AirForce() { + return this.buildDefault('IntlHHGMoveOriginSITExtensionRequestedAKZone2AirForce'); + } + + async buildIntlHHGMoveOriginSITExtensionRequestedAKZone1SpaceForce() { + return this.buildDefault('IntlHHGMoveOriginSITExtensionRequestedAKZone1SpaceForce'); + } + + async buildIntlHHGMoveOriginSITExtensionRequestedAKZone2SpaceForce() { + return this.buildDefault('IntlHHGMoveOriginSITExtensionRequestedAKZone2SpaceForce'); + } + + async buildIntlHHGMoveOriginSITExtensionRequestedAKZone1USMC() { + return this.buildDefault('IntlHHGMoveOriginSITExtensionRequestedAKZone1USMC'); + } + + async buildIntlHHGMoveOriginSITExtensionRequestedAKZone2USMC() { + return this.buildDefault('IntlHHGMoveOriginSITExtensionRequestedAKZone2USMC'); + } + + /** + * Use testharness to build international move with a pending SIT extension request with destination SIT + * @returns {Promise} + */ + async buildIntlHHGMoveDestSITExtensionRequestedAKZone1Army() { + return this.buildDefault('IntlHHGMoveDestSITExtensionRequestedAKZone1Army'); + } + + async buildIntlHHGMoveDestSITExtensionRequestedAKZone2Army() { + return this.buildDefault('IntlHHGMoveDestSITExtensionRequestedAKZone2Army'); + } + + async buildIntlHHGMoveDestSITExtensionRequestedAKZone1AirForce() { + return this.buildDefault('IntlHHGMoveDestSITExtensionRequestedAKZone1AirForce'); + } + + async buildIntlHHGMoveDestSITExtensionRequestedAKZone2AirForce() { + return this.buildDefault('IntlHHGMoveDestSITExtensionRequestedAKZone2AirForce'); + } + + async buildIntlHHGMoveDestSITExtensionRequestedAKZone1SpaceForce() { + return this.buildDefault('IntlHHGMoveDestSITExtensionRequestedAKZone1SpaceForce'); + } + + async buildIntlHHGMoveDestSITExtensionRequestedAKZone2SpaceForce() { + return this.buildDefault('IntlHHGMoveDestSITExtensionRequestedAKZone2SpaceForce'); + } + + async buildIntlHHGMoveDestSITExtensionRequestedAKZone1USMC() { + return this.buildDefault('IntlHHGMoveDestSITExtensionRequestedAKZone1USMC'); + } + + async buildIntlHHGMoveDestSITExtensionRequestedAKZone2USMC() { + return this.buildDefault('IntlHHGMoveDestSITExtensionRequestedAKZone2USMC'); + } + + /** + * Use testharness to build international move with an at risk of excess weight + * @returns {Promise} + */ + async buildIntlHHGMoveExcessWeightAKZone1Army() { + return this.buildDefault('IntlHHGMoveExcessWeightAKZone1Army'); + } + + async buildIntlHHGMoveExcessWeightAKZone2Army() { + return this.buildDefault('IntlHHGMoveExcessWeightAKZone2Army'); + } + + /** + * Use testharness to build international move with an at risk of UB excess weight + * @returns {Promise} + */ + async buildIntlUBMoveExcessWeightAKZone1Army() { + return this.buildDefault('IntlUBMoveExcessWeightAKZone1Army'); + } + + async buildIntlUBMoveExcessWeightAKZone2Army() { + return this.buildDefault('IntlUBMoveExcessWeightAKZone2Army'); + } } export default TestHarness; diff --git a/scripts/db-truncate b/scripts/db-truncate index f1b0d6db10a..3494c718de2 100755 --- a/scripts/db-truncate +++ b/scripts/db-truncate @@ -15,10 +15,10 @@ BEGIN 'ports','port_locations', 're_fsc_multipliers', 'ghc_diesel_fuel_prices', 're_zip3s','zip3_distances', 're_contracts', 're_domestic_service_areas', 're_intl_prices', 're_intl_other_prices', 're_domestic_linehaul_prices', - 're_domestic_service_area_prices', 're_domestic_other_prices', 'pay_grades', - 'hhg_allowances', - 'jppso_regions', 'jppso_region_state_assignments', 'gbloc_aors', 'roles')) LOOP + 're_domestic_service_area_prices', 're_domestic_other_prices', + 'jppso_regions', 'jppso_region_state_assignments', 'gbloc_aors', + 'pay_grades', 'hhg_allowances', 'roles')) LOOP EXECUTE 'TRUNCATE TABLE ' || quote_ident(r.tablename) || ' CASCADE'; END LOOP; END \$\$; -" \ No newline at end of file +" diff --git a/scripts/prereqs b/scripts/prereqs index 23dccde6126..e7591f86fd2 100755 --- a/scripts/prereqs +++ b/scripts/prereqs @@ -58,4 +58,4 @@ if [ -z "${SKIP_CHECKS:-}" ]; then check-go-version check-hosts-file check-node-version -fi +fi \ No newline at end of file diff --git a/scripts/run-server-test b/scripts/run-server-test index 45e8ab87afe..497039f6186 100755 --- a/scripts/run-server-test +++ b/scripts/run-server-test @@ -47,6 +47,12 @@ else gotest_args+=("-parallel" "8") gotest_args+=("-failfast") fi + +## Add SKIP_FAIL_TESTS on dev machine within .envrc.local file +if [ -n "${SKIP_FAIL_TESTS+x}" ]; then + gotest_args+=("-skip" "TestGHCRateEngineImportSuite") +fi + ## mac users uncomment the following line to run tests with the classic linker, which clears a lot of warnings that fill the console, do not commit to repo uncommented #gotest_args+=("-ldflags=-extldflags=-Wl,-ld_classic") diff --git a/src/components/BulkAssignment/BulkAssignmentModal.jsx b/src/components/BulkAssignment/BulkAssignmentModal.jsx index ca58c6814a2..a4522e8bd76 100644 --- a/src/components/BulkAssignment/BulkAssignmentModal.jsx +++ b/src/components/BulkAssignment/BulkAssignmentModal.jsx @@ -1,46 +1,126 @@ -import React from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import PropTypes from 'prop-types'; import { Button } from '@trussworks/react-uswds'; +import styles from './BulkAssignmentModal.module.scss'; + import Modal, { ModalTitle, ModalClose, ModalActions, connectModal } from 'components/Modal/Modal'; +import { getBulkAssignmentData } from 'services/ghcApi'; +import { milmoveLogger } from 'utils/milmoveLog'; +import { userName } from 'utils/formatters'; + +export const BulkAssignmentModal = ({ onClose, onSubmit, title, submitText, closeText, queueType }) => { + const [bulkAssignmentData, setBulkAssignmentData] = useState(null); + const [isDisabled, setIsDisabled] = useState(false); + const [numberOfMoves, setNumberOfMoves] = useState(0); + const [showCancelModal, setShowCancelModal] = useState(false); + const fetchData = useCallback(async () => { + try { + const data = await getBulkAssignmentData(queueType); + setBulkAssignmentData(data); + + if (!data.bulkAssignmentMoveIDs) { + setIsDisabled(true); + setNumberOfMoves(0); + } else { + setNumberOfMoves(data.bulkAssignmentMoveIDs.length); + } + } catch (err) { + milmoveLogger.error('Error fetching bulk assignment data:', err); + } + }, [queueType]); -export const BulkAssignmentModal = ({ onClose, onSubmit, title, content, submitText, closeText }) => ( - - onClose()} /> - -

{title}

-
-

{content}

- - - - -
-); + useEffect(() => { + fetchData(); + }, [fetchData]); + + return ( +
+ + {!showCancelModal && setShowCancelModal(true)} />} + +

+ {title} ({numberOfMoves}) +

+
+
+ + + + + + + {bulkAssignmentData?.availableOfficeUsers?.map((user) => { + return ( + + + + + + ); + })} +
UserWorkloadAssignment
+

{userName(user)}

+
+

{user.workload || 0}

+
+ +
+
+ {showCancelModal ? ( +
+ Any unsaved work will be lost. Are you sure you want to cancel? +
+ + +
+
+ ) : ( + + + + + )} +
+
+ ); +}; BulkAssignmentModal.propTypes = { onClose: PropTypes.func.isRequired, onSubmit: PropTypes.func.isRequired, title: PropTypes.string, - content: PropTypes.string, submitText: PropTypes.string, closeText: PropTypes.string, }; BulkAssignmentModal.defaultProps = { title: 'Bulk Assignment', - content: 'Here we will display moves to be assigned in bulk.', submitText: 'Save', closeText: 'Cancel', }; diff --git a/src/components/BulkAssignment/BulkAssignmentModal.module.scss b/src/components/BulkAssignment/BulkAssignmentModal.module.scss new file mode 100644 index 00000000000..4fa9fd266a5 --- /dev/null +++ b/src/components/BulkAssignment/BulkAssignmentModal.module.scss @@ -0,0 +1,72 @@ +@import 'shared/styles/colors.scss'; + +.BulkModal { + min-width: 650px !important; + max-width: 90vw; + overflow-y: auto; + max-height: 90vh; + overflow-x: hidden; + + button, + :global(.usa-button) { + margin: 0; + flex-grow: 0; + flex-basis: auto; + text-decoration: none; + font-size: 1rem; + } + + /* these styles are used to fix an issue with duplicate usa-button--secondary definitions in USWDS */ + .cancelNoButton { + border-radius: 0.2666666667rem; + color: #0050d8; + background-color: transparent; + box-shadow: inset 0 0 0 2px #0050d8; + padding-left: 1.6rem; + padding-right: 1.6rem; + padding-bottom: 0.8rem; + padding-top: 0.8rem; + height: 42px; + width: 68px; + } +} + +.BulkAssignmentTable { + table { + th { + max-width: 10px; + text-align: center; + } + .BulkAssignmentDataCenter { + text-align: center; + } + .BulkAssignmentAssignment { + width: 60px; + text-align: center; + padding-left: 15px; + padding-top: 4px; + } + } +} + +.areYouSureSection { + display: flex; + justify-content: space-evenly; + align-items: center; + padding-top: 20px; + + .confirmButtons { + display: flex; + align-items: center; + gap: 10px; + padding: 10px; + max-height: 42px; + } + + .cancelYesButton { + font-size: 12px; + max-width: 100px; + height: 42px; + align-items: center; + } +} diff --git a/src/components/BulkAssignment/BulkAssignmentModal.test.jsx b/src/components/BulkAssignment/BulkAssignmentModal.test.jsx index 4ffc69e7bd3..d1cf48141ef 100644 --- a/src/components/BulkAssignment/BulkAssignmentModal.test.jsx +++ b/src/components/BulkAssignment/BulkAssignmentModal.test.jsx @@ -1,8 +1,10 @@ import React from 'react'; -import { render, screen } from '@testing-library/react'; +import { act, render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { BulkAssignmentModal } from 'components/BulkAssignment/BulkAssignmentModal'; +import { QUEUE_TYPES } from 'constants/queues'; +import { MockProviders } from 'testUtils'; let onClose; let onSubmit; @@ -11,40 +13,105 @@ beforeEach(() => { onSubmit = jest.fn(); }); +const bulkAssignmentData = { + availableOfficeUsers: [ + { + firstName: 'sc', + lastName: 'user', + officeUserId: '045c3048-df9a-4d44-88ed-8cd6e2100e08', + workload: 1, + }, + { + firstName: 'test1', + lastName: 'person', + officeUserId: '4b1f2722-b0bf-4b16-b8c4-49b4e49ba42a', + }, + ], + bulkAssignmentMoveIDs: [ + 'b3baf6ce-f43b-437c-85be-e1145c0ddb96', + '962ce8d2-03a2-435c-94ca-6b9ef6c226c1', + 'fee7f916-35a6-4c0b-9ea6-a1d8094b3ed3', + ], +}; + +jest.mock('services/ghcApi', () => ({ + getBulkAssignmentData: jest.fn().mockImplementation(() => Promise.resolve(bulkAssignmentData)), +})); + describe('BulkAssignmentModal', () => { it('renders the component', async () => { - render(); + render( + + + , + ); - expect(await screen.findByRole('heading', { level: 3, name: 'Bulk Assignment' })).toBeInTheDocument(); + expect(await screen.findByRole('heading', { level: 3, name: 'Bulk Assignment (3)' })).toBeInTheDocument(); }); - it('closes the modal when close icon is clicked', async () => { + it('shows cancel confirmation modal when close icon is clicked', async () => { render(); const closeButton = await screen.findByTestId('modalCloseButton'); await userEvent.click(closeButton); - expect(onClose).toHaveBeenCalledTimes(1); + expect(screen.getByTestId('cancelModalYes')).toBeInTheDocument(); }); - it('closes the modal when the Cancel button is clicked', async () => { + it('shows cancel confirmation modal when the Cancel button is clicked', async () => { render(); const cancelButton = await screen.findByRole('button', { name: 'Cancel' }); await userEvent.click(cancelButton); - expect(onClose).toHaveBeenCalledTimes(1); + expect(screen.getByTestId('cancelModalYes')).toBeInTheDocument(); }); it('calls the submit function when Save button is clicked', async () => { render(); + const saveButton = await screen.findByTestId('modalSubmitButton'); + await userEvent.click(saveButton); + expect(onSubmit).toHaveBeenCalledTimes(1); + }); - const saveButton = await screen.findByRole('button', { name: 'Save' }); + it('renders the user data', async () => { + render(); + const userTable = await screen.findByRole('table'); + expect(userTable).toBeInTheDocument(); + expect(screen.getByText('User')).toBeInTheDocument(); + expect(screen.getByText('Workload')).toBeInTheDocument(); + expect(screen.getByText('Assignment')).toBeInTheDocument(); + await act(async () => { + expect(await screen.getByText('user, sc')).toBeInTheDocument(); + }); + expect(screen.getAllByTestId('bulkAssignmentUserWorkload')[0]).toHaveTextContent('1'); + }); - await userEvent.click(saveButton); + it('closes the modal when the close is confirmed', async () => { + render(); - expect(onSubmit).toHaveBeenCalledTimes(1); + const closeButton = await screen.findByTestId('modalCloseButton'); + + await userEvent.click(closeButton); + + const confirmButton = await screen.findByTestId('cancelModalYes'); + await userEvent.click(confirmButton); + + expect(onClose).toHaveBeenCalledTimes(1); + }); + + it('close confirmation goes away when clicking no', async () => { + render(); + + const closeButton = await screen.findByTestId('modalCloseButton'); + await userEvent.click(closeButton); + + const cancelModalNo = await screen.findByTestId('cancelModalNo'); + await userEvent.click(cancelModalNo); + + const confirmButton = await screen.queryByTestId('cancelModalYes'); + expect(confirmButton).not.toBeInTheDocument(); }); }); diff --git a/src/components/Customer/EditOrdersForm/EditOrdersForm.test.jsx b/src/components/Customer/EditOrdersForm/EditOrdersForm.test.jsx index e073ddcc883..2af2c863552 100644 --- a/src/components/Customer/EditOrdersForm/EditOrdersForm.test.jsx +++ b/src/components/Customer/EditOrdersForm/EditOrdersForm.test.jsx @@ -195,7 +195,6 @@ const initialValues = { report_by_date: '2020-11-26', has_dependents: 'no', origin_duty_location: { - provides_services_counseling: true, address: { city: 'Des Moines', country: 'US', @@ -212,6 +211,7 @@ const initialValues = { id: 'f9299768-16d2-4a13-ae39-7087a58b1f62', name: 'Yuma AFB', updated_at: '2020-10-19T17:01:16.114Z', + provides_services_counseling: true, }, counseling_office_id: '3e937c1f-5539-4919-954d-017989130584', new_duty_location: { @@ -344,7 +344,6 @@ describe('EditOrdersForm component', () => { await waitFor(() => { expect(submitButton).not.toBeDisabled(); }); - await userEvent.selectOptions(screen.getByLabelText(/Orders type/), ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION); await userEvent.type(screen.getByLabelText(/Orders date/), '08 Nov 2020'); await userEvent.type(screen.getByLabelText(/Report by date/), '26 Nov 2020'); diff --git a/src/components/Customer/MtoShipmentForm/MtoShipmentForm.jsx b/src/components/Customer/MtoShipmentForm/MtoShipmentForm.jsx index d8e04da7494..d69159fd9e5 100644 --- a/src/components/Customer/MtoShipmentForm/MtoShipmentForm.jsx +++ b/src/components/Customer/MtoShipmentForm/MtoShipmentForm.jsx @@ -51,6 +51,7 @@ import withRouter from 'utils/routing'; import { ORDERS_TYPE } from 'constants/orders'; import { isBooleanFlagEnabled } from 'utils/featureFlags'; import { dateSelectionWeekendHolidayCheck } from 'utils/calendar'; +import { isPreceedingAddressComplete } from 'shared/utils'; const blankAddress = { address: { @@ -105,7 +106,7 @@ class MtoShipmentForm extends Component { const { moveId } = params; const isNTSR = shipmentType === SHIPMENT_OPTIONS.NTSR; - const saveDeliveryAddress = hasDeliveryAddress === 'yes' || isNTSR; + const saveDeliveryAddress = hasDeliveryAddress === 'true' || isNTSR; const preformattedMtoShipment = { shipmentType, @@ -116,14 +117,14 @@ class MtoShipmentForm extends Component { ...delivery, address: saveDeliveryAddress ? delivery.address : undefined, }, - hasSecondaryPickup: hasSecondaryPickup === 'yes', - secondaryPickup: hasSecondaryPickup === 'yes' ? secondaryPickup : {}, - hasSecondaryDelivery: hasSecondaryDelivery === 'yes', - secondaryDelivery: hasSecondaryDelivery === 'yes' ? secondaryDelivery : {}, - hasTertiaryPickup: hasTertiaryPickup === 'yes', - tertiaryPickup: hasTertiaryPickup === 'yes' ? tertiaryPickup : {}, - hasTertiaryDelivery: hasTertiaryDelivery === 'yes', - tertiaryDelivery: hasTertiaryDelivery === 'yes' ? tertiaryDelivery : {}, + hasSecondaryPickup: hasSecondaryPickup === 'true', + secondaryPickup: hasSecondaryPickup === 'true' ? secondaryPickup : {}, + hasSecondaryDelivery: hasSecondaryDelivery === 'true', + secondaryDelivery: hasSecondaryDelivery === 'true' ? secondaryDelivery : {}, + hasTertiaryPickup: hasTertiaryPickup === 'true', + tertiaryPickup: hasTertiaryPickup === 'true' ? tertiaryPickup : {}, + hasTertiaryDelivery: hasTertiaryDelivery === 'true', + tertiaryDelivery: hasTertiaryDelivery === 'true' ? tertiaryDelivery : {}, }; const pendingMtoShipment = formatMtoShipmentForAPI(preformattedMtoShipment); @@ -379,9 +380,9 @@ class MtoShipmentForm extends Component { data-testid="has-secondary-pickup" label="Yes" name="hasSecondaryPickup" - value="yes" + value="true" title="Yes, I have a second pickup address" - checked={hasSecondaryPickup === 'yes'} + checked={hasSecondaryPickup === 'true'} />
- {hasSecondaryPickup === 'yes' && ( + {hasSecondaryPickup === 'true' && ( )} - {isTertiaryAddressEnabled && hasSecondaryPickup === 'yes' && ( + {isTertiaryAddressEnabled && hasSecondaryPickup === 'true' && (

Do you want movers to pick up any belongings from a third address?

@@ -414,9 +415,15 @@ class MtoShipmentForm extends Component { data-testid="has-tertiary-pickup" label="Yes" name="hasTertiaryPickup" - value="yes" + value="true" title="Yes, I have a third pickup address" - checked={hasTertiaryPickup === 'yes'} + checked={hasTertiaryPickup === 'true'} + disabled={ + !isPreceedingAddressComplete( + hasSecondaryPickup, + values.secondaryPickup.address, + ) + } />
)} {isTertiaryAddressEnabled && - hasTertiaryPickup === 'yes' && - hasSecondaryPickup === 'yes' && ( + hasTertiaryPickup === 'true' && + hasSecondaryPickup === 'true' && ( <>

Third Pickup Address

)} - {(hasDeliveryAddress === 'yes' || isNTSR) && ( + {(hasDeliveryAddress === 'true' || isNTSR) && (
- {hasSecondaryDelivery === 'yes' && ( + {hasSecondaryDelivery === 'true' && ( )} - {isTertiaryAddressEnabled && hasSecondaryDelivery === 'yes' && ( + {isTertiaryAddressEnabled && hasSecondaryDelivery === 'true' && (

Do you want movers to deliver any belongings to a third address?

@@ -569,9 +584,15 @@ class MtoShipmentForm extends Component { data-testid="has-tertiary-delivery" label="Yes" name="hasTertiaryDelivery" - value="yes" + value="true" title="Yes, I have a third delivery address" - checked={hasTertiaryDelivery === 'yes'} + checked={hasTertiaryDelivery === 'true'} + disabled={ + !isPreceedingAddressComplete( + hasSecondaryDelivery, + values.secondaryDelivery.address, + ) + } />
)} {isTertiaryAddressEnabled && - hasTertiaryDelivery === 'yes' && - hasSecondaryDelivery === 'yes' && ( + hasTertiaryDelivery === 'true' && + hasSecondaryDelivery === 'true' && ( <>

Third Delivery Address

)} - {hasDeliveryAddress === 'no' && !isRetireeSeparatee && !isNTSR && ( + {hasDeliveryAddress === 'false' && !isRetireeSeparatee && !isNTSR && (

We can use the zip of your new duty location.
@@ -616,7 +643,7 @@ class MtoShipmentForm extends Component { You can add the specific delivery address later, once you know it.

)} - {hasDeliveryAddress === 'no' && isRetireeSeparatee && !isNTSR && ( + {hasDeliveryAddress === 'false' && isRetireeSeparatee && !isNTSR && (

We can use the zip of the HOR, PLEAD or HOS you entered with your orders.
diff --git a/src/components/Customer/MtoShipmentForm/MtoShipmentForm.test.jsx b/src/components/Customer/MtoShipmentForm/MtoShipmentForm.test.jsx index 424bbe04d55..4205d3e9155 100644 --- a/src/components/Customer/MtoShipmentForm/MtoShipmentForm.test.jsx +++ b/src/components/Customer/MtoShipmentForm/MtoShipmentForm.test.jsx @@ -388,24 +388,24 @@ describe('MtoShipmentForm component', () => { await userEvent.click(screen.getByTitle('Yes, I have a second delivery address')); const streetAddress1 = await screen.findAllByLabelText(/Address 1/); - expect(streetAddress1.length).toBe(3); - expect(streetAddress1[2]).toHaveAttribute('name', 'secondaryDelivery.address.streetAddress1'); + expect(streetAddress1[0]).toHaveAttribute('name', 'pickup.address.streetAddress1'); + expect(streetAddress1[1]).toHaveAttribute('name', 'delivery.address.streetAddress1'); const streetAddress2 = await screen.findAllByLabelText(/Address 2/); - expect(streetAddress2.length).toBe(3); - expect(streetAddress2[2]).toHaveAttribute('name', 'secondaryDelivery.address.streetAddress2'); + expect(streetAddress2[0]).toHaveAttribute('name', 'pickup.address.streetAddress2'); + expect(streetAddress2[1]).toHaveAttribute('name', 'delivery.address.streetAddress2'); const city = screen.getAllByTestId('City'); - expect(city.length).toBe(3); - expect(city[2]).toHaveAttribute('aria-label', 'secondaryDelivery.address.city'); + expect(city[0]).toHaveAttribute('aria-label', 'pickup.address.city'); + expect(city[1]).toHaveAttribute('aria-label', 'delivery.address.city'); const state = await screen.getAllByTestId(/State/); - expect(state.length).toBe(3); - expect(state[2]).toHaveAttribute('aria-label', 'secondaryDelivery.address.state'); + expect(state[0]).toHaveAttribute('aria-label', 'pickup.address.state'); + expect(state[1]).toHaveAttribute('aria-label', 'delivery.address.state'); const zip = await screen.getAllByTestId(/ZIP/); - expect(zip.length).toBe(3); - expect(zip[2]).toHaveAttribute('aria-label', 'secondaryDelivery.address.postalCode'); + expect(zip[0]).toHaveAttribute('aria-label', 'pickup.address.postalCode'); + expect(zip[1]).toHaveAttribute('aria-label', 'delivery.address.postalCode'); }); it('goes back when the back button is clicked', async () => { diff --git a/src/components/Customer/OrdersInfoForm/OrdersInfoForm.jsx b/src/components/Customer/OrdersInfoForm/OrdersInfoForm.jsx index 51ca8552b27..e4ab0bf904c 100644 --- a/src/components/Customer/OrdersInfoForm/OrdersInfoForm.jsx +++ b/src/components/Customer/OrdersInfoForm/OrdersInfoForm.jsx @@ -36,7 +36,6 @@ const OrdersInfoForm = ({ ordersTypeOptions, initialValues, onSubmit, onBack }) const [hasDependents, setHasDependents] = useState(false); const [isOconusMove, setIsOconusMove] = useState(false); const [enableUB, setEnableUB] = useState(false); - const [isHasDependentsDisabled, setHasDependentsDisabled] = useState(false); const [prevOrderType, setPrevOrderType] = useState(''); const [filteredOrderTypeOptions, setFilteredOrderTypeOptions] = useState(ordersTypeOptions); diff --git a/src/components/Customer/PPM/Booking/DateAndLocationForm/DateAndLocationForm.jsx b/src/components/Customer/PPM/Booking/DateAndLocationForm/DateAndLocationForm.jsx index ca158d027cd..f4df895c05a 100644 --- a/src/components/Customer/PPM/Booking/DateAndLocationForm/DateAndLocationForm.jsx +++ b/src/components/Customer/PPM/Booking/DateAndLocationForm/DateAndLocationForm.jsx @@ -21,6 +21,7 @@ import { OptionalAddressSchema } from 'components/Customer/MtoShipmentForm/valid import { requiredAddressSchema, partialRequiredAddressSchema } from 'utils/validation'; import { isBooleanFlagEnabled } from 'utils/featureFlags'; import RequiredTag from 'components/form/RequiredTag'; +import { isSecondaryAddressCompletePPM } from 'shared/utils'; let meta = ''; @@ -276,6 +277,12 @@ const DateAndLocationForm = ({ mtoShipment, destinationDutyLocation, serviceMemb value="true" title="Yes, I have a third delivery address" checked={values.hasTertiaryPickupAddress === 'true'} + disabled={ + !isSecondaryAddressCompletePPM( + values.hasSecondaryPickupAddress, + values.secondaryPickupAddress.address, + ) + } /> @@ -390,6 +403,12 @@ const DateAndLocationForm = ({ mtoShipment, destinationDutyLocation, serviceMemb value="true" title="Yes, I have a third delivery address" checked={values.hasTertiaryDestinationAddress === 'true'} + disabled={ + !isSecondaryAddressCompletePPM( + values.hasSecondaryDestinationAddress, + values.secondaryDestinationAddress.address, + ) + } /> diff --git a/src/components/Customer/Review/ShipmentCard/MobileHomeShipmentCard/MobileHomeShipmentCard.test.jsx b/src/components/Customer/Review/ShipmentCard/MobileHomeShipmentCard/MobileHomeShipmentCard.test.jsx index 6c787861d14..4bfe62f5017 100644 --- a/src/components/Customer/Review/ShipmentCard/MobileHomeShipmentCard/MobileHomeShipmentCard.test.jsx +++ b/src/components/Customer/Review/ShipmentCard/MobileHomeShipmentCard/MobileHomeShipmentCard.test.jsx @@ -61,7 +61,7 @@ describe('MobileHomeShipmentCard component', () => { it('renders component with all fields', () => { render(); - expect(screen.getAllByTestId('ShipmentCardNumber').length).toBe(1); + expect(screen.getByRole('heading', { level: 3 })).toHaveTextContent('Mobile Home 1'); expect(screen.getByText(/^#testMove123-01$/, { selector: 'p' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Edit' })).toBeInTheDocument(); @@ -139,6 +139,11 @@ describe('MobileHomeShipmentCard component', () => { expect(screen.getByTitle('Help about incomplete shipment')).toBeInTheDocument(); await userEvent.click(screen.getByTitle('Help about incomplete shipment')); - expect(screen.getAllByTestId('ShipmentCardNumber').length).toBe(1); + + expect(incompleteShipmentProps.onIncompleteClick).toHaveBeenCalledWith( + 'Mobile Home 1', + 'testMove123-01', + SHIPMENT_TYPES.MOBILE_HOME, + ); }); }); diff --git a/src/components/DocumentViewer/DocumentViewer.jsx b/src/components/DocumentViewer/DocumentViewer.jsx index ceb30cda9c5..c28661850bf 100644 --- a/src/components/DocumentViewer/DocumentViewer.jsx +++ b/src/components/DocumentViewer/DocumentViewer.jsx @@ -16,6 +16,8 @@ import { bulkDownloadPaymentRequest, updateUpload } from 'services/ghcApi'; import { formatDate } from 'shared/dates'; import { filenameFromPath } from 'utils/formatters'; import AsyncPacketDownloadLink from 'shared/AsyncPacketDownloadLink/AsyncPacketDownloadLink'; +import { UPLOAD_DOC_STATUS, UPLOAD_SCAN_STATUS, UPLOAD_DOC_STATUS_DISPLAY_MESSAGE } from 'shared/constants'; +import Alert from 'shared/Alert'; /** * TODO @@ -23,13 +25,15 @@ import AsyncPacketDownloadLink from 'shared/AsyncPacketDownloadLink/AsyncPacketD * - implement rotate left/right */ -const DocumentViewer = ({ files, allowDownload, paymentRequestId }) => { +const DocumentViewer = ({ files, allowDownload, paymentRequestId, isFileUploading }) => { const [selectedFileIndex, selectFile] = useState(0); const [disableSaveButton, setDisableSaveButton] = useState(false); const [menuIsOpen, setMenuOpen] = useState(false); const [showContentError, setShowContentError] = useState(false); const sortedFiles = files.sort((a, b) => moment(b.createdAt) - moment(a.createdAt)); const selectedFile = sortedFiles[parseInt(selectedFileIndex, 10)]; + const [isJustUploadedFile, setIsJustUploadedFile] = useState(false); + const [fileStatus, setFileStatus] = useState(null); const [rotationValue, setRotationValue] = useState(selectedFile?.rotation || 0); @@ -37,6 +41,15 @@ const DocumentViewer = ({ files, allowDownload, paymentRequestId }) => { const queryClient = useQueryClient(); + useEffect(() => { + if (isFileUploading) { + setIsJustUploadedFile(true); + setFileStatus(UPLOAD_DOC_STATUS.UPLOADING); + } else { + setIsJustUploadedFile(false); + } + }, [isFileUploading]); + const { mutate: mutateUploads } = useMutation(updateUpload, { onSuccess: async (data, variables) => { if (mountedRef.current) { @@ -75,12 +88,85 @@ const DocumentViewer = ({ files, allowDownload, paymentRequestId }) => { useEffect(() => { setShowContentError(false); setRotationValue(selectedFile?.rotation || 0); - }, [selectedFile]); + const handleFileProcessing = async (status) => { + switch (status) { + case UPLOAD_SCAN_STATUS.PROCESSING: + setFileStatus(UPLOAD_DOC_STATUS.SCANNING); + break; + case UPLOAD_SCAN_STATUS.CLEAN: + setFileStatus(UPLOAD_DOC_STATUS.ESTABLISHING); + break; + case UPLOAD_SCAN_STATUS.INFECTED: + setFileStatus(UPLOAD_DOC_STATUS.INFECTED); + break; + default: + throw new Error(`unrecognized file status`); + } + }; + if (!isFileUploading && isJustUploadedFile) { + setFileStatus(UPLOAD_DOC_STATUS.UPLOADING); + } + + let sse; + if (selectedFile) { + sse = new EventSource(`/ghc/v1/uploads/${selectedFile.id}/status`, { withCredentials: true }); + sse.onmessage = (event) => { + handleFileProcessing(event.data); + if ( + event.data === UPLOAD_SCAN_STATUS.CLEAN || + event.data === UPLOAD_SCAN_STATUS.INFECTED || + event.data === 'Connection closed' + ) { + sse.close(); + } + }; + sse.onerror = () => { + sse.close(); + setFileStatus(null); + }; + } + + return () => { + sse?.close(); + }; + }, [selectedFile, isFileUploading, isJustUploadedFile]); + useEffect(() => { + if (fileStatus === UPLOAD_DOC_STATUS.ESTABLISHING) { + setTimeout(() => { + setFileStatus(UPLOAD_DOC_STATUS.LOADED); + }, 2000); + } + }, [fileStatus]); const fileType = useRef(selectedFile?.contentType); - if (!selectedFile) { - return

File Not Found

; + const getStatusMessage = (currentFileStatus, currentSelectedFile) => { + switch (currentFileStatus) { + case UPLOAD_DOC_STATUS.UPLOADING: + return UPLOAD_DOC_STATUS_DISPLAY_MESSAGE.UPLOADING; + case UPLOAD_DOC_STATUS.SCANNING: + return UPLOAD_DOC_STATUS_DISPLAY_MESSAGE.SCANNING; + case UPLOAD_DOC_STATUS.ESTABLISHING: + return UPLOAD_DOC_STATUS_DISPLAY_MESSAGE.ESTABLISHING_DOCUMENT_FOR_VIEWING; + case UPLOAD_DOC_STATUS.INFECTED: + return UPLOAD_DOC_STATUS_DISPLAY_MESSAGE.INFECTED_FILE_MESSAGE; + default: + if (!currentSelectedFile) { + return UPLOAD_DOC_STATUS_DISPLAY_MESSAGE.FILE_NOT_FOUND; + } + return null; + } + }; + + const alertMessage = getStatusMessage(fileStatus, selectedFile); + const alertType = fileStatus === UPLOAD_SCAN_STATUS.INFECTED ? 'error' : 'info'; + const alertHeading = fileStatus === UPLOAD_SCAN_STATUS.INFECTED ? 'Ask for a new file' : 'Document Status'; + if (alertMessage) { + return ( + + {alertMessage} + + ); } const openMenu = () => { @@ -92,6 +178,7 @@ const DocumentViewer = ({ files, allowDownload, paymentRequestId }) => { const handleSelectFile = (index) => { selectFile(index); + setFileStatus(UPLOAD_DOC_STATUS.ESTABLISHING); closeMenu(); }; diff --git a/src/components/DocumentViewer/DocumentViewer.stories.jsx b/src/components/DocumentViewer/DocumentViewer.stories.jsx index 9beae2af445..6aafb548b5e 100644 --- a/src/components/DocumentViewer/DocumentViewer.stories.jsx +++ b/src/components/DocumentViewer/DocumentViewer.stories.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; import DocumentViewer from './DocumentViewer'; import pdf from './sample.pdf'; diff --git a/src/components/DocumentViewer/DocumentViewer.test.jsx b/src/components/DocumentViewer/DocumentViewer.test.jsx index b5a211cd951..9de2f71a640 100644 --- a/src/components/DocumentViewer/DocumentViewer.test.jsx +++ b/src/components/DocumentViewer/DocumentViewer.test.jsx @@ -1,8 +1,7 @@ /* eslint-disable react/jsx-props-no-spreading */ import React from 'react'; -import { render, screen, waitFor } from '@testing-library/react'; +import { screen, waitFor, act } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; import DocumentViewer from './DocumentViewer'; import samplePDF from './sample.pdf'; @@ -11,6 +10,8 @@ import samplePNG from './sample2.png'; import sampleGIF from './sample3.gif'; import { bulkDownloadPaymentRequest } from 'services/ghcApi'; +import { UPLOAD_SCAN_STATUS, UPLOAD_DOC_STATUS_DISPLAY_MESSAGE } from 'shared/constants'; +import { renderWithProviders } from 'testUtils'; const toggleMenuClass = () => { const container = document.querySelector('[data-testid="menuButtonContainer"]'); @@ -18,6 +19,17 @@ const toggleMenuClass = () => { container.className = container.className === 'closed' ? 'open' : 'closed'; } }; +// Mocking necessary functions/module +const mockMutateUploads = jest.fn(); + +jest.mock('@tanstack/react-query', () => ({ + ...jest.requireActual('@tanstack/react-query'), + useMutation: () => ({ mutate: mockMutateUploads }), +})); + +beforeEach(() => { + jest.clearAllMocks(); +}); const mockFiles = [ { @@ -112,11 +124,7 @@ jest.mock('./Content/Content', () => ({ describe('DocumentViewer component', () => { it('initial state is closed menu and first file selected', async () => { - render( - - - , - ); + renderWithProviders(); const selectedFileTitle = await screen.getAllByTestId('documentTitle')[0]; expect(selectedFileTitle.textContent).toEqual('Test File 4.gif - Added on 16 Jun 2021'); @@ -126,23 +134,14 @@ describe('DocumentViewer component', () => { }); it('renders the file creation date with the correctly sorted props', async () => { - render( - - - , - ); - + renderWithProviders(); const files = screen.getAllByRole('listitem'); expect(files[0].textContent).toContain('Test File 4.gif - Added on 2021-06-16T15:09:26.979879Z'); }); it('renders the title bar with the correct props', async () => { - render( - - - , - ); + renderWithProviders(); const title = await screen.getAllByTestId('documentTitle')[0]; @@ -150,11 +149,7 @@ describe('DocumentViewer component', () => { }); it('handles the open menu button', async () => { - render( - - - , - ); + renderWithProviders(); const openMenuButton = await screen.findByTestId('menuButton'); @@ -165,11 +160,7 @@ describe('DocumentViewer component', () => { }); it('handles the close menu button', async () => { - render( - - - , - ); + renderWithProviders(); // defaults to closed so we need to open it first. const openMenuButton = await screen.findByTestId('menuButton'); @@ -185,12 +176,8 @@ describe('DocumentViewer component', () => { }); it('shows error if file type is unsupported', async () => { - render( - - - , + renderWithProviders( + , ); expect(screen.getByText('id: undefined')).toBeInTheDocument(); @@ -200,38 +187,22 @@ describe('DocumentViewer component', () => { const errorMessageText = 'If your document does not display, please refresh your browser.'; const downloadLinkText = 'Download file'; it('no error message normally', async () => { - render( - - - , - ); + renderWithProviders(); expect(screen.queryByText(errorMessageText)).toBeNull(); }); it('download link normally', async () => { - render( - - - , - ); + renderWithProviders(); expect(screen.getByText(downloadLinkText)).toBeVisible(); }); it('show message on content error', async () => { - render( - - - , - ); + renderWithProviders(); expect(screen.getByText(errorMessageText)).toBeVisible(); }); it('download link on content error', async () => { - render( - - - , - ); + renderWithProviders(); expect(screen.getByText(downloadLinkText)).toBeVisible(); }); }); @@ -247,16 +218,14 @@ describe('DocumentViewer component', () => { data: null, }; - render( - - - , + renderWithProviders( + , ); bulkDownloadPaymentRequest.mockImplementation(() => Promise.resolve(mockResponse)); @@ -269,3 +238,83 @@ describe('DocumentViewer component', () => { }); }); }); + +// Mock the EventSource +class MockEventSource { + constructor(url) { + this.url = url; + this.onmessage = null; + } + + close() { + this.isClosed = true; + } +} +global.EventSource = MockEventSource; +// Helper function for finding the file status text +const findByTextContent = (text) => { + return screen.getByText((content, node) => { + const hasText = (element) => element.textContent.includes(text); + const nodeHasText = hasText(node); + const childrenDontHaveText = Array.from(node.children).every((child) => !hasText(child)); + return nodeHasText && childrenDontHaveText; + }); +}; + +describe('Test DocumentViewer File Upload Statuses', () => { + let eventSource; + const renderDocumentViewer = (props) => { + return renderWithProviders(); + }; + + beforeEach(() => { + eventSource = new MockEventSource(''); + jest.spyOn(global, 'EventSource').mockImplementation(() => eventSource); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('displays Uploading status', () => { + renderDocumentViewer({ files: mockFiles, isFileUploading: true }); + expect(findByTextContent(UPLOAD_DOC_STATUS_DISPLAY_MESSAGE.UPLOADING)).toBeInTheDocument(); + }); + + it('displays Scanning status', async () => { + renderDocumentViewer({ files: mockFiles }); + await act(async () => { + eventSource.onmessage({ data: UPLOAD_SCAN_STATUS.PROCESSING }); + }); + await waitFor(() => { + expect(findByTextContent(UPLOAD_DOC_STATUS_DISPLAY_MESSAGE.SCANNING)).toBeInTheDocument(); + }); + }); + + it('displays Establishing document for viewing status', async () => { + renderDocumentViewer({ files: mockFiles }); + await act(async () => { + eventSource.onmessage({ data: UPLOAD_SCAN_STATUS.CLEAN }); + }); + await waitFor(() => { + expect( + findByTextContent(UPLOAD_DOC_STATUS_DISPLAY_MESSAGE.ESTABLISHING_DOCUMENT_FOR_VIEWING), + ).toBeInTheDocument(); + }); + }); + + it('displays infected file message', async () => { + renderDocumentViewer({ files: mockFiles }); + await act(async () => { + eventSource.onmessage({ data: UPLOAD_SCAN_STATUS.INFECTED }); + }); + await waitFor(() => { + expect(findByTextContent(UPLOAD_DOC_STATUS_DISPLAY_MESSAGE.INFECTED_FILE_MESSAGE)).toBeInTheDocument(); + }); + }); + + it('displays File Not Found message when no file is selected', () => { + renderDocumentViewer({ files: [] }); + expect(findByTextContent(UPLOAD_DOC_STATUS_DISPLAY_MESSAGE.FILE_NOT_FOUND)).toBeInTheDocument(); + }); +}); diff --git a/src/components/DocumentViewerFileManager/DocumentViewerFileManager.jsx b/src/components/DocumentViewerFileManager/DocumentViewerFileManager.jsx index 7e765b93882..dd4789d8413 100644 --- a/src/components/DocumentViewerFileManager/DocumentViewerFileManager.jsx +++ b/src/components/DocumentViewerFileManager/DocumentViewerFileManager.jsx @@ -29,6 +29,7 @@ const DocumentViewerFileManager = ({ documentType, updateAmendedDocument, fileUploadRequired, + onAddFile, }) => { const queryClient = useQueryClient(); const filePondEl = useRef(); @@ -246,6 +247,7 @@ const DocumentViewerFileManager = ({ ref={filePondEl} createUpload={handleUpload} onChange={handleChange} + onAddFile={onAddFile} labelIdle={'Drag files here or click to upload'} /> PDF, JPG, or PNG only. Maximum file size 25MB. Each page must be clear and legible diff --git a/src/components/Office/AddOrdersForm/AddOrdersForm.jsx b/src/components/Office/AddOrdersForm/AddOrdersForm.jsx index a2c6be4ff4d..a64245d5dae 100644 --- a/src/components/Office/AddOrdersForm/AddOrdersForm.jsx +++ b/src/components/Office/AddOrdersForm/AddOrdersForm.jsx @@ -18,9 +18,9 @@ import { ORDERS_PAY_GRADE_OPTIONS, ORDERS_TYPE } from 'constants/orders'; import { dropdownInputOptions } from 'utils/formatters'; import WizardNavigation from 'components/Customer/WizardNavigation/WizardNavigation'; import Callout from 'components/Callout'; +import ConnectedFlashMessage from 'containers/FlashMessage/FlashMessage'; import MaskedTextField from 'components/form/fields/MaskedTextField/MaskedTextField'; import formStyles from 'styles/form.module.scss'; -import ConnectedFlashMessage from 'containers/FlashMessage/FlashMessage'; import { showCounselingOffices } from 'services/ghcApi'; let originMeta; diff --git a/src/components/Office/AddOrdersForm/AddOrdersForm.test.jsx b/src/components/Office/AddOrdersForm/AddOrdersForm.test.jsx index 9110d400626..3fdc95c7972 100644 --- a/src/components/Office/AddOrdersForm/AddOrdersForm.test.jsx +++ b/src/components/Office/AddOrdersForm/AddOrdersForm.test.jsx @@ -228,7 +228,8 @@ describe('CreateMoveCustomerInfo Component', () => { describe('AddOrdersForm - OCONUS and Accompanied Tour Test', () => { it('submits the form with OCONUS values and accompanied tour selection', async () => { - isBooleanFlagEnabled.mockImplementation(() => Promise.resolve(true)); + isBooleanFlagEnabled.mockResolvedValue(true); + render( @@ -241,28 +242,21 @@ describe('AddOrdersForm - OCONUS and Accompanied Tour Test', () => { await userEvent.click(screen.getByLabelText('No')); await userEvent.selectOptions(screen.getByLabelText(/Pay grade/), ['E_5']); - // Test Current Duty Location Search Box interaction - await userEvent.type(screen.getByLabelText(/Current duty location/), 'AFB', { delay: 100 }); - const selectedOptionCurrent = await screen.findByText(/Elmendorf/); - await userEvent.click(selectedOptionCurrent); + await userEvent.type(screen.getByLabelText(/Current duty location/), 'AFB'); + await userEvent.click(await screen.findByText(/Elmendorf/)); const counselingOfficeLabel = await screen.queryByText(/Counseling office/); expect(counselingOfficeLabel).toBeFalsy(); - // Test New Duty Location Search Box interaction - await userEvent.type(screen.getByLabelText(/New duty location/), 'AFB', { delay: 100 }); - const selectedOptionNew = await screen.findByText(/Luke/); - await userEvent.click(selectedOptionNew); + await userEvent.type(screen.getByLabelText(/New duty location/), 'AFB'); + await userEvent.click(await screen.findByText(/Luke/)); await userEvent.click(screen.getByTestId('hasDependentsYes')); - - // should now see the OCONUS inputs await userEvent.click(screen.getByTestId('isAnAccompaniedTourYes')); await userEvent.type(screen.getByTestId('dependentsUnderTwelve'), '2'); await userEvent.type(screen.getByTestId('dependentsTwelveAndOver'), '1'); const nextBtn = screen.getByRole('button', { name: 'Next' }); - expect(nextBtn).not.toBeDisabled(); await userEvent.click(nextBtn); await waitFor(() => { @@ -439,8 +433,8 @@ describe('AddOrdersForm - With Counseling Office', () => { ); await userEvent.selectOptions(await screen.findByLabelText(/Orders type/), 'PERMANENT_CHANGE_OF_STATION'); - await userEvent.type(screen.getByLabelText(/Orders date/), '08 Nov 2020'); - await userEvent.type(screen.getByLabelText(/Report by date/), '26 Nov 2020'); + await userEvent.paste(screen.getByLabelText(/Orders date/), '08 Nov 2020'); + await userEvent.paste(screen.getByLabelText(/Report by date/), '26 Nov 2020'); await userEvent.click(screen.getByLabelText('No')); await userEvent.selectOptions(screen.getByLabelText(/Pay grade/), ['E_5']); diff --git a/src/components/Office/AllowancesDetailForm/AllowancesDetailForm.jsx b/src/components/Office/AllowancesDetailForm/AllowancesDetailForm.jsx index 7b38d00bb68..9f15de8621a 100644 --- a/src/components/Office/AllowancesDetailForm/AllowancesDetailForm.jsx +++ b/src/components/Office/AllowancesDetailForm/AllowancesDetailForm.jsx @@ -21,7 +21,7 @@ const AllowancesDetailForm = ({ header, entitlements, branchOptions, formIsDisab entitlements?.dependentsTwelveAndOver || entitlements?.dependentsUnderTwelve ); - const { setFieldValue } = useFormikContext(); + const { values, setFieldValue } = useFormikContext(); const [isAdminWeightLocationChecked, setIsAdminWeightLocationChecked] = useState(entitlements?.weightRestriction > 0); useEffect(() => { // Functional component version of "componentDidMount" @@ -37,20 +37,20 @@ const AllowancesDetailForm = ({ header, entitlements, branchOptions, formIsDisab useEffect(() => { if (!isAdminWeightLocationChecked) { - // Find the weight restriction input and reset its value to 0 - const weightRestrictionInput = document.getElementById('weightRestrictionId'); - if (weightRestrictionInput) { - weightRestrictionInput.value = ''; - } + setFieldValue('weightRestriction', `${values.weightRestriction}`); } - }, [isAdminWeightLocationChecked]); + }, [setFieldValue, values.weightRestriction, isAdminWeightLocationChecked]); const handleAdminWeightLocationChange = (e) => { const isChecked = e.target.checked; setIsAdminWeightLocationChecked(isChecked); if (!isChecked) { - setFieldValue('weightRestriction', ''); + setFieldValue('weightRestriction', `${values.weightRestriction}`); + } else if (isChecked && values.weightRestriction) { + setFieldValue('weightRestriction', `${values.weightRestriction}`); + } else { + setFieldValue('weightRestriction', null); } }; @@ -205,26 +205,16 @@ const AllowancesDetailForm = ({ header, entitlements, branchOptions, formIsDisab )} -
- -
); }; diff --git a/src/components/Office/AllowancesDetailForm/AllowancesDetailForm.test.jsx b/src/components/Office/AllowancesDetailForm/AllowancesDetailForm.test.jsx index fcf825bd404..3b0e5dbe29e 100644 --- a/src/components/Office/AllowancesDetailForm/AllowancesDetailForm.test.jsx +++ b/src/components/Office/AllowancesDetailForm/AllowancesDetailForm.test.jsx @@ -12,6 +12,7 @@ const initialValues = { proGearWeightSpouse: '500', requiredMedicalEquipmentWeight: '1000', organizationalClothingAndIndividualEquipment: true, + weightRestriction: '500', }; const initialValuesOconusAdditions = { @@ -81,6 +82,7 @@ const entitlements = { storageInTransit: 90, totalWeight: 11000, totalDependents: 2, + weightRestriction: 500, }; const entitlementOconusAdditions = { @@ -177,13 +179,28 @@ describe('AllowancesDetailForm additional tests', () => { , ); + const adminWeightCheckbox = await screen.findByTestId('adminWeightLocation'); + expect(adminWeightCheckbox).toBeInTheDocument(); + expect(screen.getByLabelText('Admin restricted weight location')).toBeChecked(); + + const weightRestrictionInput = screen.getByTestId('weightRestrictionInput'); + expect(weightRestrictionInput).toBeInTheDocument(); + expect(weightRestrictionInput).toHaveValue('500'); + }); + + it('does not render the admin weight location section when the weightRestriction entitlement is null', async () => { + render( + + + , + ); + const adminWeightCheckbox = await screen.findByTestId('adminWeightLocation'); expect(adminWeightCheckbox).toBeInTheDocument(); expect(screen.queryByTestId('weightRestrictionInput')).not.toBeInTheDocument(); - await act(async () => { - adminWeightCheckbox.click(); - }); - expect(screen.getByTestId('weightRestrictionInput')).toBeInTheDocument(); }); it('displays the total weight allowance correctly', async () => { diff --git a/src/components/Office/DefinitionLists/AllowancesList.jsx b/src/components/Office/DefinitionLists/AllowancesList.jsx index 7bdd17862ae..a61b2e45882 100644 --- a/src/components/Office/DefinitionLists/AllowancesList.jsx +++ b/src/components/Office/DefinitionLists/AllowancesList.jsx @@ -41,10 +41,6 @@ const AllowancesList = ({ info, showVisualCues }) => {
Storage in transit (SIT)
{info.storageInTransit} days
-
-
Dependents
-
{info.dependents ? 'Authorized' : 'Unauthorized'}
-
{/* Begin OCONUS fields */} {/* As these fields are grouped together and only apply to OCONUS orders They will all be NULL for CONUS orders. If one of these fields are present, diff --git a/src/components/Office/DefinitionLists/AllowancesList.stories.jsx b/src/components/Office/DefinitionLists/AllowancesList.stories.jsx index 44e3eda03e8..289f0eb2b77 100644 --- a/src/components/Office/DefinitionLists/AllowancesList.stories.jsx +++ b/src/components/Office/DefinitionLists/AllowancesList.stories.jsx @@ -21,7 +21,6 @@ const info = { progear: 2000, spouseProgear: 500, storageInTransit: 90, - dependents: true, requiredMedicalEquipmentWeight: 1000, organizationalClothingAndIndividualEquipment: true, ubAllowance: 400, diff --git a/src/components/Office/DefinitionLists/AllowancesList.test.jsx b/src/components/Office/DefinitionLists/AllowancesList.test.jsx index 9eed73f1d62..073665f6d70 100644 --- a/src/components/Office/DefinitionLists/AllowancesList.test.jsx +++ b/src/components/Office/DefinitionLists/AllowancesList.test.jsx @@ -107,17 +107,6 @@ describe('AllowancesList', () => { expect(screen.getByText('90 days')).toBeInTheDocument(); }); - it('renders authorized dependents', () => { - render(); - expect(screen.getByTestId('dependents').textContent).toEqual('Authorized'); - }); - - it('renders unauthorized dependents', () => { - const withUnauthorizedDependents = { ...info, dependents: false }; - render(); - expect(screen.getByTestId('dependents').textContent).toEqual('Unauthorized'); - }); - it('renders formatted pro-gear', () => { render(); expect(screen.getByText('2,000 lbs')).toBeInTheDocument(); diff --git a/src/components/Office/DefinitionLists/OrdersList.jsx b/src/components/Office/DefinitionLists/OrdersList.jsx index 46ec027d40e..99486430346 100644 --- a/src/components/Office/DefinitionLists/OrdersList.jsx +++ b/src/components/Office/DefinitionLists/OrdersList.jsx @@ -15,7 +15,7 @@ import { ordersTypeDetailReadable, } from 'utils/formatters'; -const OrdersList = ({ ordersInfo, showMissingWarnings }) => { +const OrdersList = ({ ordersInfo, moveInfo, showMissingWarnings }) => { const { ordersType } = ordersInfo; const isRetiree = ordersType === 'RETIREMENT'; const isSeparatee = ordersType === 'SEPARATION'; @@ -57,6 +57,12 @@ const OrdersList = ({ ordersInfo, showMissingWarnings }) => {
Current duty location
{ordersInfo.currentDutyLocation?.name}
+
+
Counseling office
+
+ {moveInfo?.counselingOffice?.name ? moveInfo.counselingOffice.name : '—'} +
+
{ {isRetiree || isSeparatee ? 'HOR, HOS, or PLEAD' : 'New duty location'}
- {ordersInfo.newDutyLocation?.name ? ordersInfo.newDutyLocation?.name : '-'} + {ordersInfo.newDutyLocation?.name ? ordersInfo.newDutyLocation?.name : '—'}
{
Orders type detail
{ordersTypeDetailReadable(ordersInfo.ordersTypeDetail, missingText)}
+
+
Dependents
+
{ordersInfo.dependents ? 'Authorized' : 'Unauthorized'}
+
( ordersNumber: text('ordersInfo.ordersNumber', '999999999'), ordersType: text('ordersInfo.ordersType', ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION), ordersTypeDetail: text('ordersInfo.ordersTypeDetail', 'HHG_PERMITTED'), + dependents: true, ordersDocuments: array('ordersInfo.ordersDocuments', [ { 'c0a22a98-a806-47a2-ab54-2dac938667b3': { @@ -43,6 +44,9 @@ export const Basic = () => ( NTStac: text('ordersInfo.NTStac', '9999'), payGrade: text('ordersInfo.payGrade', 'E_5'), }} + moveInfo={{ + name: 'PPPO Los Angeles SFB - USAF', + }} />
); @@ -60,6 +64,7 @@ export const AsServiceCounselor = () => ( ordersNumber: '', ordersType: '', ordersTypeDetail: '', + dependents: false, ordersDocuments: array('ordersInfo.ordersDocuments', [ { 'c0a22a98-a806-47a2-ab54-2dac938667b3': { @@ -81,6 +86,9 @@ export const AsServiceCounselor = () => ( NTStac: '', payGrade: text('ordersInfo.payGrade', 'E_5'), }} + moveInfo={{ + name: 'PPPO Los Angeles SFB - USAF', + }} /> ); @@ -98,6 +106,7 @@ export const AsServiceCounselorProcessingRetirement = () => ( ordersNumber: '', ordersType: 'RETIREMENT', ordersTypeDetail: '', + dependents: false, ordersDocuments: null, tacMDC: '', sacSDN: '', @@ -105,6 +114,9 @@ export const AsServiceCounselorProcessingRetirement = () => ( NTStac: '', payGrade: text('ordersInfo.payGrade', 'E_5'), }} + moveInfo={{ + name: 'PPPO Los Angeles SFB - USAF', + }} /> ); @@ -122,6 +134,7 @@ export const AsServiceCounselorProcessingSeparation = () => ( ordersNumber: '', ordersType: 'SEPARATION', ordersTypeDetail: '', + dependents: false, ordersDocuments: null, tacMDC: '', sacSDN: '', @@ -129,6 +142,9 @@ export const AsServiceCounselorProcessingSeparation = () => ( NTStac: '', payGrade: text('ordersInfo.payGrade', 'E_5'), }} + moveInfo={{ + name: 'PPPO Los Angeles SFB - USAF', + }} /> ); @@ -145,6 +161,7 @@ export const AsTOO = () => ( ordersNumber: '', ordersType: '', ordersTypeDetail: '', + dependents: false, ordersDocuments: array('ordersInfo.ordersDocuments', [ { 'c0a22a98-a806-47a2-ab54-2dac938667b3': { @@ -166,6 +183,9 @@ export const AsTOO = () => ( NTStac: '', payGrade: text('ordersInfo.payGrade', 'E_5'), }} + moveInfo={{ + name: 'PPPO Los Angeles SFB - USAF', + }} /> ); @@ -182,6 +202,7 @@ export const AsTOOProcessingRetirement = () => ( ordersNumber: '', ordersType: 'RETIREMENT', ordersTypeDetail: '', + dependents: false, ordersDocuments: null, tacMDC: '', sacSDN: '', @@ -205,6 +226,7 @@ export const AsTOOProcessingSeparation = () => ( ordersNumber: '', ordersType: 'SEPARATION', ordersTypeDetail: '', + dependents: false, ordersDocuments: null, tacMDC: '', sacSDN: '', @@ -212,6 +234,9 @@ export const AsTOOProcessingSeparation = () => ( NTStac: '', payGrade: text('ordersInfo.payGrade', 'E_5'), }} + moveInfo={{ + name: 'PPPO Los Angeles SFB - USAF', + }} /> ); diff --git a/src/components/Office/DefinitionLists/OrdersList.test.jsx b/src/components/Office/DefinitionLists/OrdersList.test.jsx index 586c0d1bfab..b280d1c7630 100644 --- a/src/components/Office/DefinitionLists/OrdersList.test.jsx +++ b/src/components/Office/DefinitionLists/OrdersList.test.jsx @@ -12,6 +12,7 @@ const ordersInfo = { ordersNumber: '999999999', ordersType: 'PERMANENT_CHANGE_OF_STATION', ordersTypeDetail: 'HHG_PERMITTED', + dependents: true, ordersDocuments: [ { 'c0a22a98-a806-47a2-ab54-2dac938667b3': { @@ -32,9 +33,16 @@ const ordersInfo = { payGrade: 'E_7', }; +const moveInfo = { + counselingOffice: { + name: 'PPPO Los Angeles SFB - USAF', + }, +}; + // what ordersInfo from above should be rendered as const expectedRenderedOrdersInfo = { currentDutyLocation: 'JBSA Lackland', + counselingOffice: 'PPPO Los Angeles SFB - USAF', newDutyLocation: 'JB Lewis-McChord', issuedDate: '08 Mar 2020', reportByDate: '01 Apr 2020', @@ -65,12 +73,23 @@ const ordersInfoMissing = { describe('OrdersList', () => { it('renders formatted orders info', () => { - render(); + render(); Object.keys(expectedRenderedOrdersInfo).forEach((key) => { expect(screen.getByText(expectedRenderedOrdersInfo[key])).toBeInTheDocument(); }); }); + it('renders authorized dependents', () => { + render(); + expect(screen.getByTestId('dependents').textContent).toEqual('Authorized'); + }); + + it('renders unauthorized dependents', () => { + const withUnauthorizedDependents = { ...ordersInfo, dependents: false }; + render(); + expect(screen.getByTestId('dependents').textContent).toEqual('Unauthorized'); + }); + it('renders missing orders info as warning if showMissingWarnings is included', () => { render(); expect(screen.getByTestId('departmentIndicator').textContent).toEqual('Missing'); diff --git a/src/components/Office/DefinitionLists/ShipmentInfoListSelector.jsx b/src/components/Office/DefinitionLists/ShipmentInfoListSelector.jsx index 6830db6b735..b50fbc53e2f 100644 --- a/src/components/Office/DefinitionLists/ShipmentInfoListSelector.jsx +++ b/src/components/Office/DefinitionLists/ShipmentInfoListSelector.jsx @@ -140,6 +140,7 @@ ShipmentInfoListSelector.propTypes = { SHIPMENT_TYPES.BOAT_HAUL_AWAY, SHIPMENT_TYPES.BOAT_TOW_AWAY, SHIPMENT_OPTIONS.MOBILE_HOME, + SHIPMENT_OPTIONS.UNACCOMPANIED_BAGGAGE, ]), isForEvaluationReport: PropTypes.bool, destinationDutyLocationPostalCode: PropTypes.string, diff --git a/src/components/Office/OrdersDetailForm/OrdersDetailForm.jsx b/src/components/Office/OrdersDetailForm/OrdersDetailForm.jsx index 26028b4ea69..dc05e2008e5 100644 --- a/src/components/Office/OrdersDetailForm/OrdersDetailForm.jsx +++ b/src/components/Office/OrdersDetailForm/OrdersDetailForm.jsx @@ -106,7 +106,15 @@ const OrdersDetailForm = ({ isDisabled={formIsDisabled} /> )} - +
+ +
{showHHGTac && showHHGSac &&

HHG accounting codes

} {showHHGTac && ( { // correct labels are visible expect(await screen.findByLabelText('Orders type')).toBeDisabled(); }); + + it('renders dependents authorized checkbox field', async () => { + renderOrdersDetailForm(); + expect(await screen.findByTestId('dependentsAuthorizedInput')).toBeInTheDocument(); + }); }); diff --git a/src/components/Office/PPM/PPMHeaderSummary/EditPPMHeaderSummaryModal.test.jsx b/src/components/Office/PPM/PPMHeaderSummary/EditPPMHeaderSummaryModal.test.jsx index caf85ad3aba..8a3fa5d0e09 100644 --- a/src/components/Office/PPM/PPMHeaderSummary/EditPPMHeaderSummaryModal.test.jsx +++ b/src/components/Office/PPM/PPMHeaderSummary/EditPPMHeaderSummaryModal.test.jsx @@ -116,7 +116,7 @@ describe('EditPPMHeaderSummaryModal', () => { expect(screen.getByLabelText('Close')).toBeInstanceOf(HTMLButtonElement); }); - it('renders actual expense reimbursement', async () => { + it('renders allowable weight', async () => { await act(async () => { render( { sectionInfo={sectionInfo} onClose={onClose} onSubmit={onSubmit} - editItemName="isActualExpenseReimbursement" + editItemName="allowableWeight" />, ); }); expect(await screen.findByRole('heading', { level: 3, name: 'Edit Shipment Info' })).toBeInTheDocument(); - expect(screen.getByText('Is this PPM an Actual Expense Reimbursement?')).toBeInTheDocument(); + expect(screen.getByText('Allowable Weight')).toBeInTheDocument(); + expect(screen.getByTestId('editAllowableWeightInput')).toHaveValue('1,750'); expect(screen.getByRole('button', { name: 'Save' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument(); expect(screen.getByLabelText('Close')).toBeInstanceOf(HTMLButtonElement); }); - it('renders allowable weight', async () => { + it('renders actual expense reimbursement', async () => { await act(async () => { render( { sectionInfo={sectionInfo} onClose={onClose} onSubmit={onSubmit} - editItemName="allowableWeight" + editItemName="isActualExpenseReimbursement" />, ); }); expect(await screen.findByRole('heading', { level: 3, name: 'Edit Shipment Info' })).toBeInTheDocument(); - expect(screen.getByText('Allowable Weight')).toBeInTheDocument(); - expect(screen.getByTestId('editAllowableWeightInput')).toHaveValue('1,750'); + expect(screen.getByText('Is this PPM an Actual Expense Reimbursement?')).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Save' })).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument(); expect(screen.getByLabelText('Close')).toBeInstanceOf(HTMLButtonElement); diff --git a/src/components/Office/PPM/PPMHeaderSummary/HeaderSection.jsx b/src/components/Office/PPM/PPMHeaderSummary/HeaderSection.jsx index a9201b3ec3f..b7be816baeb 100644 --- a/src/components/Office/PPM/PPMHeaderSummary/HeaderSection.jsx +++ b/src/components/Office/PPM/PPMHeaderSummary/HeaderSection.jsx @@ -388,8 +388,8 @@ export default function HeaderSection({ updatedItemName, setUpdatedItemName, readOnly, - grade, expanded, + grade, }) { const requestDetailsButtonTestId = `${sectionInfo.type}-showRequestDetailsButton`; const { shipmentId, moveCode } = useParams(); diff --git a/src/components/Office/PPM/PPMHeaderSummary/PPMHeaderSummary.jsx b/src/components/Office/PPM/PPMHeaderSummary/PPMHeaderSummary.jsx index 23d84460bf7..a21328b3d24 100644 --- a/src/components/Office/PPM/PPMHeaderSummary/PPMHeaderSummary.jsx +++ b/src/components/Office/PPM/PPMHeaderSummary/PPMHeaderSummary.jsx @@ -81,8 +81,8 @@ export default function PPMHeaderSummary({ ppmShipmentInfo, order, ppmNumber, sh miles: ppmShipmentInfo.miles, estimatedWeight: ppmShipmentInfo.estimatedWeight, actualWeight: ppmShipmentInfo.actualWeight, - isActualExpenseReimbursement: ppmShipmentInfo.isActualExpenseReimbursement, allowableWeight: ppmShipmentInfo.allowableWeight, + isActualExpenseReimbursement: ppmShipmentInfo.isActualExpenseReimbursement, }; return ( @@ -103,8 +103,8 @@ export default function PPMHeaderSummary({ ppmShipmentInfo, order, ppmNumber, sh updatedItemName={updatedItemName} setUpdatedItemName={setUpdatedItemName} readOnly={readOnly} - grade={order?.grade} expanded + grade={order?.grade} /> {showAllFields && ( diff --git a/src/components/Office/PPM/ReviewDocumentsSidePanel/ReviewDocumentsSidePanel.jsx b/src/components/Office/PPM/ReviewDocumentsSidePanel/ReviewDocumentsSidePanel.jsx index dbf7583d636..db19e898ced 100644 --- a/src/components/Office/PPM/ReviewDocumentsSidePanel/ReviewDocumentsSidePanel.jsx +++ b/src/components/Office/PPM/ReviewDocumentsSidePanel/ReviewDocumentsSidePanel.jsx @@ -112,7 +112,7 @@ export default function ReviewDocumentsSidePanel({ return (
-
+
{ - return mtoServiceItems?.find((mtoServiceItem) => mtoServiceItem.reServiceCode === mtoServiceItemCode); + const findAdditionalServiceItemData = (mtoServiceItemID) => { + return mtoServiceItems?.find((mtoServiceItem) => mtoServiceItem.id === mtoServiceItemID); }; return ( @@ -167,7 +167,7 @@ const PaymentRequestDetails = ({ return ( { expect(screen.getByTestId('counselingFee')).toBeInTheDocument(); }); - it('renders the "Add service items to move" section with only counseling when all shipments are PPM', () => { + it('does not render the "Add service items to move" section or Counseling option when all shipments are PPM', () => { const testPropsServiceItemsEmpty = { mtoServiceItems: serviceItemsEmpty, mtoShipments: ppmOnlyShipments, @@ -912,10 +912,10 @@ describe('RequestedShipments', () => { }; renderComponent(testPropsServiceItemsEmpty); - expect(screen.getByText('Add service items to this move')).toBeInTheDocument(); + expect(screen.queryByText('Add service items to this move')).not.toBeInTheDocument(); expect(screen.getByText('Approve selected')).toBeInTheDocument(); expect(screen.queryByTestId('shipmentManagementFee')).not.toBeInTheDocument(); - expect(screen.getByTestId('counselingFee')).toBeInTheDocument(); + expect(screen.queryByTestId('counselingFee')).not.toBeInTheDocument(); }); }); }); diff --git a/src/components/Office/RequestedShipments/SubmittedRequestedShipments.jsx b/src/components/Office/RequestedShipments/SubmittedRequestedShipments.jsx index a296057ec95..feaae1e694d 100644 --- a/src/components/Office/RequestedShipments/SubmittedRequestedShipments.jsx +++ b/src/components/Office/RequestedShipments/SubmittedRequestedShipments.jsx @@ -27,8 +27,8 @@ import { fieldValidationShape } from 'utils/displayFlags'; import ButtonDropdown from 'components/ButtonDropdown/ButtonDropdown'; import { SHIPMENT_OPTIONS_URL, FEATURE_FLAG_KEYS } from 'shared/constants'; import { setFlashMessage as setFlashMessageAction } from 'store/flash/actions'; -import { isBooleanFlagEnabled } from 'utils/featureFlags'; import { updateMTOShipment } from 'services/ghcApi'; +import { isBooleanFlagEnabled } from 'utils/featureFlags'; // nts defaults show preferred pickup date and pickup address, flagged items when collapsed // ntsr defaults shows preferred delivery date, storage facility address, delivery address, flagged items when collapsed @@ -320,8 +320,9 @@ const SubmittedRequestedShipments = ({ const dutyLocationPostal = { postalCode: ordersInfo.newDutyLocation?.address?.postalCode }; - // Hide counseling line item if prime counseling is already in the service items or if service counseling has been applied - const hideCounselingCheckbox = hasCounseling(mtoServiceItems) || moveTaskOrder?.serviceCounselingCompletedAt; + // Hide counseling line item if prime counseling is already in the service items, if service counseling has been applied, or if full PPM move + const hideCounselingCheckbox = + hasCounseling(mtoServiceItems) || moveTaskOrder?.serviceCounselingCompletedAt || isPPMOnly(mtoShipments); // Hide move management line item if it is already in the service items or for PPM only moves const hideMoveManagementCheckbox = hasMoveManagement(mtoServiceItems) || isPPMOnly(mtoShipments); diff --git a/src/components/Office/RequestedShipments/SubmittedRequestedShipments.test.jsx b/src/components/Office/RequestedShipments/SubmittedRequestedShipments.test.jsx new file mode 100644 index 00000000000..1388bdf84e4 --- /dev/null +++ b/src/components/Office/RequestedShipments/SubmittedRequestedShipments.test.jsx @@ -0,0 +1,194 @@ +import React from 'react'; +import { render, screen, within } from '@testing-library/react'; + +import { + shipments, + ordersInfo, + allowancesInfo, + customerInfo, + serviceItemsEmpty, + ppmOnlyShipments, + closeoutOffice, +} from './RequestedShipmentsTestData'; +import SubmittedRequestedShipments from './SubmittedRequestedShipments'; + +import { MockProviders } from 'testUtils'; +import { permissionTypes } from 'constants/permissions'; + +const mockNavigate = jest.fn(); +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useNavigate: () => mockNavigate, +})); + +const moveTaskOrderAvailableToPrimeAt = { + eTag: 'MjAyMC0wNi0yNlQyMDoyMjo0MS43Mjc4NTNa', + id: '6e8c5ca4-774c-4170-934a-59d22259e480', + availableToPrimeAt: '2020-06-10T15:58:02.431995Z', +}; + +const moveTaskOrderServicesCounselingCompleted = { + eTag: 'MjAyMC0wNi0yNlQyMDoyMjo0MS43Mjc4NTNa', + id: '6e8c5ca4-774c-4170-934a-59d22259e480', + serviceCounselingCompletedAt: '2020-10-02T19:20:08.481139Z', +}; + +const approveMTO = jest.fn().mockResolvedValue({ response: { status: 200 } }); + +const submittedRequestedShipmentsComponent = ( + + + +); + +const submittedRequestedShipmentsComponentWithPermission = ( + + + +); + +const submittedRequestedShipmentsComponentAvailableToPrimeAt = ( + + + +); + +const submittedRequestedShipmentsComponentServicesCounselingCompleted = ( + + + +); + +const submittedRequestedShipmentsCanCreateNewShipment = ( + + + +); + +describe('RequestedShipments', () => { + describe('Prime-handled shipments', () => { + it('renders the container successfully without services counseling completed', () => { + render(submittedRequestedShipmentsComponent); + expect(screen.getByTestId('requested-shipments')).toBeInTheDocument(); + expect(screen.queryByTestId('services-counseling-completed-text')).not.toBeInTheDocument(); + }); + + it('renders the container successfully with services counseling completed', () => { + render(submittedRequestedShipmentsComponentServicesCounselingCompleted); + expect(screen.getByTestId('requested-shipments')).toBeInTheDocument(); + expect(screen.queryByTestId('services-counseling-completed-text')).toBeInTheDocument(); + }); + + it('renders a shipment passed to it', () => { + render(submittedRequestedShipmentsComponent); + const withinContainer = within(screen.getByTestId('requested-shipments')); + expect(withinContainer.getAllByText('HHG').length).toEqual(2); + expect(withinContainer.getAllByText('NTS').length).toEqual(1); + }); + + it('renders the button', () => { + render(submittedRequestedShipmentsComponentWithPermission); + expect( + screen.getByRole('button', { + name: 'Approve selected', + }), + ).toBeInTheDocument(); + expect( + screen.getByRole('button', { + name: 'Approve selected', + }), + ).toBeDisabled(); + }); + + it('renders the button when it is available to the prime', () => { + render(submittedRequestedShipmentsComponentAvailableToPrimeAt); + expect(screen.getByTestId('shipmentApproveButton')).toBeInTheDocument(); + expect(screen.getByTestId('shipmentApproveButton')).toBeDisabled(); + }); + + it('renders the checkboxes', () => { + render(submittedRequestedShipmentsComponentWithPermission); + expect(screen.getAllByTestId('checkbox').length).toEqual(5); + }); + + it('renders Add a new shipment Button', async () => { + render(submittedRequestedShipmentsCanCreateNewShipment); + + expect(await screen.getByRole('combobox', { name: 'Add a new shipment' })).toBeInTheDocument(); + }); + }); + + describe('Conditional form display', () => { + const renderComponent = (props) => { + render( + + + , + ); + }; + const conditionalFormTestProps = { + ordersInfo, + allowancesInfo, + customerInfo, + approveMTO, + moveCode: 'TE5TC0DE', + }; + + it('does not render the "Add service items to move" section or Counseling option when all shipments are PPM', () => { + const testPropsServiceItemsEmpty = { + mtoServiceItems: serviceItemsEmpty, + mtoShipments: ppmOnlyShipments, + ...conditionalFormTestProps, + }; + renderComponent(testPropsServiceItemsEmpty); + + expect(screen.queryByText('Add service items to this move')).not.toBeInTheDocument(); + expect(screen.getByText('Approve selected')).toBeInTheDocument(); + expect(screen.queryByTestId('shipmentManagementFee')).not.toBeInTheDocument(); + expect(screen.queryByTestId('counselingFee')).not.toBeInTheDocument(); + }); + }); +}); diff --git a/src/components/Office/ReviewServiceItems/ReviewAccountingCodes.test.jsx b/src/components/Office/ReviewServiceItems/ReviewAccountingCodes.test.jsx index 248b310746f..f9ed97ab87a 100644 --- a/src/components/Office/ReviewServiceItems/ReviewAccountingCodes.test.jsx +++ b/src/components/Office/ReviewServiceItems/ReviewAccountingCodes.test.jsx @@ -50,6 +50,42 @@ describe('components/Office/ReviewServiceItems/ReviewAccountingCodes', () => { expect(screen.queryByText('HHG')).not.toBeInTheDocument(); }); + + it('should not display move management fee if move management service item is not requested', () => { + render( + , + ); + + expect(screen.queryByText('Move management fee')).not.toBeInTheDocument(); + expect(screen.getByText('Counseling fee')).toBeInTheDocument(); + expect(screen.getByText('$20.65')).toBeInTheDocument(); + }); + + it('should not display counseling fee if counseling service item is not requested', () => { + render( + , + ); + + expect(screen.queryByText('Counseling fee')).not.toBeInTheDocument(); + expect(screen.getByText('Move management fee')).toBeInTheDocument(); + expect(screen.getByText('$44.33')).toBeInTheDocument(); + }); }); describe('can display codes', () => { diff --git a/src/components/Office/ServiceItemCalculations/ServiceItemCalculations.jsx b/src/components/Office/ServiceItemCalculations/ServiceItemCalculations.jsx index fea91773c2e..9e4fd608148 100644 --- a/src/components/Office/ServiceItemCalculations/ServiceItemCalculations.jsx +++ b/src/components/Office/ServiceItemCalculations/ServiceItemCalculations.jsx @@ -83,7 +83,7 @@ const ServiceItemCalculations = ({ {calc.label} - {appendSign(index, calculations.length)} + {calc.value === null || calc.value === '' ? null : appendSign(index, calculations.length)} {calc.value}
diff --git a/src/components/Office/ServiceItemCalculations/ServiceItemCalculations.module.scss b/src/components/Office/ServiceItemCalculations/ServiceItemCalculations.module.scss index 48e25cd1efb..d7b5246dc2b 100644 --- a/src/components/Office/ServiceItemCalculations/ServiceItemCalculations.module.scss +++ b/src/components/Office/ServiceItemCalculations/ServiceItemCalculations.module.scss @@ -102,6 +102,7 @@ .descriptionTitle { @include u-font-weight('bold'); + @include u-margin-right(0.5); color: $base-darker; } diff --git a/src/components/Office/ServiceItemCalculations/helpers.js b/src/components/Office/ServiceItemCalculations/helpers.js index 8412e4192d7..cfc467941b7 100644 --- a/src/components/Office/ServiceItemCalculations/helpers.js +++ b/src/components/Office/ServiceItemCalculations/helpers.js @@ -1,4 +1,9 @@ -import { SERVICE_ITEM_CALCULATION_LABELS, SERVICE_ITEM_CODES, SERVICE_ITEM_PARAM_KEYS } from 'constants/serviceItems'; +import { + SERVICE_ITEM_CALCULATION_LABELS, + SERVICE_ITEM_CODES, + SERVICE_ITEM_PARAM_KEYS, + EXTERNAL_CRATE_MIN_CUBIC_FT, +} from 'constants/serviceItems'; import { LONGHAUL_MIN_DISTANCE } from 'constants/shipments'; import { formatDateWithUTC } from 'shared/dates'; import { @@ -28,6 +33,13 @@ const peak = (params) => { }`; }; +const getMarket = (params) => { + const marketValue = + getParamValue(SERVICE_ITEM_PARAM_KEYS.MarketOrigin, params) || + getParamValue(SERVICE_ITEM_PARAM_KEYS.MarketDest, params); + return ` ${marketValue?.toLowerCase() === 'o' ? 'OCONUS' : 'CONUS'}`; +}; + const serviceAreaOrigin = (params) => { return `${SERVICE_ITEM_CALCULATION_LABELS[SERVICE_ITEM_PARAM_KEYS.ServiceAreaOrigin]}: ${getParamValue( SERVICE_ITEM_PARAM_KEYS.ServiceAreaOrigin, @@ -368,6 +380,20 @@ const shuttleOriginPriceDomestic = (params) => { ); }; +const shuttleOriginPriceInternational = (params) => { + const value = getPriceRateOrFactor(params); + const label = SERVICE_ITEM_CALCULATION_LABELS.OriginPrice; + + const pickupDate = `${SERVICE_ITEM_CALCULATION_LABELS.PickupDate}: ${formatDateWithUTC( + getParamValue(SERVICE_ITEM_PARAM_KEYS.ReferenceDate, params), + 'DD MMM YYYY', + )}`; + + const market = getParamValue(SERVICE_ITEM_PARAM_KEYS.MarketDest, params) === 'O' ? 'Oconus' : 'Conus'; + + return calculation(value, label, formatDetail(pickupDate), formatDetail(market)); +}; + // There is no param representing the destination price as available in the re_domestic_service_area_prices table // A param to return the service schedule is also not being created const destinationPrice = (params, shipmentType) => { @@ -406,6 +432,20 @@ const shuttleDestinationPriceDomestic = (params) => { ); }; +const shuttleDestinationPriceInternational = (params) => { + const value = getPriceRateOrFactor(params); + const label = SERVICE_ITEM_CALCULATION_LABELS.DestinationPrice; + + const deliveryDate = `${SERVICE_ITEM_CALCULATION_LABELS.DeliveryDate}: ${formatDateWithUTC( + getParamValue(SERVICE_ITEM_PARAM_KEYS.ReferenceDate, params), + 'DD MMM YYYY', + )}`; + + const market = getParamValue(SERVICE_ITEM_PARAM_KEYS.MarketDest, params) === 'O' ? 'Oconus' : 'Conus'; + + return calculation(value, label, formatDetail(deliveryDate), formatDetail(market)); +}; + const priceEscalationFactor = (params) => { const value = getParamValue(SERVICE_ITEM_PARAM_KEYS.EscalationCompounded, params) ? getParamValue(SERVICE_ITEM_PARAM_KEYS.EscalationCompounded, params) @@ -647,18 +687,71 @@ const unCratingPrice = (params) => { ); }; +const cratingPriceIntl = (params) => { + const value = getParamValue(SERVICE_ITEM_PARAM_KEYS.PriceRateOrFactor, params); + const label = SERVICE_ITEM_CALCULATION_LABELS.CratingPrice; + + return calculation(value, label, formatDetail(cratingDate(params)), formatDetail(getMarket(params))); +}; + +const unCratingPriceIntl = (params) => { + const value = getParamValue(SERVICE_ITEM_PARAM_KEYS.PriceRateOrFactor, params); + const label = SERVICE_ITEM_CALCULATION_LABELS.UncratingPrice; + + return calculation(value, label, formatDetail(unCratingDate(params)), formatDetail(getMarket(params))); +}; + +const isExternalCrateMinSizeApplied = (params) => { + const cubicFeetBilled = getParamValue(SERVICE_ITEM_PARAM_KEYS.CubicFeetBilled, params); + const cubicFeetCrating = getParamValue(SERVICE_ITEM_PARAM_KEYS.CubicFeetCrating, params); + const externalCrate = + getParamValue(SERVICE_ITEM_PARAM_KEYS.ExternalCrate, params)?.toLowerCase() === 'true' + ? SERVICE_ITEM_CALCULATION_LABELS.ExternalCrate + : ''; + + return ( + cubicFeetCrating !== cubicFeetBilled && externalCrate && cubicFeetBilled?.toString() === EXTERNAL_CRATE_MIN_CUBIC_FT + ); +}; + const cratingSize = (params, mtoParams) => { - const value = getParamValue(SERVICE_ITEM_PARAM_KEYS.CubicFeetBilled, params); + const cubicFeetBilled = getParamValue(SERVICE_ITEM_PARAM_KEYS.CubicFeetBilled, params); const length = getParamValue(SERVICE_ITEM_PARAM_KEYS.DimensionLength, params); const height = getParamValue(SERVICE_ITEM_PARAM_KEYS.DimensionHeight, params); const width = getParamValue(SERVICE_ITEM_PARAM_KEYS.DimensionWidth, params); - const label = SERVICE_ITEM_CALCULATION_LABELS.CubicFeetBilled; + let label = SERVICE_ITEM_CALCULATION_LABELS.CubicFeetBilled; + let cubicFeetCratingInfo = ''; const description = `${SERVICE_ITEM_CALCULATION_LABELS.Description}: ${mtoParams.description}`; const formattedDimensions = `${SERVICE_ITEM_CALCULATION_LABELS.Dimensions}: ${length}x${width}x${height} in`; - return calculation(value, label, formatDetail(description), formatDetail(formattedDimensions)); + const externalCrate = + getParamValue(SERVICE_ITEM_PARAM_KEYS.ExternalCrate, params)?.toLowerCase() === 'true' + ? SERVICE_ITEM_CALCULATION_LABELS.ExternalCrate + : ''; + + // currently external intl crate gets 4 cu ft min applied to pricing + const isMinCrateSizeApplied = isExternalCrateMinSizeApplied(params); + + if (isMinCrateSizeApplied) { + label += ' - Minimum'; + + // show actual size if minimum was applied + cubicFeetCratingInfo = `${SERVICE_ITEM_CALCULATION_LABELS.CubicFeetCrating}: ${getParamValue( + SERVICE_ITEM_PARAM_KEYS.CubicFeetCrating, + params, + )} cu ft`; + } + + return calculation( + cubicFeetBilled, + label, + formatDetail(description), + formatDetail(formattedDimensions), + formatDetail(cubicFeetCratingInfo), + formatDetail(externalCrate), + ); }; const standaloneCrate = (params) => { @@ -680,11 +773,17 @@ const standaloneCrate = (params) => { const uncappedRequestTotal = (params) => { const uncappedTotal = getParamValue(SERVICE_ITEM_PARAM_KEYS.UncappedRequestTotal, params); const value = toDollarString(uncappedTotal); - const label = `${SERVICE_ITEM_CALCULATION_LABELS.UncappedRequestTotal}:`; + const label = `${SERVICE_ITEM_CALCULATION_LABELS.UncappedRequestTotal}`; return calculation(value, label); }; +const minSizeCrateApplied = () => { + const label = SERVICE_ITEM_CALCULATION_LABELS.MinSizeCrateApplied; + + return calculation('', label); +}; + const totalAmountRequested = (totalAmount) => { const value = toDollarString(formatCents(totalAmount)); const label = `${SERVICE_ITEM_CALCULATION_LABELS.Total}: `; @@ -851,6 +950,15 @@ export default function makeCalculations(itemCode, totalAmount, params, mtoParam totalAmountRequested(totalAmount), ]; break; + // International origin shuttle service + case SERVICE_ITEM_CODES.IOSHUT: + result = [ + shuttleBillableWeight(params), + shuttleOriginPriceInternational(params), + priceEscalationFactorWithoutContractYear(params), + totalAmountRequested(totalAmount), + ]; + break; // Domestic Destination Additional Days SIT case SERVICE_ITEM_CODES.DDASIT: result = [ @@ -879,6 +987,15 @@ export default function makeCalculations(itemCode, totalAmount, params, mtoParam totalAmountRequested(totalAmount), ]; break; + // International destination shuttle service + case SERVICE_ITEM_CODES.IDSHUT: + result = [ + shuttleBillableWeight(params), + shuttleDestinationPriceInternational(params), + priceEscalationFactorWithoutContractYear(params), + totalAmountRequested(totalAmount), + ]; + break; // Domestic crating case SERVICE_ITEM_CODES.DCRT: result = [ @@ -950,6 +1067,34 @@ export default function makeCalculations(itemCode, totalAmount, params, mtoParam totalAmountRequested(totalAmount), ]; break; + // International crating + case SERVICE_ITEM_CODES.ICRT: + result = [ + cratingSize(params, mtoParams), + cratingPriceIntl(params), + priceEscalationFactorWithoutContractYear(params), + totalAmountRequested(totalAmount), + ]; + if ( + SERVICE_ITEM_PARAM_KEYS.StandaloneCrate !== null && + getParamValue(SERVICE_ITEM_PARAM_KEYS.StandaloneCrate, params) === 'true' + ) { + result.splice(result.length - 1, 0, uncappedRequestTotal(params)); + result.splice(result.length - 1, 0, standaloneCrate(params)); + } + if (isExternalCrateMinSizeApplied(params)) { + result.splice(result.length - 1, 0, minSizeCrateApplied(params)); + } + break; + // International uncrating + case SERVICE_ITEM_CODES.IUCRT: + result = [ + cratingSize(params, mtoParams), + unCratingPriceIntl(params), + priceEscalationFactorWithoutContractYear(params), + totalAmountRequested(totalAmount), + ]; + break; default: break; } diff --git a/src/components/Office/ServiceItemCalculations/helpers.test.js b/src/components/Office/ServiceItemCalculations/helpers.test.js index 8ca38112f65..4ff4fb3e02b 100644 --- a/src/components/Office/ServiceItemCalculations/helpers.test.js +++ b/src/components/Office/ServiceItemCalculations/helpers.test.js @@ -11,12 +11,12 @@ function testData(code) { 'Crating size (cu ft)': '4.00', }; } - if (code === 'DCRT') { + if (code === 'DCRT' || code === 'ICRT') { result = { ...result, 'Crating price (per cu ft)': '1.71', }; - } else if (code === 'DUCRT') { + } else if (code === 'DUCRT' || code === 'IUCRT') { result = { ...result, 'Uncrating price (per cu ft)': '1.71', @@ -363,4 +363,28 @@ describe('DomesticDestinationSITDelivery', () => { const expected = testData('PODFSC'); testAB(result, expected); }); + + it('returns correct data for ICRT', () => { + const result = makeCalculations( + 'ICRT', + 99999, + testParams.InternationalCrating, + testParams.additionalCratingDataDCRT, + ); + const expected = testData('ICRT'); + + testAB(result, expected); + }); + + it('returns correct data for IUCRT', () => { + const result = makeCalculations( + 'IUCRT', + 99999, + testParams.InternationalUncrating, + testParams.additionalCratingDataDCRT, + ); + const expected = testData('IUCRT'); + + testAB(result, expected); + }); }); diff --git a/src/components/Office/ServiceItemCalculations/serviceItemTestParams.js b/src/components/Office/ServiceItemCalculations/serviceItemTestParams.js index e234dc167c9..bf29cdd1807 100644 --- a/src/components/Office/ServiceItemCalculations/serviceItemTestParams.js +++ b/src/components/Office/ServiceItemCalculations/serviceItemTestParams.js @@ -493,6 +493,33 @@ const PortZip = { type: 'STRING', value: '99505', }; +const ExternalCrate = { + eTag: 'MjAyMS0wNy0yOVQyMDoxNTowMS4xNDA1MjZa', + id: 'f5bb063e-38da-4c86-88ce-a6a328e70b92', + key: 'ExternalCrate', + origin: 'PRIME', + paymentServiceItemID: '28039a62-387d-479f-b50f-e0041b7e6e22', + type: 'BOOLEAN', + value: 'FALSE', +}; +const MarketOrigin = { + eTag: 'MjAyMS0wNy0yOVQyMDoxNTowMS4xNDA1MjZa', + id: 'f5bb063e-38da-4c86-88ce-a6a328e70b92', + key: 'MarketOrigin', + origin: 'PRIME', + paymentServiceItemID: '28039a62-387d-479f-b50f-e0041b7e6e22', + type: 'STRING', + value: 'O', +}; +const MarketDest = { + eTag: 'MjAyMS0wNy0yOVQyMDoxNTowMS4xNDA1MjZa', + id: 'f5bb063e-38da-4c86-88ce-a6a328e70b92', + key: 'MarketDest', + origin: 'PRIME', + paymentServiceItemID: '28039a62-387d-479f-b50f-e0041b7e6e22', + type: 'STRING', + value: 'C', +}; const testParams = { DomesticLongHaul: [ ContractCode, @@ -916,6 +943,33 @@ const testParams = { ZipPickupAddress, PortZip, ], + InternationalCrating: [ + ContractYearName, + EscalationCompounded, + PriceRateOrFactor, + ReferenceDate, + CubicFeetBilled, + MarketOrigin, + ServiceAreaOrigin, + ZipPickupAddress, + DimensionWidth, + DimensionHeight, + DimensionLength, + StandaloneCrate, + ExternalCrate, + ], + InternationalUncrating: [ + ReferenceDate, + EscalationCompounded, + CubicFeetBilled, + PriceRateOrFactor, + MarketDest, + ServiceAreaDest, + ZipDestAddress, + DimensionWidth, + DimensionHeight, + DimensionLength, + ], additionalCratingDataDCRT: { reServiceCode: 'DCRT', description: 'Grand piano', diff --git a/src/components/Office/ServiceItemDetails/ServiceItemDetails.jsx b/src/components/Office/ServiceItemDetails/ServiceItemDetails.jsx index 689a790ca64..cbad67cc0b5 100644 --- a/src/components/Office/ServiceItemDetails/ServiceItemDetails.jsx +++ b/src/components/Office/ServiceItemDetails/ServiceItemDetails.jsx @@ -13,6 +13,7 @@ import { SitStatusShape } from 'types/sitStatusShape'; import { formatDateWithUTC } from 'shared/dates'; import { formatCityStateAndPostalCode } from 'utils/shipmentDisplay'; import { formatWeight, convertFromThousandthInchToInch, formatCents, toDollarString } from 'utils/formatters'; +import { SERVICE_ITEM_CODES } from 'constants/serviceItems'; function generateDetailText(details, id, className) { const detailList = Object.keys(details).map((detail) => ( @@ -38,7 +39,7 @@ const generateDestinationSITDetailSection = (id, serviceRequestDocUploads, detai 'First available delivery date 1': '-', 'Customer contact 1': '-', }); - const numberOfDaysApprovedForDOASIT = shipment.sitDaysAllowance ? shipment.sitDaysAllowance - 1 : 0; + const numberOfDaysApprovedForSIT = shipment.sitDaysAllowance ? shipment.sitDaysAllowance - 1 : 0; const sitEndDate = sitStatus && sitStatus.currentSIT?.sitAuthorizedEndDate && @@ -50,7 +51,7 @@ const generateDestinationSITDetailSection = (id, serviceRequestDocUploads, detai return (
- {code === 'DDFSIT' + {code === SERVICE_ITEM_CODES.DDFSIT || code === SERVICE_ITEM_CODES.IDFSIT ? generateDetailText({ 'Original Delivery Address': originalDeliveryAddress ? formatCityStateAndPostalCode(originalDeliveryAddress) @@ -58,7 +59,7 @@ const generateDestinationSITDetailSection = (id, serviceRequestDocUploads, detai 'SIT entry date': details.sitEntryDate ? formatDateWithUTC(details.sitEntryDate, 'DD MMM YYYY') : '-', }) : null} - {code === 'DDASIT' && ( + {code === SERVICE_ITEM_CODES.DDASIT && ( <> {generateDetailText( { @@ -68,7 +69,7 @@ const generateDestinationSITDetailSection = (id, serviceRequestDocUploads, detai "Add'l SIT Start Date": details.sitEntryDate ? moment.utc(details.sitEntryDate).add(1, 'days').format('DD MMM YYYY') : '-', - '# of days approved for': shipment.sitDaysAllowance ? `${numberOfDaysApprovedForDOASIT} days` : '-', + '# of days approved for': shipment.sitDaysAllowance ? `${numberOfDaysApprovedForSIT} days` : '-', 'SIT expiration date': sitEndDate || '-', }, id, @@ -87,7 +88,36 @@ const generateDestinationSITDetailSection = (id, serviceRequestDocUploads, detai ) : null} )} - {code === 'DDSFSC' + {code === SERVICE_ITEM_CODES.IDASIT && ( + <> + {generateDetailText( + { + 'Original Delivery Address': originalDeliveryAddress + ? formatCityStateAndPostalCode(originalDeliveryAddress) + : '-', + "Add'l SIT Start Date": details.sitEntryDate + ? moment.utc(details.sitEntryDate).add(1, 'days').format('DD MMM YYYY') + : '-', + '# of days approved for': shipment.sitDaysAllowance ? `${numberOfDaysApprovedForSIT} days` : '-', + 'SIT expiration date': sitEndDate || '-', + }, + id, + )} + {!isEmpty(serviceRequestDocUploads) ? ( +
+

Download service item documentation:

+ {serviceRequestDocUploads.map((file) => ( + + ))} +
+ ) : null} + + )} + {code === SERVICE_ITEM_CODES.DDSFSC || code === SERVICE_ITEM_CODES.IDSFSC ? generateDetailText( { 'Original Delivery Address': originalDeliveryAddress @@ -102,7 +132,45 @@ const generateDestinationSITDetailSection = (id, serviceRequestDocUploads, detai id, ) : null} - {code === 'DDDSIT' && ( + {code === SERVICE_ITEM_CODES.DDDSIT && ( + <> + {generateDetailText( + { + 'Original Delivery Address': originalDeliveryAddress + ? formatCityStateAndPostalCode(originalDeliveryAddress) + : '-', + 'Final Delivery Address': + details.sitDestinationFinalAddress && details.status !== 'SUBMITTED' + ? formatCityStateAndPostalCode(details.sitDestinationFinalAddress) + : '-', + 'Delivery miles out of SIT': details.sitDeliveryMiles ? details.sitDeliveryMiles : '-', + 'Customer contacted homesafe': details.sitCustomerContacted + ? formatDateWithUTC(details.sitCustomerContacted, 'DD MMM YYYY') + : '-', + 'Customer requested delivery date': details.sitRequestedDelivery + ? formatDateWithUTC(details.sitRequestedDelivery, 'DD MMM YYYY') + : '-', + 'SIT departure date': details.sitDepartureDate + ? formatDateWithUTC(details.sitDepartureDate, 'DD MMM YYYY') + : '-', + }, + id, + )} + {!isEmpty(serviceRequestDocUploads) ? ( +
+

Download service item documentation:

+ {serviceRequestDocUploads.map((file) => ( + + ))} +
+ ) : null} + + )} + {code === SERVICE_ITEM_CODES.IDDSIT && ( <> {generateDetailText( { @@ -140,7 +208,7 @@ const generateDestinationSITDetailSection = (id, serviceRequestDocUploads, detai ) : null} )} - {code === 'DDFSIT' && ( + {(code === SERVICE_ITEM_CODES.DDFSIT || code === SERVICE_ITEM_CODES.IDFSIT) && ( <> {!isEmpty(sortedCustomerContacts) ? sortedCustomerContacts.map((contact, index) => ( @@ -188,7 +256,8 @@ const ServiceItemDetails = ({ id, code, details, serviceRequestDocs, shipment, s let detailSection; switch (code) { - case 'DOFSIT': { + case SERVICE_ITEM_CODES.DOFSIT: + case SERVICE_ITEM_CODES.IOFSIT: { detailSection = (
@@ -221,12 +290,13 @@ const ServiceItemDetails = ({ id, code, details, serviceRequestDocs, shipment, s ); break; } - case 'DOASIT': { - const numberOfDaysApprovedForDOASIT = shipment.sitDaysAllowance ? shipment.sitDaysAllowance - 1 : 0; + case SERVICE_ITEM_CODES.DOASIT: + case SERVICE_ITEM_CODES.IOASIT: { const sitEndDate = sitStatus && sitStatus.currentSIT?.sitAuthorizedEndDate && formatDateWithUTC(sitStatus.currentSIT.sitAuthorizedEndDate, 'DD MMM YYYY'); + const numberOfDaysApprovedForSIT = shipment.sitDaysAllowance ? shipment.sitDaysAllowance - 1 : 0; detailSection = (
@@ -239,7 +309,7 @@ const ServiceItemDetails = ({ id, code, details, serviceRequestDocs, shipment, s "Add'l SIT Start Date": details.sitEntryDate ? moment.utc(details.sitEntryDate).add(1, 'days').format('DD MMM YYYY') : '-', - '# of days approved for': shipment.sitDaysAllowance ? `${numberOfDaysApprovedForDOASIT} days` : '-', + '# of days approved for': shipment.sitDaysAllowance ? `${numberOfDaysApprovedForSIT} days` : '-', 'SIT expiration date': sitEndDate || '-', 'Customer contacted homesafe': details.sitCustomerContacted ? formatDateWithUTC(details.sitCustomerContacted, 'DD MMM YYYY') @@ -272,42 +342,10 @@ const ServiceItemDetails = ({ id, code, details, serviceRequestDocs, shipment, s ); break; } - case 'DOPSIT': { - detailSection = ( -
-
- {generateDetailText( - { - 'Original Pickup Address': details.sitOriginHHGOriginalAddress - ? formatCityStateAndPostalCode(details.sitOriginHHGOriginalAddress) - : '-', - 'Actual Pickup Address': details.sitOriginHHGActualAddress - ? formatCityStateAndPostalCode(details.sitOriginHHGActualAddress) - : '-', - 'Delivery miles into SIT': details.sitDeliveryMiles ? details.sitDeliveryMiles : '-', - }, - id, - )} - {details.rejectionReason && - generateDetailText({ 'Rejection reason': details.rejectionReason }, id, 'margin-top-2')} - {!isEmpty(serviceRequestDocUploads) ? ( -
-

Download service item documentation:

- {serviceRequestDocUploads.map((file) => ( - - ))} -
- ) : null} -
-
- ); - break; - } - case 'DOSFSC': { + case SERVICE_ITEM_CODES.DOPSIT: + case SERVICE_ITEM_CODES.IOPSIT: + case SERVICE_ITEM_CODES.DOSFSC: + case SERVICE_ITEM_CODES.IOSFSC: { detailSection = (
@@ -342,30 +380,14 @@ const ServiceItemDetails = ({ id, code, details, serviceRequestDocs, shipment, s ); break; } - case 'DDFSIT': - case 'DDASIT': { - detailSection = generateDestinationSITDetailSection( - id, - serviceRequestDocUploads, - details, - code, - shipment, - sitStatus, - ); - break; - } - case 'DDDSIT': { - detailSection = generateDestinationSITDetailSection( - id, - serviceRequestDocUploads, - details, - code, - shipment, - sitStatus, - ); - break; - } - case 'DDSFSC': { + case SERVICE_ITEM_CODES.DDFSIT: + case SERVICE_ITEM_CODES.DDASIT: + case SERVICE_ITEM_CODES.IDFSIT: + case SERVICE_ITEM_CODES.IDASIT: + case SERVICE_ITEM_CODES.DDDSIT: + case SERVICE_ITEM_CODES.IDDSIT: + case SERVICE_ITEM_CODES.DDSFSC: + case SERVICE_ITEM_CODES.IDSFSC: { detailSection = generateDestinationSITDetailSection( id, serviceRequestDocUploads, @@ -376,8 +398,8 @@ const ServiceItemDetails = ({ id, code, details, serviceRequestDocs, shipment, s ); break; } - case 'DCRT': - case 'DCRTSA': { + case SERVICE_ITEM_CODES.DCRT: + case SERVICE_ITEM_CODES.DCRTSA: { const { description, itemDimensions, crateDimensions } = details; const itemDimensionFormat = `${convertFromThousandthInchToInch( itemDimensions?.length, @@ -415,7 +437,7 @@ const ServiceItemDetails = ({ id, code, details, serviceRequestDocs, shipment, s ); break; } - case 'DUCRT': { + case SERVICE_ITEM_CODES.DUCRT: { const { description, itemDimensions, crateDimensions } = details; const itemDimensionFormat = `${convertFromThousandthInchToInch( itemDimensions?.length, @@ -452,8 +474,8 @@ const ServiceItemDetails = ({ id, code, details, serviceRequestDocs, shipment, s ); break; } - case 'DOSHUT': - case 'DDSHUT': { + case SERVICE_ITEM_CODES.DOSHUT: + case SERVICE_ITEM_CODES.DDSHUT: { const estimatedWeight = details.estimatedWeight != null ? formatWeight(details.estimatedWeight) : `— lbs`; detailSection = (
@@ -481,18 +503,54 @@ const ServiceItemDetails = ({ id, code, details, serviceRequestDocs, shipment, s ); break; } - case 'DLH': - case 'DSH': - case 'FSC': - case 'DOP': - case 'DDP': - case 'DPK': - case 'DUPK': - case 'ISLH': - case 'IHPK': - case 'IHUPK': - case 'POEFSC': - case 'PODFSC': { + case SERVICE_ITEM_CODES.IOSHUT: + case SERVICE_ITEM_CODES.IDSHUT: { + const estimatedWeight = details.estimatedWeight != null ? formatWeight(details.estimatedWeight) : `— lbs`; + detailSection = ( +
+
+
+
{estimatedWeight}
estimated weight
+
+ {generateDetailText({ + 'Estimated Price': details.estimatedPrice ? toDollarString(formatCents(details.estimatedPrice)) : '-', + })} + {generateDetailText({ Reason: details.reason })} + {generateDetailText({ Market: details.market })} + {details.rejectionReason && + generateDetailText({ 'Rejection reason': details.rejectionReason }, id, 'margin-top-2')} + {!isEmpty(serviceRequestDocUploads) ? ( +
+

Download service item documentation:

+ {serviceRequestDocUploads.map((file) => ( + + ))} +
+ ) : null} +
+
+ ); + break; + } + case SERVICE_ITEM_CODES.DLH: + case SERVICE_ITEM_CODES.DSH: + case SERVICE_ITEM_CODES.FSC: + case SERVICE_ITEM_CODES.DOP: + case SERVICE_ITEM_CODES.DDP: + case SERVICE_ITEM_CODES.DPK: + case SERVICE_ITEM_CODES.DUPK: + case SERVICE_ITEM_CODES.ISLH: + case SERVICE_ITEM_CODES.IHPK: + case SERVICE_ITEM_CODES.IHUPK: + case SERVICE_ITEM_CODES.IUBPK: + case SERVICE_ITEM_CODES.IUBUPK: + case SERVICE_ITEM_CODES.POEFSC: + case SERVICE_ITEM_CODES.PODFSC: + case SERVICE_ITEM_CODES.UBP: { detailSection = (
@@ -504,8 +562,8 @@ const ServiceItemDetails = ({ id, code, details, serviceRequestDocs, shipment, s ); break; } - case 'MS': - case 'CS': { + case SERVICE_ITEM_CODES.MS: + case SERVICE_ITEM_CODES.CS: { const { estimatedPrice } = details; detailSection = (
@@ -514,7 +572,7 @@ const ServiceItemDetails = ({ id, code, details, serviceRequestDocs, shipment, s ); break; } - case 'ICRT': { + case SERVICE_ITEM_CODES.ICRT: { const { description, itemDimensions, crateDimensions, market, externalCrate } = details; const itemDimensionFormat = `${convertFromThousandthInchToInch( itemDimensions?.length, @@ -554,7 +612,7 @@ const ServiceItemDetails = ({ id, code, details, serviceRequestDocs, shipment, s ); break; } - case 'IUCRT': { + case SERVICE_ITEM_CODES.IUCRT: { const { description, itemDimensions, crateDimensions, market } = details; const itemDimensionFormat = `${convertFromThousandthInchToInch( itemDimensions?.length, diff --git a/src/components/Office/ServiceItemDetails/ServiceItemDetails.test.jsx b/src/components/Office/ServiceItemDetails/ServiceItemDetails.test.jsx index d9267d7e572..0aa382213fe 100644 --- a/src/components/Office/ServiceItemDetails/ServiceItemDetails.test.jsx +++ b/src/components/Office/ServiceItemDetails/ServiceItemDetails.test.jsx @@ -170,6 +170,42 @@ describe('ServiceItemDetails Domestic Destination SIT', () => { expect(downloadLink).toBeInstanceOf(HTMLAnchorElement); }); + it('renders IDASIT details', () => { + render( + , + ); + expect(screen.getByText('Original Delivery Address:')).toBeInTheDocument(); + expect(screen.getByText('Destination Original Tampa, FL 33621')).toBeInTheDocument(); + + expect(screen.getByText("Add'l SIT Start Date:")).toBeInTheDocument(); + expect(screen.getByText('12 Mar 2024')).toBeInTheDocument(); + + expect(screen.queryByText('Customer contacted homesafe:')).not.toBeInTheDocument(); + expect(screen.queryByText('14 Mar 2024')).not.toBeInTheDocument(); + + expect(screen.getByText('# of days approved for:')).toBeInTheDocument(); + expect(screen.getByText('89 days')).toBeInTheDocument(); + + expect(screen.getByText('SIT expiration date:')).toBeInTheDocument(); + expect(screen.getByText('17 Mar 2024')).toBeInTheDocument(); + + expect(screen.queryByText('Customer requested delivery date:')).not.toBeInTheDocument(); + expect(screen.queryByText('15 Mar 2024')).not.toBeInTheDocument(); + + expect(screen.queryByText('SIT departure date:')).not.toBeInTheDocument(); + expect(screen.queryByText('16 Mar 2024')).not.toBeInTheDocument(); + expect(screen.getByText('Download service item documentation:')).toBeInTheDocument(); + const downloadLink = screen.getByText('receipt.pdf'); + expect(downloadLink).toBeInstanceOf(HTMLAnchorElement); + }); + it('renders DDDSIT details', () => { render(); expect(screen.getByText('Original Delivery Address:')).toBeInTheDocument(); @@ -193,6 +229,31 @@ describe('ServiceItemDetails Domestic Destination SIT', () => { const downloadLink = screen.getByText('receipt.pdf'); expect(downloadLink).toBeInstanceOf(HTMLAnchorElement); }); + + it('renders IDDSIT details', () => { + render(); + expect(screen.getByText('Original Delivery Address:')).toBeInTheDocument(); + expect(screen.getByText('Destination Original Tampa, FL 33621')).toBeInTheDocument(); + + expect(screen.getByText('Final Delivery Address:')).toBeInTheDocument(); + expect(screen.getByText('Destination Final MacDill, FL 33621')).toBeInTheDocument(); + + expect(screen.getByText('Delivery miles out of SIT:')).toBeInTheDocument(); + expect(screen.getByText('50')).toBeInTheDocument(); + + expect(screen.getByText('Customer contacted homesafe:')).toBeInTheDocument(); + expect(screen.getByText('14 Mar 2024')).toBeInTheDocument(); + + expect(screen.getByText('Customer requested delivery date:')).toBeInTheDocument(); + expect(screen.getByText('15 Mar 2024')).toBeInTheDocument(); + + expect(screen.getByText('SIT departure date:')).toBeInTheDocument(); + expect(screen.getByText('16 Mar 2024')).toBeInTheDocument(); + expect(screen.getByText('Download service item documentation:')).toBeInTheDocument(); + const downloadLink = screen.getByText('receipt.pdf'); + expect(downloadLink).toBeInstanceOf(HTMLAnchorElement); + }); + it('renders DDDSIT details with - for the final delivery address is service item is in submitted state', () => { render( { expect(screen.getByText('Final Delivery Address:')).toBeInTheDocument(); expect(screen.getByText('-')).toBeInTheDocument(); }); + + it('renders IDDSIT details with - for the final delivery address is service item is in submitted state', () => { + render( + , + ); + + expect(screen.getByText('Final Delivery Address:')).toBeInTheDocument(); + expect(screen.getByText('-')).toBeInTheDocument(); + }); + it('renders DDFSIT details', () => { render(); expect(screen.getByText('Original Delivery Address:')).toBeInTheDocument(); expect(screen.getByText('Destination Original Tampa, FL 33621')).toBeInTheDocument(); }); + + it('renders IDFSIT details', () => { + render(); + expect(screen.getByText('Original Delivery Address:')).toBeInTheDocument(); + expect(screen.getByText('Destination Original Tampa, FL 33621')).toBeInTheDocument(); + }); + it('renders DDSFSC details', () => { render(); expect(screen.getByText('Original Delivery Address:')).toBeInTheDocument(); @@ -222,6 +305,19 @@ describe('ServiceItemDetails Domestic Destination SIT', () => { expect(screen.getByText('Delivery miles out of SIT:')).toBeInTheDocument(); expect(screen.getByText('50')).toBeInTheDocument(); }); + + it('renders IDSFSC details', () => { + render(); + expect(screen.getByText('Original Delivery Address:')).toBeInTheDocument(); + expect(screen.getByText('Destination Original Tampa, FL 33621')).toBeInTheDocument(); + + expect(screen.getByText('Final Delivery Address:')).toBeInTheDocument(); + expect(screen.getByText('Destination Final MacDill, FL 33621')).toBeInTheDocument(); + + expect(screen.getByText('Delivery miles out of SIT:')).toBeInTheDocument(); + expect(screen.getByText('50')).toBeInTheDocument(); + }); + it('renders DDSFSC details with - for the final delivery address is service item is in submitted state', () => { render( { expect(screen.getByText('Final Delivery Address:')).toBeInTheDocument(); expect(screen.getByText('-')).toBeInTheDocument(); }); + + it('renders IDSFSC details with - for the final delivery address is service item is in submitted state', () => { + render( + , + ); + + expect(screen.getByText('Final Delivery Address:')).toBeInTheDocument(); + expect(screen.getByText('-')).toBeInTheDocument(); + }); }); describe('ServiceItemDetails Domestic Origin SIT', () => { @@ -272,6 +382,40 @@ describe('ServiceItemDetails Domestic Origin SIT', () => { expect(screen.getByText('16 Mar 2024')).toBeInTheDocument(); }); + it(`renders IOASIT details`, () => { + render( + , + ); + + expect(screen.getByText('Original Pickup Address:')).toBeInTheDocument(); + expect(screen.getByText('Origin Original Tampa, FL 33621')).toBeInTheDocument(); + + expect(screen.getByText("Add'l SIT Start Date:")).toBeInTheDocument(); + expect(screen.getByText('12 Mar 2024')).toBeInTheDocument(); + + expect(screen.getByText('# of days approved for:')).toBeInTheDocument(); + expect(screen.getByText('89 days')).toBeInTheDocument(); + + expect(screen.getByText('SIT expiration date:')).toBeInTheDocument(); + expect(screen.getByText('17 Mar 2024')).toBeInTheDocument(); + + expect(screen.getByText('Customer contacted homesafe:')).toBeInTheDocument(); + expect(screen.getByText('14 Mar 2024')).toBeInTheDocument(); + + expect(screen.getByText('Customer requested delivery date:')).toBeInTheDocument(); + expect(screen.getByText('15 Mar 2024')).toBeInTheDocument(); + + expect(screen.getByText('SIT departure date:')).toBeInTheDocument(); + expect(screen.getByText('16 Mar 2024')).toBeInTheDocument(); + }); + it(`renders DOPSIT details`, () => { render(); @@ -285,6 +429,19 @@ describe('ServiceItemDetails Domestic Origin SIT', () => { expect(screen.getByText('50')).toBeInTheDocument(); }); + it(`renders IOPSIT details`, () => { + render(); + + expect(screen.getByText('Original Pickup Address:')).toBeInTheDocument(); + expect(screen.getByText('Origin Original Tampa, FL 33621')).toBeInTheDocument(); + + expect(screen.getByText('Actual Pickup Address:')).toBeInTheDocument(); + expect(screen.getByText('Origin Actual MacDill, FL 33621')).toBeInTheDocument(); + + expect(screen.getByText('Delivery miles into SIT:')).toBeInTheDocument(); + expect(screen.getByText('50')).toBeInTheDocument(); + }); + it(`renders DOSFSC details`, () => { render(); @@ -297,11 +454,24 @@ describe('ServiceItemDetails Domestic Origin SIT', () => { expect(screen.getByText('Delivery miles into SIT:')).toBeInTheDocument(); expect(screen.getByText('50')).toBeInTheDocument(); }); + + it(`renders IOSFSC details`, () => { + render(); + + expect(screen.getByText('Original Pickup Address:')).toBeInTheDocument(); + expect(screen.getByText('Origin Original Tampa, FL 33621')).toBeInTheDocument(); + + expect(screen.getByText('Actual Pickup Address:')).toBeInTheDocument(); + expect(screen.getByText('Origin Actual MacDill, FL 33621')).toBeInTheDocument(); + + expect(screen.getByText('Delivery miles into SIT:')).toBeInTheDocument(); + expect(screen.getByText('50')).toBeInTheDocument(); + }); }); -describe('ServiceItemDetails for DOFSIT', () => { - it('renders SIT entry date, ZIP, original pickup address, and reason', () => { - render(); +describe('ServiceItemDetails for DOFSIT/IOFSIT - origin 1st day SIT', () => { + it.each([['DOFSIT'], ['IOFSIT']])('renders SIT entry date, ZIP, original pickup address, and reason', (code) => { + render(); expect(screen.getByText('Original Pickup Address:')).toBeInTheDocument(); expect(screen.getByText('Origin Original Tampa, FL 33621')).toBeInTheDocument(); @@ -475,6 +645,33 @@ describe('ServiceItemDetails Domestic Shuttling', () => { }); }); +describe('ServiceItemDetails International Shuttling', () => { + const shuttleDetails = { + ...details, + market: 'OCONUS', + }; + + it.each([['IOSHUT'], ['IDSHUT']])('renders formatted estimated weight and reason', (code) => { + render(); + + expect(screen.getByText('2,500 lbs')).toBeInTheDocument(); + expect(screen.getByText('estimated weight')).toBeInTheDocument(); + expect(screen.getByText('Reason:')).toBeInTheDocument(); + expect(screen.getByText('Market:')).toBeInTheDocument(); + expect(screen.getByText('some reason')).toBeInTheDocument(); + expect(screen.getByText('Download service item documentation:')).toBeInTheDocument(); + const downloadLink = screen.getByText('receipt.pdf'); + expect(downloadLink).toBeInstanceOf(HTMLAnchorElement); + }); + + it.each([['DOSHUT'], ['DDSHUT']])('renders estimated weight nil values with an em dash', (code) => { + render(); + + expect(screen.getByText('— lbs')).toBeInTheDocument(); + expect(screen.getByText('estimated weight')).toBeInTheDocument(); + }); +}); + describe('ServiceItemDetails Crating Rejected', () => { it('renders the rejection reason field when it is populated with information', () => { render( @@ -523,44 +720,70 @@ describe('ServiceItemDetails Crating Rejected', () => { }); }); -describe('ServiceItemDetails Estimated Price for DLH, DSH, FSC, DOP, DDP, DPK, DUPK', () => { - it.each([['DLH'], ['DSH'], ['FSC'], ['DOP'], ['DDP'], ['DPK'], ['DUPK']])( - 'renders the formatted estimated price field for the service items', - (code) => { - render( - , - ); - - expect(screen.getByText('Estimated Price:')).toBeInTheDocument(); - expect(screen.getByText('$28.00')).toBeInTheDocument(); - }, - ); +describe('ServiceItemDetails Estimated Price for DLH, DSH, FSC, DOP, DDP, DPK, DUPK, ISLH, IHPK, IHUPK, IUBPK, IUBUPK, POEFSC, PODFSC, UBP', () => { + it.each([ + ['DLH'], + ['DSH'], + ['FSC'], + ['DOP'], + ['DDP'], + ['DPK'], + ['DUPK'], + ['ISLH'], + ['IHPK'], + ['IHUPK'], + ['IUBPK'], + ['IUBUPK'], + ['POEFSC'], + ['PODFSC'], + ['UBP'], + ])('renders the formatted estimated price field for the service item: %s', (code) => { + render( + , + ); + + expect(screen.getByText('Estimated Price:')).toBeInTheDocument(); + expect(screen.getByText('$28.00')).toBeInTheDocument(); + }); const noEstimatePriceDetails = {}; - it.each([['DLH'], ['DSH'], ['FSC'], ['DOP'], ['DDP'], ['DPK'], ['DUPK']])( - 'renders - for estimated price when price is not in details', - (code) => { - render( - , - ); - - expect(screen.getByText('Estimated Price:')).toBeInTheDocument(); - expect(screen.getByText('-')).toBeInTheDocument(); - }, - ); + it.each([ + ['DLH'], + ['DSH'], + ['FSC'], + ['DOP'], + ['DDP'], + ['DPK'], + ['DUPK'], + ['ISLH'], + ['IHPK'], + ['IHUPK'], + ['IUBPK'], + ['IUBUPK'], + ['POEFSC'], + ['PODFSC'], + ['UBP'], + ])('renders - for estimated price when price is not in details for the service item: %s', (code) => { + render( + , + ); + + expect(screen.getByText('Estimated Price:')).toBeInTheDocument(); + expect(screen.getByText('-')).toBeInTheDocument(); + }); }); describe('ServiceItemDetails Price for MS, CS', () => { diff --git a/src/components/Office/ServiceItemsTable/ServiceItemsTable.jsx b/src/components/Office/ServiceItemsTable/ServiceItemsTable.jsx index 60f48a93f84..5495ad0fc48 100644 --- a/src/components/Office/ServiceItemsTable/ServiceItemsTable.jsx +++ b/src/components/Office/ServiceItemsTable/ServiceItemsTable.jsx @@ -10,6 +10,7 @@ import { ServiceItemDetailsShape } from '../../../types/serviceItems'; import styles from './ServiceItemsTable.module.scss'; import { SERVICE_ITEM_STATUS } from 'shared/constants'; +import { SERVICE_ITEM_CODES } from 'constants/serviceItems'; import { ALLOWED_RESUBMISSION_SI_CODES, ALLOWED_SIT_UPDATE_SI_CODES } from 'constants/sitUpdates'; import { formatDateFromIso } from 'utils/formatters'; import ServiceItemDetails from 'components/Office/ServiceItemDetails/ServiceItemDetails'; @@ -30,19 +31,37 @@ import { nullSafeStringCompare } from 'utils/string'; // destination SIT function sortServiceItems(items) { // Prioritize service items with codes 'DSH' (shorthaul) and 'DLH' (linehaul) to be at the top of the list - const haulTypeServiceItemCodes = ['DSH', 'DLH']; + const haulTypeServiceItemCodes = [SERVICE_ITEM_CODES.DSH, SERVICE_ITEM_CODES.DLH]; const haulTypeServiceItems = items.filter((item) => haulTypeServiceItemCodes.includes(item.code)); const sortedHaulTypeServiceItems = haulTypeServiceItems.sort( (a, b) => haulTypeServiceItemCodes.indexOf(a.code) - haulTypeServiceItemCodes.indexOf(b.code), ); // Filter and sort destination SIT. Code index is also the sort order - const destinationServiceItemCodes = ['DDFSIT', 'DDASIT', 'DDDSIT', 'DDSFSC']; + const destinationServiceItemCodes = [ + SERVICE_ITEM_CODES.DDFSIT, + SERVICE_ITEM_CODES.DDASIT, + SERVICE_ITEM_CODES.DDDSIT, + SERVICE_ITEM_CODES.DDSFSC, + SERVICE_ITEM_CODES.IDFSIT, + SERVICE_ITEM_CODES.IDASIT, + SERVICE_ITEM_CODES.IDDSIT, + SERVICE_ITEM_CODES.IDSFSC, + ]; const destinationServiceItems = items.filter((item) => destinationServiceItemCodes.includes(item.code)); const sortedDestinationServiceItems = destinationServiceItems.sort( (a, b) => destinationServiceItemCodes.indexOf(a.code) - destinationServiceItemCodes.indexOf(b.code), ); // Filter origin SIT. Code index is also the sort order - const originServiceItemCodes = ['DOFSIT', 'DOASIT', 'DOPSIT', 'DOSFSC']; + const originServiceItemCodes = [ + SERVICE_ITEM_CODES.DOFSIT, + SERVICE_ITEM_CODES.DOASIT, + SERVICE_ITEM_CODES.DOPSIT, + SERVICE_ITEM_CODES.DOSFSC, + SERVICE_ITEM_CODES.IOFSIT, + SERVICE_ITEM_CODES.IOASIT, + SERVICE_ITEM_CODES.IOPSIT, + SERVICE_ITEM_CODES.IOSFSC, + ]; const originServiceItems = items.filter((item) => originServiceItemCodes.includes(item.code)); const sortedOriginServiceItems = originServiceItems.sort( (a, b) => originServiceItemCodes.indexOf(a.code) - originServiceItemCodes.indexOf(b.code), @@ -202,7 +221,7 @@ const ServiceItemsTable = ({ // we don't want to display the "Accept" button for a DLH or DSH service item that was rejected by a shorthaul to linehaul change or vice versa let rejectedDSHorDLHServiceItem = false; if ( - (serviceItem.code === 'DLH' || serviceItem.code === 'DSH') && + (serviceItem.code === SERVICE_ITEM_CODES.DLH || serviceItem.code === SERVICE_ITEM_CODES.DSH) && serviceItem.details.rejectionReason === 'Automatically rejected due to change in delivery address affecting the ZIP code qualification for short haul / line haul.' ) { @@ -215,7 +234,9 @@ const ServiceItemsTable = ({
{serviceItem.serviceItem} - {(code === 'DCRT' || code === 'ICRT') && serviceItem.details.standaloneCrate && ' - Standalone'} + {(code === SERVICE_ITEM_CODES.DCRT || code === SERVICE_ITEM_CODES.ICRT) && + serviceItem.details.standaloneCrate && + ' - Standalone'} {ALLOWED_RESUBMISSION_SI_CODES.includes(code) && resubmittedToolTip.isResubmitted ? ( { - if (code === 'DDFSIT' || code === 'DOFSIT') { + if ( + code === SERVICE_ITEM_CODES.DDFSIT || + code === SERVICE_ITEM_CODES.DOFSIT || + code === SERVICE_ITEM_CODES.IDFSIT || + code === SERVICE_ITEM_CODES.IOFSIT + ) { handleShowEditSitEntryDateModal(id, mtoShipmentID); } else { handleShowEditSitAddressModal(id, mtoShipmentID); diff --git a/src/components/Office/ServiceItemsTable/ServiceItemsTable.test.jsx b/src/components/Office/ServiceItemsTable/ServiceItemsTable.test.jsx index 6b3e4a6ee87..cc159e86625 100644 --- a/src/components/Office/ServiceItemsTable/ServiceItemsTable.test.jsx +++ b/src/components/Office/ServiceItemsTable/ServiceItemsTable.test.jsx @@ -298,6 +298,63 @@ describe('ServiceItemsTable', () => { expect(wrapper.find('dd').at(5).text()).toBe('01 Jan 2021, 0800Z'); }); + it('renders the customer contacts for IDFSIT service item', () => { + const serviceItems = [ + { + id: 'abc123', + createdAt: '2020-11-20', + serviceItem: 'Domestic Crating', + code: 'IDFSIT', + details: { + sitEntryDate: '2020-12-31', + customerContacts: [ + { + timeMilitary: '0400Z', + firstAvailableDeliveryDate: '2020-12-31', + dateOfContact: '2020-12-31', + }, + { timeMilitary: '0800Z', firstAvailableDeliveryDate: '2021-01-01', dateOfContact: '2021-01-01' }, + ], + sitDestinationOriginalAddress: { + city: 'Destination Original Tampa', + eTag: 'MjAyNC0wMy0xMlQxOTo1OTowOC41NjkxMzla', + id: '7fd6cb90-54cd-44d8-8735-102e28734d84', + postalCode: '33621', + state: 'FL', + streetAddress1: 'MacDill', + }, + }, + }, + ]; + + const wrapper = mount( + + + , + ); + + expect(wrapper.find('table').exists()).toBe(true); + expect(wrapper.find('dt').at(0).text()).toBe('Original Delivery Address:'); + expect(wrapper.find('dd').at(0).text()).toBe('Destination Original Tampa, FL 33621'); + + expect(wrapper.find('dt').at(1).text()).toBe('SIT entry date:'); + expect(wrapper.find('dd').at(1).text()).toBe('31 Dec 2020'); + + expect(wrapper.find('dt').at(2).text()).toBe('First available delivery date 1:'); + expect(wrapper.find('dd').at(2).text()).toBe('31 Dec 2020'); + expect(wrapper.find('dt').at(3).text()).toBe('Customer contact attempt 1:'); + expect(wrapper.find('dd').at(3).text()).toBe('31 Dec 2020, 0400Z'); + + expect(wrapper.find('dt').at(4).text()).toBe('First available delivery date 2:'); + expect(wrapper.find('dd').at(4).text()).toBe('01 Jan 2021'); + expect(wrapper.find('dt').at(5).text()).toBe('Customer contact attempt 2:'); + expect(wrapper.find('dd').at(5).text()).toBe('01 Jan 2021, 0800Z'); + }); + it('should render the SITPostalCode ZIP, and reason for DOFSIT service item', () => { const serviceItems = [ { @@ -340,6 +397,48 @@ describe('ServiceItemsTable', () => { expect(wrapper.find('dd').at(2).contains('This is the reason')).toBe(true); }); + it('should render the SITPostalCode ZIP, and reason for IOFSIT service item', () => { + const serviceItems = [ + { + id: 'abc123', + submittedAt: '2020-11-20', + serviceItem: 'Domestic Origin 1st Day SIT', + code: 'IOFSIT', + details: { + pickupPostalCode: '11111', + SITPostalCode: '12345', + reason: 'This is the reason', + sitEntryDate: '2023-12-25T00:00:00.000Z', + sitOriginHHGOriginalAddress: { + city: 'Origin Original Tampa', + eTag: 'MjAyNC0wMy0xMlQxOTo1OTowOC41NjkxMzla', + id: '7fd6cb90-54cd-44d8-8735-102e28734d84', + postalCode: '33621', + state: 'FL', + streetAddress1: 'MacDill', + }, + }, + }, + ]; + + const wrapper = mount( + + + , + ); + expect(wrapper.find('dt').at(0).contains('Original Pickup Address')).toBe(true); + expect(wrapper.find('dd').at(0).contains('Origin Original Tampa, FL 33621')).toBe(true); + + expect(wrapper.find('dt').at(1).contains('SIT entry date')).toBe(true); + expect(wrapper.find('dd').at(1).contains('25 Dec 2023')).toBe(true); + expect(wrapper.find('dt').at(2).contains('Reason')).toBe(true); + expect(wrapper.find('dd').at(2).contains('This is the reason')).toBe(true); + }); + it('calls the update service item status handler when the accept button is clicked', () => { const serviceItems = [ { diff --git a/src/components/Office/ShipmentDisplay/ShipmentDisplay.jsx b/src/components/Office/ShipmentDisplay/ShipmentDisplay.jsx index 573c5edc984..3627b64714a 100644 --- a/src/components/Office/ShipmentDisplay/ShipmentDisplay.jsx +++ b/src/components/Office/ShipmentDisplay/ShipmentDisplay.jsx @@ -193,6 +193,7 @@ ShipmentDisplay.propTypes = { SHIPMENT_TYPES.BOAT_HAUL_AWAY, SHIPMENT_TYPES.BOAT_TOW_AWAY, SHIPMENT_OPTIONS.MOBILE_HOME, + SHIPMENT_OPTIONS.UNACCOMPANIED_BAGGAGE, ]), displayInfo: PropTypes.oneOfType([ PropTypes.shape({ diff --git a/src/components/Office/ShipmentForm/ShipmentForm.jsx b/src/components/Office/ShipmentForm/ShipmentForm.jsx index 076212d6953..5b2be6f5606 100644 --- a/src/components/Office/ShipmentForm/ShipmentForm.jsx +++ b/src/components/Office/ShipmentForm/ShipmentForm.jsx @@ -70,6 +70,7 @@ import { validateDate } from 'utils/validation'; import { isBooleanFlagEnabled } from 'utils/featureFlags'; import { dateSelectionWeekendHolidayCheck } from 'utils/calendar'; import { datePickerFormat, formatDate } from 'shared/dates'; +import { isPreceedingAddressComplete } from 'shared/utils'; const ShipmentForm = (props) => { const { @@ -560,14 +561,14 @@ const ShipmentForm = (props) => { storageFacility, usesExternalVendor, destinationType, - hasSecondaryPickup: hasSecondaryPickup === 'yes', - secondaryPickup: hasSecondaryPickup === 'yes' ? secondaryPickup : {}, - hasSecondaryDelivery: hasSecondaryDelivery === 'yes', - secondaryDelivery: hasSecondaryDelivery === 'yes' ? secondaryDelivery : {}, - hasTertiaryPickup: hasTertiaryPickup === 'yes', - tertiaryPickup: hasTertiaryPickup === 'yes' ? tertiaryPickup : {}, - hasTertiaryDelivery: hasTertiaryDelivery === 'yes', - tertiaryDelivery: hasTertiaryDelivery === 'yes' ? tertiaryDelivery : {}, + hasSecondaryPickup: hasSecondaryPickup === 'true', + secondaryPickup: hasSecondaryPickup === 'true' ? secondaryPickup : {}, + hasSecondaryDelivery: hasSecondaryDelivery === 'true', + secondaryDelivery: hasSecondaryDelivery === 'true' ? secondaryDelivery : {}, + hasTertiaryPickup: hasTertiaryPickup === 'true', + tertiaryPickup: hasTertiaryPickup === 'true' ? tertiaryPickup : {}, + hasTertiaryDelivery: hasTertiaryDelivery === 'true', + tertiaryDelivery: hasTertiaryDelivery === 'true' ? tertiaryDelivery : {}, }); // Mobile Home Shipment @@ -657,7 +658,6 @@ const ShipmentForm = (props) => { hasTertiaryDelivery, isActualExpenseReimbursement, } = values; - const lengthHasError = !!( (formikProps.touched.lengthFeet && formikProps.errors.lengthFeet === 'Required') || (formikProps.touched.lengthInches && formikProps.errors.lengthFeet === 'Required') @@ -788,7 +788,7 @@ const ShipmentForm = (props) => { if (status === ADDRESS_UPDATE_STATUS.APPROVED) { setValues({ ...values, - hasDeliveryAddress: 'yes', + hasDeliveryAddress: 'true', delivery: { ...values.delivery, address: mtoShipment.deliveryAddressUpdate.newAddress, @@ -891,8 +891,8 @@ const ShipmentForm = (props) => { {isNTSR && } - {isMobileHome && ( - { /> )} - {isBoat && ( - { data-testid="has-secondary-pickup" label="Yes" name="hasSecondaryPickup" - value="yes" + value="true" title="Yes, I have a second pickup address" - checked={hasSecondaryPickup === 'yes'} + checked={hasSecondaryPickup === 'true'} + disabled={!isPreceedingAddressComplete('true', values.pickup.address)} /> { data-testid="no-secondary-pickup" label="No" name="hasSecondaryPickup" - value="no" + value="false" title="No, I do not have a second pickup address" - checked={hasSecondaryPickup !== 'yes'} + checked={hasSecondaryPickup !== 'true'} + disabled={!isPreceedingAddressComplete('true', values.pickup.address)} />
- {hasSecondaryPickup === 'yes' && ( + {hasSecondaryPickup === 'true' && ( <> { data-testid="has-tertiary-pickup" label="Yes" name="hasTertiaryPickup" - value="yes" + value="true" title="Yes, I have a third pickup address" - checked={hasTertiaryPickup === 'yes'} + checked={hasTertiaryPickup === 'true'} + disabled={ + !isPreceedingAddressComplete( + hasSecondaryPickup, + values.secondaryPickup.address, + ) + } /> { data-testid="no-tertiary-pickup" label="No" name="hasTertiaryPickup" - value="no" + value="false" title="No, I do not have a third pickup address" - checked={hasTertiaryPickup !== 'yes'} + checked={hasTertiaryPickup !== 'true'} + disabled={ + !isPreceedingAddressComplete( + hasSecondaryPickup, + values.secondaryPickup.address, + ) + } />
- {hasTertiaryPickup === 'yes' && ( + {hasTertiaryPickup === 'true' && ( { id="has-secondary-delivery" label="Yes" name="hasSecondaryDelivery" - value="yes" + value="true" title="Yes, I have a second destination location" - checked={hasSecondaryDelivery === 'yes'} + checked={hasSecondaryDelivery === 'true'} + disabled={!isPreceedingAddressComplete('true', values.delivery.address)} /> { id="no-secondary-delivery" label="No" name="hasSecondaryDelivery" - value="no" + value="false" title="No, I do not have a second destination location" - checked={hasSecondaryDelivery !== 'yes'} + checked={hasSecondaryDelivery !== 'true'} + disabled={!isPreceedingAddressComplete('true', values.delivery.address)} />
- {hasSecondaryDelivery === 'yes' && ( + {hasSecondaryDelivery === 'true' && ( <> { data-testid="has-tertiary-delivery" label="Yes" name="hasTertiaryDelivery" - value="yes" + value="true" title="Yes, I have a third delivery address" - checked={hasTertiaryDelivery === 'yes'} + checked={hasTertiaryDelivery === 'true'} + disabled={ + !isPreceedingAddressComplete( + hasSecondaryDelivery, + values.secondaryDelivery.address, + ) + } /> { data-testid="no-tertiary-delivery" label="No" name="hasTertiaryDelivery" - value="no" + value="false" title="No, I do not have a third delivery address" - checked={hasTertiaryDelivery !== 'yes'} + checked={hasTertiaryDelivery !== 'true'} + disabled={ + !isPreceedingAddressComplete( + hasSecondaryDelivery, + values.secondaryDelivery.address, + ) + } />
- {hasTertiaryDelivery === 'yes' && ( + {hasTertiaryDelivery === 'true' && ( { id="has-delivery-address" label="Yes" name="hasDeliveryAddress" - value="yes" + value="true" title="Yes, I know my delivery address" - checked={hasDeliveryAddress === 'yes'} + checked={hasDeliveryAddress === 'true'} />
- {hasDeliveryAddress === 'yes' ? ( + {hasDeliveryAddress === 'true' ? ( { id="has-secondary-delivery" label="Yes" name="hasSecondaryDelivery" - value="yes" + value="true" title="Yes, I have a second destination location" - checked={hasSecondaryDelivery === 'yes'} + checked={hasSecondaryDelivery === 'true'} + disabled={ + !isPreceedingAddressComplete(hasDeliveryAddress, values.delivery.address) + } /> { id="no-secondary-delivery" label="No" name="hasSecondaryDelivery" - value="no" + value="false" title="No, I do not have a second destination location" - checked={hasSecondaryDelivery !== 'yes'} + checked={hasSecondaryDelivery !== 'true'} + disabled={ + !isPreceedingAddressComplete(hasDeliveryAddress, values.delivery.address) + } />
- {hasSecondaryDelivery === 'yes' && ( + {hasSecondaryDelivery === 'true' && ( <> { data-testid="has-tertiary-delivery" label="Yes" name="hasTertiaryDelivery" - value="yes" + value="true" title="Yes, I have a third delivery address" - checked={hasTertiaryDelivery === 'yes'} + checked={hasTertiaryDelivery === 'true'} + disabled={ + !isPreceedingAddressComplete( + hasSecondaryDelivery, + values.secondaryDelivery.address, + ) + } /> { data-testid="no-tertiary-delivery" label="No" name="hasTertiaryDelivery" - value="no" + value="false" title="No, I do not have a third delivery address" - checked={hasTertiaryDelivery !== 'yes'} + checked={hasTertiaryDelivery !== 'true'} + disabled={ + !isPreceedingAddressComplete( + hasSecondaryDelivery, + values.secondaryDelivery.address, + ) + } />
- {hasTertiaryDelivery === 'yes' && ( + {hasTertiaryDelivery === 'true' && ( { value="true" title="Yes, there is a second pickup address" checked={hasSecondaryPickup === 'true'} + disabled={!isPreceedingAddressComplete('true', values.pickup.address)} /> { value="false" title="No, there is not a second pickup address" checked={hasSecondaryPickup !== 'true'} + disabled={!isPreceedingAddressComplete('true', values.pickup.address)} />
@@ -1487,6 +1535,12 @@ const ShipmentForm = (props) => { value="true" title="Yes, there is a third pickup address" checked={hasTertiaryPickup === 'true'} + disabled={ + !isPreceedingAddressComplete( + hasSecondaryPickup, + values.secondaryPickup.address, + ) + } /> { value="false" title="No, there is not a third pickup address" checked={hasTertiaryPickup !== 'true'} + disabled={ + !isPreceedingAddressComplete( + hasSecondaryPickup, + values.secondaryPickup.address, + ) + } />
@@ -1539,6 +1599,7 @@ const ShipmentForm = (props) => { value="true" title="Yes, there is a second destination location" checked={hasSecondaryDestination === 'true'} + disabled={!isPreceedingAddressComplete('true', values.destination.address)} /> { value="false" title="No, there is not a second destination location" checked={hasSecondaryDestination !== 'true'} + disabled={!isPreceedingAddressComplete('true', values.destination.address)} />
@@ -1577,6 +1639,12 @@ const ShipmentForm = (props) => { value="true" title="Yes, I have a third delivery address" checked={hasTertiaryDestination === 'true'} + disabled={ + !isPreceedingAddressComplete( + hasSecondaryDestination, + values.secondaryDestination.address, + ) + } /> { value="false" title="No, I do not have a third delivery address" checked={hasTertiaryDestination !== 'true'} + disabled={ + !isPreceedingAddressComplete( + hasSecondaryDestination, + values.secondaryDestination.address, + ) + } /> diff --git a/src/components/Office/ShipmentFormRemarks/ShipmentFormRemarks.jsx b/src/components/Office/ShipmentFormRemarks/ShipmentFormRemarks.jsx index 2ede143fb6b..20808b6be56 100644 --- a/src/components/Office/ShipmentFormRemarks/ShipmentFormRemarks.jsx +++ b/src/components/Office/ShipmentFormRemarks/ShipmentFormRemarks.jsx @@ -72,6 +72,7 @@ ShipmentFormRemarks.propTypes = { SHIPMENT_OPTIONS.BOAT, SHIPMENT_TYPES.BOAT_HAUL_AWAY, SHIPMENT_TYPES.BOAT_TOW_AWAY, + SHIPMENT_OPTIONS.UNACCOMPANIED_BAGGAGE, ]).isRequired, customerRemarks: PropTypes.string, counselorRemarks: PropTypes.string, diff --git a/src/components/Office/ShipmentHeading/ShipmentHeading.jsx b/src/components/Office/ShipmentHeading/ShipmentHeading.jsx index 2c70891dd12..41459aeea00 100644 --- a/src/components/Office/ShipmentHeading/ShipmentHeading.jsx +++ b/src/components/Office/ShipmentHeading/ShipmentHeading.jsx @@ -21,8 +21,10 @@ function ShipmentHeading({ shipmentInfo, handleShowCancellationModal, isMoveLock return (
- {shipmentInfo.marketCode} -

{shipmentInfo.shipmentType}

+

+ {shipmentInfo.marketCode} + {shipmentInfo.shipmentType} +

{shipmentStatus === shipmentStatuses.CANCELED && canceled} {shipmentInfo.isDiversion && diversion} diff --git a/src/components/Office/ShipmentServiceItemsTable/ShipmentServiceItemsTable.test.jsx b/src/components/Office/ShipmentServiceItemsTable/ShipmentServiceItemsTable.test.jsx index 592ae52467a..d896ad62a6f 100644 --- a/src/components/Office/ShipmentServiceItemsTable/ShipmentServiceItemsTable.test.jsx +++ b/src/components/Office/ShipmentServiceItemsTable/ShipmentServiceItemsTable.test.jsx @@ -11,14 +11,14 @@ const reServiceItemResponse = [ isAutoApproved: true, marketCode: 'i', serviceCode: 'POEFSC', - serviceName: 'International POE Fuel Surcharge', + serviceName: 'International POE fuel surcharge', shipmentType: 'UNACCOMPANIED_BAGGAGE', }, { isAutoApproved: true, marketCode: 'i', serviceCode: 'PODFSC', - serviceName: 'International POD Fuel Surcharge', + serviceName: 'International POD fuel surcharge', shipmentType: 'UNACCOMPANIED_BAGGAGE', }, { @@ -46,21 +46,21 @@ const reServiceItemResponse = [ isAutoApproved: true, marketCode: 'i', serviceCode: 'POEFSC', - serviceName: 'International POE Fuel Surcharge', + serviceName: 'International POE fuel surcharge', shipmentType: 'HHG', }, { isAutoApproved: true, marketCode: 'i', serviceCode: 'PODFSC', - serviceName: 'International POD Fuel Surcharge', + serviceName: 'International POD fuel surcharge', shipmentType: 'HHG', }, { isAutoApproved: true, marketCode: 'i', serviceCode: 'ISLH', - serviceName: 'International Shipping & Linehaul', + serviceName: 'International shipping & linehaul', shipmentType: 'HHG', }, { @@ -200,25 +200,25 @@ const reServiceItemResponse = [ { marketCode: 'i', serviceCode: 'IOSFSC', - serviceName: 'International Origin SIT Fuel Surcharge', + serviceName: 'International origin SIT fuel surcharge', shipmentType: 'HHG', }, { marketCode: 'i', serviceCode: 'IDSFSC', - serviceName: 'International Destination SIT Fuel Surcharge', + serviceName: 'International destination SIT fuel surcharge', shipmentType: 'HHG', }, { marketCode: 'i', serviceCode: 'IOSFSC', - serviceName: 'International Origin SIT Fuel Surcharge', + serviceName: 'International origin SIT fuel surcharge', shipmentType: 'UNACCOMPANIED_BAGGAGE', }, { marketCode: 'i', serviceCode: 'IDSFSC', - serviceName: 'International Destination SIT Fuel Surcharge', + serviceName: 'International destination SIT fuel furcharge', shipmentType: 'UNACCOMPANIED_BAGGAGE', }, ]; @@ -371,7 +371,7 @@ describe('Shipment Service Items Table', () => { describe('renders the intl UB shipment type (CONUS -> OCONUS) with service items', () => { it.each([ ['International UB price'], - ['International POE Fuel Surcharge'], + ['International POE fuel surcharge'], ['International UB pack'], ['International UB unpack'], ])('expects %s to be in the document', async (serviceItem) => { @@ -386,7 +386,7 @@ describe('Shipment Service Items Table', () => { describe('renders the intl UB shipment type (OCONUS -> CONUS) with service items', () => { it.each([ ['International UB price'], - ['International POD Fuel Surcharge'], + ['International POD fuel surcharge'], ['International UB pack'], ['International UB unpack'], ])('expects %s to be in the document', async (serviceItem) => { diff --git a/src/components/PrimeUI/CreateShipmentServiceItemForm/CreateShipmentServiceItemForm.jsx b/src/components/PrimeUI/CreateShipmentServiceItemForm/CreateShipmentServiceItemForm.jsx index b351755d15b..d1b44bfe85b 100644 --- a/src/components/PrimeUI/CreateShipmentServiceItemForm/CreateShipmentServiceItemForm.jsx +++ b/src/components/PrimeUI/CreateShipmentServiceItemForm/CreateShipmentServiceItemForm.jsx @@ -5,6 +5,8 @@ import PropTypes from 'prop-types'; import styles from './CreateShipmentServiceItemForm.module.scss'; import DestinationSITServiceItemForm from './DestinationSITServiceItemForm'; import OriginSITServiceItemForm from './OriginSITServiceItemForm'; +import InternationalDestinationSITServiceItemForm from './InternationalDestinationSITServiceItemForm'; +import InternationalOriginSITServiceItemForm from './InternationalOriginSITServiceItemForm'; import ShuttleSITServiceItemForm from './ShuttleSITServiceItemForm'; import DomesticCratingForm from './DomesticCratingForm'; import InternationalCratingForm from './InternationalCratingForm'; @@ -20,7 +22,9 @@ const CreateShipmentServiceItemForm = ({ shipment, createServiceItemMutation }) const { MTOServiceItemOriginSIT, MTOServiceItemDestSIT, - MTOServiceItemShuttle, + MTOServiceItemInternationalOriginSIT, + MTOServiceItemInternationalDestSIT, + MTOServiceItemDomesticShuttle, MTOServiceItemDomesticCrating, MTOServiceItemInternationalCrating, MTOServiceItemInternationalShuttle, @@ -49,7 +53,9 @@ const CreateShipmentServiceItemForm = ({ shipment, createServiceItemMutation }) <> - + + + {enableAlaskaFeatureFlag && } @@ -61,7 +67,15 @@ const CreateShipmentServiceItemForm = ({ shipment, createServiceItemMutation }) {selectedServiceItemType === MTOServiceItemDestSIT && ( )} - {selectedServiceItemType === MTOServiceItemShuttle && ( + + {selectedServiceItemType === MTOServiceItemInternationalOriginSIT && ( + + )} + {selectedServiceItemType === MTOServiceItemInternationalDestSIT && ( + + )} + + {selectedServiceItemType === MTOServiceItemDomesticShuttle && ( )} {selectedServiceItemType === MTOServiceItemInternationalShuttle && ( diff --git a/src/components/PrimeUI/CreateShipmentServiceItemForm/CreateShipmentServiceItemForm.test.jsx b/src/components/PrimeUI/CreateShipmentServiceItemForm/CreateShipmentServiceItemForm.test.jsx index f30c0b9f459..3efc032ed9e 100644 --- a/src/components/PrimeUI/CreateShipmentServiceItemForm/CreateShipmentServiceItemForm.test.jsx +++ b/src/components/PrimeUI/CreateShipmentServiceItemForm/CreateShipmentServiceItemForm.test.jsx @@ -88,7 +88,9 @@ describe('CreateShipmentServiceItemForm component', () => { it.each([ ['originSITServiceItemForm', createServiceItemModelTypes.MTOServiceItemOriginSIT], ['destinationSITServiceItemForm', createServiceItemModelTypes.MTOServiceItemDestSIT], - ['shuttleSITServiceItemForm', createServiceItemModelTypes.MTOServiceItemShuttle], + ['internationalOriginSITServiceItemForm', createServiceItemModelTypes.MTOServiceItemInternationalOriginSIT], + ['internationalDestinationSITServiceItemForm', createServiceItemModelTypes.MTOServiceItemInternationalDestSIT], + ['shuttleSITServiceItemForm', createServiceItemModelTypes.MTOServiceItemDomesticShuttle], ['DomesticCratingForm', createServiceItemModelTypes.MTOServiceItemDomesticCrating], ['InternationalCratingForm', createServiceItemModelTypes.MTOServiceItemInternationalCrating], ['InternationalShuttleServiceItemForm', createServiceItemModelTypes.MTOServiceItemInternationalShuttle], diff --git a/src/components/PrimeUI/CreateShipmentServiceItemForm/InternationalDestinationSITServiceItemForm.jsx b/src/components/PrimeUI/CreateShipmentServiceItemForm/InternationalDestinationSITServiceItemForm.jsx new file mode 100644 index 00000000000..0a4c4ffeb70 --- /dev/null +++ b/src/components/PrimeUI/CreateShipmentServiceItemForm/InternationalDestinationSITServiceItemForm.jsx @@ -0,0 +1,133 @@ +import * as Yup from 'yup'; +import { Formik } from 'formik'; +import { Button } from '@trussworks/react-uswds'; +import React from 'react'; +import PropTypes from 'prop-types'; + +import { Form } from 'components/form/Form'; +import MaskedTextField from 'components/form/fields/MaskedTextField/MaskedTextField'; +import { formatDateForSwagger } from 'shared/dates'; +import { formatAddressForPrimeAPI } from 'utils/formatters'; +import { DatePickerInput } from 'components/form/fields'; +import { ShipmentShape } from 'types/shipment'; +import TextField from 'components/form/fields/TextField/TextField'; +import Hint from 'components/Hint'; + +const destinationSITValidationSchema = Yup.object().shape({ + reason: Yup.string().required('Required'), + firstAvailableDeliveryDate1: Yup.date().typeError('Enter a complete date in DD MMM YYYY format (day, month, year).'), + timeMilitary1: Yup.string().matches(/^(\d{4}Z)$/, 'Must be a valid military time (e.g. 1400Z)'), + firstAvailableDeliveryDate2: Yup.date().typeError('Enter a complete date in DD MMM YYYY format (day, month, year).'), + timeMilitary2: Yup.string().matches(/^(\d{4}Z)$/, 'Must be a valid military time (e.g. 1400Z)'), + sitEntryDate: Yup.date() + .typeError('Enter a complete date in DD MMM YYYY format (day, month, year).') + .required('Required'), + sitDepartureDate: Yup.date().typeError('Enter a complete date in DD MMM YYYY format (day, month, year).'), +}); + +const InternationalDestinationSITServiceItemForm = ({ shipment, submission }) => { + const initialValues = { + moveTaskOrderID: shipment.moveTaskOrderID, + mtoShipmentID: shipment.id, + modelType: 'MTOServiceItemInternationalDestSIT', + reServiceCode: 'IDFSIT', + reason: '', + firstAvailableDeliveryDate1: '', + dateOfContact1: '', + timeMilitary1: '', + firstAvailableDeliveryDate2: '', + dateOfContact2: '', + timeMilitary2: '', + sitEntryDate: '', + sitDepartureDate: '', + sitDestinationFinalAddress: { streetAddress1: '', streetAddress2: '', city: '', state: '', postalCode: '' }, + }; + + const onSubmit = (values) => { + const { + firstAvailableDeliveryDate1, + firstAvailableDeliveryDate2, + sitEntryDate, + sitDepartureDate, + sitDestinationFinalAddress, + timeMilitary1, + timeMilitary2, + dateOfContact1, + dateOfContact2, + ...serviceItemValues + } = values; + const body = { + firstAvailableDeliveryDate1: formatDateForSwagger(firstAvailableDeliveryDate1), + firstAvailableDeliveryDate2: formatDateForSwagger(firstAvailableDeliveryDate2), + dateOfContact1: formatDateForSwagger(dateOfContact1), + dateOfContact2: formatDateForSwagger(dateOfContact2), + sitEntryDate: formatDateForSwagger(sitEntryDate), + sitDepartureDate: sitDepartureDate ? formatDateForSwagger(sitDepartureDate) : null, + sitDestinationFinalAddress: sitDestinationFinalAddress.streetAddress1 + ? formatAddressForPrimeAPI(sitDestinationFinalAddress) + : null, + timeMilitary1: timeMilitary1 || null, + timeMilitary2: timeMilitary2 || null, + ...serviceItemValues, + }; + submission({ body }); + }; + + return ( + +
+ + + + + + + + + + + + + + + The following service items will be created:
+ IDFSIT (Destination 1st day SIT)
+ IDASIT (Destination additional days SIT)
+ IDDSIT (Destination SIT delivery)
+ IDSFSC (Destination SIT fuel surcharge)
+
+ NOTE: The above service items will use the current delivery address of the shipment as their + final delivery address. Ensure the shipment address is accurate before creating these service items. +
+ + +
+ ); +}; + +InternationalDestinationSITServiceItemForm.propTypes = { + shipment: ShipmentShape.isRequired, + submission: PropTypes.func.isRequired, +}; + +export default InternationalDestinationSITServiceItemForm; diff --git a/src/components/PrimeUI/CreateShipmentServiceItemForm/InternationalDestinationSITServiceItemForm.test.jsx b/src/components/PrimeUI/CreateShipmentServiceItemForm/InternationalDestinationSITServiceItemForm.test.jsx new file mode 100644 index 00000000000..f4cce2860c3 --- /dev/null +++ b/src/components/PrimeUI/CreateShipmentServiceItemForm/InternationalDestinationSITServiceItemForm.test.jsx @@ -0,0 +1,156 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import InternationalDestinationSITServiceItemForm from './InternationalDestinationSITServiceItemForm'; + +const approvedMoveTaskOrder = { + moveTaskOrder: { + id: '9c7b255c-2981-4bf8-839f-61c7458e2b4d', + moveCode: 'LR4T8V', + mtoShipments: [ + { + actualPickupDate: '2020-03-17', + agents: [], + approvedDate: '2021-10-20', + createdAt: '2021-10-21', + customerRemarks: 'Please treat gently', + destinationAddress: { + city: 'Fairfield', + id: 'bfe61147-5fd7-426e-b473-54ccf77bde35', + postalCode: '94535', + state: 'CA', + streetAddress1: '987 Any Avenue', + streetAddress2: 'P.O. Box 9876', + streetAddress3: 'c/o Some Person', + }, + eTag: 'MjAyMS0xMC0xOFQxODoyNDo0MS4zNzc5Nzha', + firstAvailableDeliveryDate: null, + id: 'ce01a5b8-9b44-4511-8a8d-edb60f2a4aee', + moveTaskOrderID: '9c7b255c-2981-4bf8-839f-61c7458e2b4d', + pickupAddress: { + city: 'Beverly Hills', + id: 'cf159eca-162c-4131-84a0-795e684416a6', + postalCode: '90210', + state: 'CA', + streetAddress1: '123 Any Street', + streetAddress2: 'P.O. Box 12345', + streetAddress3: 'c/o Some Person', + }, + primeActualWeight: 2000, + primeEstimatedWeight: 1400, + primeEstimatedWeightRecordedDate: null, + requestedPickupDate: '2020-03-15', + requiredDeliveryDate: null, + scheduledPickupDate: '2020-03-16', + secondaryDeliveryAddress: { + city: null, + postalCode: null, + state: null, + streetAddress1: null, + }, + shipmentType: 'HHG', + status: 'APPROVED', + updatedAt: '2021-10-22', + mtoServiceItems: null, + reweigh: { + id: '1234', + weight: 9000, + requestedAt: '2021-10-23', + }, + }, + ], + }, +}; + +describe('InternationalDestinationSITServiceItemForm component', () => { + it.each([ + ['Reason', 'reason'], + ['First available delivery date', 'firstAvailableDeliveryDate1'], + ['First date of attempted contact', 'dateOfContact1'], + ['First time of attempted contact', 'timeMilitary1'], + ['Second available delivery date', 'firstAvailableDeliveryDate2'], + ['Second date of attempted contact', 'dateOfContact2'], + ['Second time of attempted contact', 'timeMilitary2'], + ['SIT entry date', 'sitEntryDate'], + ['SIT departure date', 'sitDepartureDate'], + ])('renders field %s in form', (labelName) => { + const shipment = approvedMoveTaskOrder.moveTaskOrder.mtoShipments[0]; + + render(); + + const field = screen.getByText(labelName); + expect(field).toBeInTheDocument(); + }); + + it('renders hint component at bottom of page - international', async () => { + const shipment = approvedMoveTaskOrder.moveTaskOrder.mtoShipments[0]; + + render( + , + ); + + const hintInfo = screen.getByTestId('destinationSitInfo'); + expect(hintInfo).toBeInTheDocument(); + + expect(hintInfo).toHaveTextContent('The following service items will be created:'); + expect(hintInfo).toHaveTextContent('IDFSIT (Destination 1st day SIT)'); + expect(hintInfo).toHaveTextContent('IDASIT (Destination additional days SIT)'); + expect(hintInfo).toHaveTextContent('IDDSIT (Destination SIT delivery)'); + expect(hintInfo).toHaveTextContent('IDSFSC (Destination SIT fuel surcharge)'); + expect(hintInfo).toHaveTextContent( + 'NOTE: The above service items will use the current delivery address of the shipment as their final delivery address. Ensure the shipment address is accurate before creating these service items.', + ); + }); + + it('renders the Create Service Item button', async () => { + const shipment = approvedMoveTaskOrder.moveTaskOrder.mtoShipments[0]; + + render(); + + // Check if the button renders + const createBtn = screen.getByRole('button', { name: 'Create service item' }); + expect(createBtn).toBeInTheDocument(); + }); + + it('submits values when create service item button is clicked for international destination SIT', async () => { + const shipment = approvedMoveTaskOrder.moveTaskOrder.mtoShipments[0]; + const submissionMock = jest.fn(); + + render( + , + ); + + await userEvent.type(screen.getByLabelText('Reason'), 'Testing'); + await userEvent.type(screen.getByLabelText('First available delivery date'), '01 Feb 2024'); + await userEvent.type(screen.getByLabelText('First date of attempted contact'), '28 Dec 2023'); + await userEvent.type(screen.getByLabelText('First time of attempted contact'), '1400Z'); + await userEvent.type(screen.getByLabelText('Second available delivery date'), '05 Feb 2024'); + await userEvent.type(screen.getByLabelText('Second date of attempted contact'), '05 Jan 2024'); + await userEvent.type(screen.getByLabelText('Second time of attempted contact'), '1400Z'); + await userEvent.type(screen.getByLabelText('SIT entry date'), '10 Jan 2024'); + await userEvent.type(screen.getByLabelText('SIT departure date'), '24 Jan 2024'); + + // Submit form + await userEvent.click(screen.getByRole('button', { name: 'Create service item' })); + expect(submissionMock).toHaveBeenCalledTimes(1); + expect(submissionMock).toHaveBeenCalledWith({ + body: { + reason: 'Testing', + dateOfContact1: '2023-12-28', + dateOfContact2: '2024-01-05', + firstAvailableDeliveryDate1: '2024-02-01', + firstAvailableDeliveryDate2: '2024-02-05', + modelType: 'MTOServiceItemInternationalDestSIT', + moveTaskOrderID: '9c7b255c-2981-4bf8-839f-61c7458e2b4d', + mtoShipmentID: 'ce01a5b8-9b44-4511-8a8d-edb60f2a4aee', + reServiceCode: 'IDFSIT', + sitDepartureDate: '2024-01-24', + sitDestinationFinalAddress: null, + sitEntryDate: '2024-01-10', + timeMilitary1: '1400Z', + timeMilitary2: '1400Z', + }, + }); + }); +}); diff --git a/src/components/PrimeUI/CreateShipmentServiceItemForm/InternationalOriginSITServiceItemForm.jsx b/src/components/PrimeUI/CreateShipmentServiceItemForm/InternationalOriginSITServiceItemForm.jsx new file mode 100644 index 00000000000..76db6a191d6 --- /dev/null +++ b/src/components/PrimeUI/CreateShipmentServiceItemForm/InternationalOriginSITServiceItemForm.jsx @@ -0,0 +1,110 @@ +import * as Yup from 'yup'; +import { Formik } from 'formik'; +import { Button } from '@trussworks/react-uswds'; +import React from 'react'; +import { useNavigate, useParams, generatePath } from 'react-router-dom'; +import PropTypes from 'prop-types'; + +import { requiredAddressSchema, ZIP_CODE_REGEX } from 'utils/validation'; +import { formatDateForSwagger } from 'shared/dates'; +import { formatAddressForPrimeAPI } from 'utils/formatters'; +import { Form } from 'components/form/Form'; +import TextField from 'components/form/fields/TextField/TextField'; +import MaskedTextField from 'components/form/fields/MaskedTextField/MaskedTextField'; +import { DatePickerInput } from 'components/form/fields'; +import { AddressFields } from 'components/form/AddressFields/AddressFields'; +import { ShipmentShape } from 'types/shipment'; +import { primeSimulatorRoutes } from 'constants/routes'; + +const originSITValidationSchema = Yup.object().shape({ + reason: Yup.string().required('Required'), + sitPostalCode: Yup.string().matches(ZIP_CODE_REGEX, 'Must be valid zip code').required('Required'), + sitEntryDate: Yup.date() + .typeError('Enter a complete date in DD MMM YYYY format (day, month, year).') + .required('Required'), + sitDepartureDate: Yup.date().typeError('Enter a complete date in DD MMM YYYY format (day, month, year).'), + sitHHGActualOrigin: requiredAddressSchema, +}); + +const InternationalOriginSITServiceItemForm = ({ shipment, submission }) => { + const initialValues = { + moveTaskOrderID: shipment.moveTaskOrderID, + mtoShipmentID: shipment.id, + modelType: 'MTOServiceItemInternationalOriginSIT', + reServiceCode: 'IOFSIT', + reason: '', + sitPostalCode: '', + sitEntryDate: '', + sitDepartureDate: '', // The Prime API is currently ignoring origin SIT departure date on creation + sitHHGActualOrigin: { + streetAddress1: '', + streetAddress2: '', + streetAddress3: '', + city: '', + state: '', + postalCode: '', + county: '', + }, + }; + + const onSubmit = (values) => { + const { sitEntryDate, sitDepartureDate, sitHHGActualOrigin, ...serviceItemValues } = values; + const body = { + sitEntryDate: formatDateForSwagger(sitEntryDate), + sitDepartureDate: sitDepartureDate ? formatDateForSwagger(sitDepartureDate) : null, + sitHHGActualOrigin: sitHHGActualOrigin.streetAddress1 ? formatAddressForPrimeAPI(sitHHGActualOrigin) : null, + ...serviceItemValues, + }; + submission({ body }); + }; + + const { moveCodeOrID } = useParams(); + const navigate = useNavigate(); + const handleCancel = () => { + navigate(generatePath(primeSimulatorRoutes.VIEW_MOVE_PATH, { moveCodeOrID })); + }; + + return ( + + {({ isValid, isSubmitting, handleSubmit, ...formikProps }) => { + return ( +
+ + + + + + + + + + + + + ); + }} +
+ ); +}; + +InternationalOriginSITServiceItemForm.propTypes = { + shipment: ShipmentShape.isRequired, + submission: PropTypes.func.isRequired, +}; + +export default InternationalOriginSITServiceItemForm; diff --git a/src/components/PrimeUI/CreateShipmentServiceItemForm/InternationalOriginSITServiceItemForm.test.jsx b/src/components/PrimeUI/CreateShipmentServiceItemForm/InternationalOriginSITServiceItemForm.test.jsx new file mode 100644 index 00000000000..f7445e10751 --- /dev/null +++ b/src/components/PrimeUI/CreateShipmentServiceItemForm/InternationalOriginSITServiceItemForm.test.jsx @@ -0,0 +1,96 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; + +import InternationalOriginSITServiceItemForm from './InternationalOriginSITServiceItemForm'; + +import { MockProviders } from 'testUtils'; + +const approvedMoveTaskOrder = { + moveTaskOrder: { + id: '9c7b255c-2981-4bf8-839f-61c7458e2b4d', + moveCode: 'LR4T8V', + mtoShipments: [ + { + actualPickupDate: '2020-03-17', + agents: [], + approvedDate: '2021-10-20', + createdAt: '2021-10-21', + customerRemarks: 'Please treat gently', + destinationAddress: { + city: 'Fairfield', + id: 'bfe61147-5fd7-426e-b473-54ccf77bde35', + postalCode: '94535', + state: 'CA', + streetAddress1: '987 Any Avenue', + streetAddress2: 'P.O. Box 9876', + streetAddress3: 'c/o Some Person', + }, + eTag: 'MjAyMS0xMC0xOFQxODoyNDo0MS4zNzc5Nzha', + firstAvailableDeliveryDate: null, + id: 'ce01a5b8-9b44-4511-8a8d-edb60f2a4aee', + moveTaskOrderID: '9c7b255c-2981-4bf8-839f-61c7458e2b4d', + pickupAddress: { + city: 'Beverly Hills', + id: 'cf159eca-162c-4131-84a0-795e684416a6', + postalCode: '90210', + state: 'CA', + streetAddress1: '123 Any Street', + streetAddress2: 'P.O. Box 12345', + streetAddress3: 'c/o Some Person', + }, + primeActualWeight: 2000, + primeEstimatedWeight: 1400, + primeEstimatedWeightRecordedDate: null, + requestedPickupDate: '2020-03-15', + requiredDeliveryDate: null, + scheduledPickupDate: '2020-03-16', + secondaryDeliveryAddress: { + city: null, + postalCode: null, + state: null, + streetAddress1: null, + }, + shipmentType: 'HHG', + status: 'APPROVED', + updatedAt: '2021-10-22', + mtoServiceItems: null, + reweigh: { + id: '1234', + weight: 9000, + requestedAt: '2021-10-23', + }, + }, + ], + }, +}; + +const renderWithProviders = (component) => { + render({component}); +}; + +describe('InternationalOriginSITServiceItemForm component', () => { + it.each([ + ['Reason', 'reason'], + ['SIT postal code', 'sitPostalCode'], + ['SIT entry Date', 'sitEntryDate'], + ['SIT departure Date', 'sitDepartureDate'], + ['SIT HHG actual origin', 'sitHHGActualOrigin'], + ])('renders field %s in form', (labelName) => { + const shipment = approvedMoveTaskOrder.moveTaskOrder.mtoShipments[0]; + + renderWithProviders(); + + const field = screen.getByText(labelName); + expect(field).toBeInTheDocument(); + }); + + it('renders the Create Service Item button', async () => { + const shipment = approvedMoveTaskOrder.moveTaskOrder.mtoShipments[0]; + + renderWithProviders(); + + // Check if the button renders + const createBtn = screen.getByRole('button', { name: 'Create service item' }); + expect(createBtn).toBeInTheDocument(); + }); +}); diff --git a/src/components/PrimeUI/CreateShipmentServiceItemForm/OriginSITServiceItemForm.test.jsx b/src/components/PrimeUI/CreateShipmentServiceItemForm/OriginSITServiceItemForm.test.jsx new file mode 100644 index 00000000000..5b261199a02 --- /dev/null +++ b/src/components/PrimeUI/CreateShipmentServiceItemForm/OriginSITServiceItemForm.test.jsx @@ -0,0 +1,96 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; + +import OriginSITServiceItemForm from './OriginSITServiceItemForm'; + +import { MockProviders } from 'testUtils'; + +const approvedMoveTaskOrder = { + moveTaskOrder: { + id: '9c7b255c-2981-4bf8-839f-61c7458e2b4d', + moveCode: 'LR4T8V', + mtoShipments: [ + { + actualPickupDate: '2020-03-17', + agents: [], + approvedDate: '2021-10-20', + createdAt: '2021-10-21', + customerRemarks: 'Please treat gently', + destinationAddress: { + city: 'Fairfield', + id: 'bfe61147-5fd7-426e-b473-54ccf77bde35', + postalCode: '94535', + state: 'CA', + streetAddress1: '987 Any Avenue', + streetAddress2: 'P.O. Box 9876', + streetAddress3: 'c/o Some Person', + }, + eTag: 'MjAyMS0xMC0xOFQxODoyNDo0MS4zNzc5Nzha', + firstAvailableDeliveryDate: null, + id: 'ce01a5b8-9b44-4511-8a8d-edb60f2a4aee', + moveTaskOrderID: '9c7b255c-2981-4bf8-839f-61c7458e2b4d', + pickupAddress: { + city: 'Beverly Hills', + id: 'cf159eca-162c-4131-84a0-795e684416a6', + postalCode: '90210', + state: 'CA', + streetAddress1: '123 Any Street', + streetAddress2: 'P.O. Box 12345', + streetAddress3: 'c/o Some Person', + }, + primeActualWeight: 2000, + primeEstimatedWeight: 1400, + primeEstimatedWeightRecordedDate: null, + requestedPickupDate: '2020-03-15', + requiredDeliveryDate: null, + scheduledPickupDate: '2020-03-16', + secondaryDeliveryAddress: { + city: null, + postalCode: null, + state: null, + streetAddress1: null, + }, + shipmentType: 'HHG', + status: 'APPROVED', + updatedAt: '2021-10-22', + mtoServiceItems: null, + reweigh: { + id: '1234', + weight: 9000, + requestedAt: '2021-10-23', + }, + }, + ], + }, +}; + +const renderWithProviders = (component) => { + render({component}); +}; + +describe('OriginSITServiceItemForm component', () => { + it.each([ + ['Reason', 'reason'], + ['SIT postal code', 'sitPostalCode'], + ['SIT entry Date', 'sitEntryDate'], + ['SIT departure Date', 'sitDepartureDate'], + ['SIT HHG actual origin', 'sitHHGActualOrigin'], + ])('renders field %s in form', (labelName) => { + const shipment = approvedMoveTaskOrder.moveTaskOrder.mtoShipments[0]; + + renderWithProviders(); + + const field = screen.getByText(labelName); + expect(field).toBeInTheDocument(); + }); + + it('renders the Create Service Item button', async () => { + const shipment = approvedMoveTaskOrder.moveTaskOrder.mtoShipments[0]; + + renderWithProviders(); + + // Check if the button renders + const createBtn = screen.getByRole('button', { name: 'Create service item' }); + expect(createBtn).toBeInTheDocument(); + }); +}); diff --git a/src/components/PrimeUI/CreateShipmentServiceItemForm/ShuttleSITServiceItemForm.jsx b/src/components/PrimeUI/CreateShipmentServiceItemForm/ShuttleSITServiceItemForm.jsx index 0bb6d6a3747..97dd8e8f128 100644 --- a/src/components/PrimeUI/CreateShipmentServiceItemForm/ShuttleSITServiceItemForm.jsx +++ b/src/components/PrimeUI/CreateShipmentServiceItemForm/ShuttleSITServiceItemForm.jsx @@ -9,7 +9,7 @@ import MaskedTextField from 'components/form/fields/MaskedTextField/MaskedTextFi import { Form } from 'components/form/Form'; import { ShipmentShape } from 'types/shipment'; import { DropdownInput } from 'components/form/fields'; -import { shuttleServiceItemCodeOptions, createServiceItemModelTypes } from 'constants/prime'; +import { domesticShuttleServiceItemCodeOptions, createServiceItemModelTypes } from 'constants/prime'; const shuttleSITValidationSchema = Yup.object().shape({ reServiceCode: Yup.string().required('Required'), @@ -20,7 +20,7 @@ const ShuttleSITServiceItemForm = ({ shipment, submission }) => { const initialValues = { moveTaskOrderID: shipment.moveTaskOrderID, mtoShipmentID: shipment.id, - modelType: createServiceItemModelTypes.MTOServiceItemShuttle, + modelType: createServiceItemModelTypes.MTOServiceItemDomesticShuttle, reason: '', estimatedWeight: null, actualWeight: null, @@ -44,7 +44,7 @@ const ShuttleSITServiceItemForm = ({ shipment, submission }) => { name="reServiceCode" id="reServiceCode" required - options={shuttleServiceItemCodeOptions} + options={domesticShuttleServiceItemCodeOptions} /> { const [isPageReload, setIsPageReload] = useState(true); useEffect(() => { @@ -205,7 +206,6 @@ const TableQueue = ({ if (isLoading || (title === 'Move history' && data.length <= 0 && !isError)) return ; if (isError) return ; - const isDateFilterValue = (value) => { return !Number.isNaN(Date.parse(value)); }; @@ -321,14 +321,18 @@ const TableQueue = ({
{isBulkAssignModalVisible && ( - + )}

{`${title} (${totalCount})`}

{isSupervisor && isBulkAssignmentFFEnabled && ( - )} diff --git a/src/components/Table/TableQueue.module.scss b/src/components/Table/TableQueue.module.scss index 0da067b63ac..47a1912d639 100644 --- a/src/components/Table/TableQueue.module.scss +++ b/src/components/Table/TableQueue.module.scss @@ -38,6 +38,11 @@ cursor: pointer; } + .bulkModal { + padding: 10.787px; + cursor: pointer; + } + .tableContainer { flex: auto; @include u-margin-top(1); diff --git a/src/constants/MoveHistory/Database/FieldMappings.js b/src/constants/MoveHistory/Database/FieldMappings.js index aa958199fa7..53724afcd8a 100644 --- a/src/constants/MoveHistory/Database/FieldMappings.js +++ b/src/constants/MoveHistory/Database/FieldMappings.js @@ -151,9 +151,11 @@ export default { approved_at: 'Approved at', counseling_office_name: 'Counseling office', assigned_sc: 'Counselor assigned', + assigned_sc_ppm: 'Closeout counselor assigned', assigned_too: 'Task ordering officer assigned', assigned_tio: 'Task invoicing officer assigned', re_assigned_sc: 'Counselor reassigned', + re_assigned_sc_ppm: 'Closeout counselor reassigned', re_assigned_too: 'Task ordering officer reassigned', re_assigned_tio: 'Task invoicing officer reassigned', available_to_prime_at: 'Available to Prime at', diff --git a/src/constants/MoveHistory/Database/Tables.js b/src/constants/MoveHistory/Database/Tables.js index 361c40f11c9..921febc1464 100644 --- a/src/constants/MoveHistory/Database/Tables.js +++ b/src/constants/MoveHistory/Database/Tables.js @@ -19,6 +19,6 @@ export default { moving_expenses: 'moving_expenses', progear_weight_tickets: 'progear_weight_tickets', gsr_appeals: 'gsr_appeals', - shipment_address_updates: 'shipment_address_updates', payment_service_items: 'payment_service_items', + shipment_address_updates: 'shipment_address_updates', }; diff --git a/src/constants/MoveHistory/EventTemplates/UpdateAssignedOfficeUser/DeleteAssignedOfficeUser.jsx b/src/constants/MoveHistory/EventTemplates/UpdateAssignedOfficeUser/DeleteAssignedOfficeUser.jsx index e721b67abd0..000e6ec56f7 100644 --- a/src/constants/MoveHistory/EventTemplates/UpdateAssignedOfficeUser/DeleteAssignedOfficeUser.jsx +++ b/src/constants/MoveHistory/EventTemplates/UpdateAssignedOfficeUser/DeleteAssignedOfficeUser.jsx @@ -3,14 +3,18 @@ import React from 'react'; import o from 'constants/MoveHistory/UIDisplay/Operations'; import a from 'constants/MoveHistory/Database/Actions'; import t from 'constants/MoveHistory/Database/Tables'; +import { MOVE_STATUSES } from 'shared/constants'; export default { action: a.UPDATE, eventName: o.deleteAssignedOfficeUser, tableName: t.moves, getEventNameDisplay: () => 'Updated move', - getDetails: ({ changedValues }) => { - if (changedValues.sc_assigned_id === null) return <>Counselor unassigned; + getDetails: ({ changedValues, oldValues }) => { + if (changedValues.sc_assigned_id === null && oldValues?.status === MOVE_STATUSES.NEEDS_SERVICE_COUNSELING) + return <>Counselor unassigned; + if (changedValues.sc_assigned_id === null && oldValues?.status !== MOVE_STATUSES.NEEDS_SERVICE_COUNSELING) + return <>Closeout counselor unassigned; if (changedValues.too_assigned_id === null) return <>Task ordering officer unassigned; if (changedValues.tio_assigned_id === null) return <>Task invoicing officer unassigned; return <>Unassigned; diff --git a/src/constants/MoveHistory/EventTemplates/UpdateAssignedOfficeUser/DeleteAssignedOfficeUser.test.jsx b/src/constants/MoveHistory/EventTemplates/UpdateAssignedOfficeUser/DeleteAssignedOfficeUser.test.jsx index cf02b1f8fb5..5c9a613271b 100644 --- a/src/constants/MoveHistory/EventTemplates/UpdateAssignedOfficeUser/DeleteAssignedOfficeUser.test.jsx +++ b/src/constants/MoveHistory/EventTemplates/UpdateAssignedOfficeUser/DeleteAssignedOfficeUser.test.jsx @@ -2,6 +2,7 @@ import { screen, render } from '@testing-library/react'; import e from 'constants/MoveHistory/EventTemplates/UpdateAssignedOfficeUser/DeleteAssignedOfficeUser'; import getTemplate from 'constants/MoveHistory/TemplateManager'; +import { MOVE_STATUSES } from 'shared/constants'; describe('When given a move that has been unassigned', () => { const historyRecord = { @@ -26,8 +27,15 @@ describe('When given a move that has been unassigned', () => { }); describe('displays the proper details for', () => { + it('closeout counselor', () => { + const template = getTemplate(historyRecord); + + render(template.getDetails(historyRecord)); + expect(screen.getByText('Closeout counselor unassigned')).toBeInTheDocument(); + }); it('services counselor', () => { const template = getTemplate(historyRecord); + historyRecord.oldValues = { status: MOVE_STATUSES.NEEDS_SERVICE_COUNSELING }; render(template.getDetails(historyRecord)); expect(screen.getByText('Counselor unassigned')).toBeInTheDocument(); diff --git a/src/constants/MoveHistory/EventTemplates/UpdateAssignedOfficeUser/UpdateAssignedOfficeUser.test.jsx b/src/constants/MoveHistory/EventTemplates/UpdateAssignedOfficeUser/UpdateAssignedOfficeUser.test.jsx index bc1e6aa2d73..f1223b4b3a5 100644 --- a/src/constants/MoveHistory/EventTemplates/UpdateAssignedOfficeUser/UpdateAssignedOfficeUser.test.jsx +++ b/src/constants/MoveHistory/EventTemplates/UpdateAssignedOfficeUser/UpdateAssignedOfficeUser.test.jsx @@ -2,6 +2,7 @@ import { screen, render } from '@testing-library/react'; import e from 'constants/MoveHistory/EventTemplates/UpdateAssignedOfficeUser/UpdateAssignedOfficeUser'; import getTemplate from 'constants/MoveHistory/TemplateManager'; +import { MOVE_STATUSES } from 'shared/constants'; describe('When given a move that has been assigned', () => { const historyRecord = { @@ -13,6 +14,7 @@ describe('When given a move that has been assigned', () => { }, oldValues: { sc_assigned_id: null, + status: MOVE_STATUSES.NEEDS_SERVICE_COUNSELING, }, context: [{ assigned_office_user_last_name: 'Daniels', assigned_office_user_first_name: 'Jayden' }], }; @@ -30,14 +32,47 @@ describe('When given a move that has been assigned', () => { }); describe('displays the proper details for', () => { - it('services counselor', () => { + it('assignment of a services counselor', () => { const template = getTemplate(historyRecord); render(template.getDetails(historyRecord)); expect(screen.getByText('Counselor assigned')).toBeInTheDocument(); expect(screen.getByText(': Daniels, Jayden')).toBeInTheDocument(); }); - it('task ordering officer', () => { + it('reassignment of a services counselor', () => { + const template = getTemplate(historyRecord); + historyRecord.oldValues = { + sc_assigned_id: '759a87ad-dc75-4b34-b551-d31309a79f64', + status: MOVE_STATUSES.NEEDS_SERVICE_COUNSELING, + }; + + render(template.getDetails(historyRecord)); + expect(screen.getByText('Counselor reassigned')).toBeInTheDocument(); + expect(screen.getByText(': Daniels, Jayden')).toBeInTheDocument(); + }); + it('assignment of a closeout counselor', () => { + const template = getTemplate(historyRecord); + historyRecord.oldValues = { + sc_assigned_id: null, + status: MOVE_STATUSES.SERVICE_COUNSELING_COMPLETED, + }; + + render(template.getDetails(historyRecord)); + expect(screen.getByText('Closeout counselor assigned')).toBeInTheDocument(); + expect(screen.getByText(': Daniels, Jayden')).toBeInTheDocument(); + }); + it('reassignment of a closeout counselor', () => { + const template = getTemplate(historyRecord); + historyRecord.oldValues = { + sc_assigned_id: '759a87ad-dc75-4b34-b551-d31309a79f64', + status: MOVE_STATUSES.SERVICE_COUNSELING_COMPLETED, + }; + + render(template.getDetails(historyRecord)); + expect(screen.getByText('Closeout counselor reassigned')).toBeInTheDocument(); + expect(screen.getByText(': Daniels, Jayden')).toBeInTheDocument(); + }); + it('assignment of a task ordering officer', () => { historyRecord.changedValues = { too_assigned_id: 'fb625e3c-067c-49d7-8fd9-88ef040e6137' }; historyRecord.oldValues = { too_assigned_id: null }; historyRecord.context = [ @@ -50,7 +85,20 @@ describe('When given a move that has been assigned', () => { expect(screen.getByText('Task ordering officer assigned')).toBeInTheDocument(); expect(screen.getByText(': Robinson, Brian')).toBeInTheDocument(); }); - it('task invoicing officer', () => { + it('reassignment of a task ordering officer', () => { + historyRecord.changedValues = { too_assigned_id: 'fb625e3c-067c-49d7-8fd9-88ef040e6137' }; + historyRecord.oldValues = { too_assigned_id: '759a87ad-dc75-4b34-b551-d31309a79f64' }; + historyRecord.context = [ + { assigned_office_user_last_name: 'Robinson', assigned_office_user_first_name: 'Brian' }, + ]; + + const template = getTemplate(historyRecord); + + render(template.getDetails(historyRecord)); + expect(screen.getByText('Task ordering officer reassigned')).toBeInTheDocument(); + expect(screen.getByText(': Robinson, Brian')).toBeInTheDocument(); + }); + it('assignment of a task invoicing officer', () => { historyRecord.changedValues = { tio_assigned_id: 'fb625e3c-067c-49d7-8fd9-88ef040e6137' }; historyRecord.oldValues = { tio_assigned_id: null }; historyRecord.context = [{ assigned_office_user_last_name: 'Luvu', assigned_office_user_first_name: 'Frankie' }]; @@ -61,5 +109,16 @@ describe('When given a move that has been assigned', () => { expect(screen.getByText('Task invoicing officer assigned')).toBeInTheDocument(); expect(screen.getByText(': Luvu, Frankie')).toBeInTheDocument(); }); + it('reassignment of a task invoicing officer', () => { + historyRecord.changedValues = { tio_assigned_id: 'fb625e3c-067c-49d7-8fd9-88ef040e6137' }; + historyRecord.oldValues = { tio_assigned_id: '759a87ad-dc75-4b34-b551-d31309a79f64' }; + historyRecord.context = [{ assigned_office_user_last_name: 'Luvu', assigned_office_user_first_name: 'Frankie' }]; + + const template = getTemplate(historyRecord); + + render(template.getDetails(historyRecord)); + expect(screen.getByText('Task invoicing officer reassigned')).toBeInTheDocument(); + expect(screen.getByText(': Luvu, Frankie')).toBeInTheDocument(); + }); }); }); diff --git a/src/constants/MoveHistory/EventTemplates/UpdateMTOServiceItem/updateServiceItemPricingAndWeights.test.jsx b/src/constants/MoveHistory/EventTemplates/UpdateMTOServiceItem/updateServiceItemPricingAndWeights.test.jsx index eae1e8fe630..cfc63966876 100644 --- a/src/constants/MoveHistory/EventTemplates/UpdateMTOServiceItem/updateServiceItemPricingAndWeights.test.jsx +++ b/src/constants/MoveHistory/EventTemplates/UpdateMTOServiceItem/updateServiceItemPricingAndWeights.test.jsx @@ -8,7 +8,7 @@ import t from 'constants/MoveHistory/Database/Tables'; describe('when given an UpdateMTOServiceItem history record with pricing/weight changes', () => { const context = [ { - name: 'International Shipping & Linehaul', + name: 'International shipping & linehaul', shipment_type: 'HHG', shipment_locator: 'RQ38D4-01', shipment_id_abbr: 'a1b2c', @@ -31,7 +31,7 @@ describe('when given an UpdateMTOServiceItem history record with pricing/weight render(template.getDetails(historyRecord)); expect(screen.getByText('Service item')).toBeInTheDocument(); - expect(screen.getByText(/International Shipping & Linehaul/)).toBeInTheDocument(); + expect(screen.getByText(/International shipping & linehaul/)).toBeInTheDocument(); expect(screen.getByText('Estimated Price')).toBeInTheDocument(); expect(screen.getByText(/\$1,500\.00/)).toBeInTheDocument(); }); @@ -52,7 +52,7 @@ describe('when given an UpdateMTOServiceItem history record with pricing/weight render(template.getDetails(historyRecord)); expect(screen.getByText('Service item')).toBeInTheDocument(); - expect(screen.getByText(/International Shipping & Linehaul/)).toBeInTheDocument(); + expect(screen.getByText(/International shipping & linehaul/)).toBeInTheDocument(); expect(screen.getByText('Estimated weight')).toBeInTheDocument(); expect(screen.getByText(/1,000 lbs/)).toBeInTheDocument(); }); diff --git a/src/constants/MoveHistory/EventTemplates/index.js b/src/constants/MoveHistory/EventTemplates/index.js index 68760edcd43..cef846f1e5f 100644 --- a/src/constants/MoveHistory/EventTemplates/index.js +++ b/src/constants/MoveHistory/EventTemplates/index.js @@ -116,8 +116,8 @@ export { default as deleteAssignedOfficeUser } from './UpdateAssignedOfficeUser/ export { default as UpdatePaymentRequestStatusMoves } from './UpdatePaymentRequestStatus/UpdatePaymentRequestStatusMoves'; export { default as reviewShipmentAddressUpdate } from './ReviewShipmentAddressUpdate/reviewShipmentAddressUpdate'; export { default as FinishDocumentReviewMoves } from './FinishDocumentReview/FinishDocumentReviewMoves'; +export { default as updatePaymentServiceItemStatus } from './UpdatePaymentServiceItem/UpdatePaymentServiceItemStatus'; export { default as approveShipments } from './ApproveShipments/approveShipments'; export { default as approveShipmentsUpdateAllowances } from './ApproveShipments/approveShipmentsUpdateAllowances'; export { default as approveShipmentsApproveMove } from './ApproveShipments/approveShipmentsApproveMove'; export { default as approveShipmentsServiceItem } from './ApproveShipments/approveShipmentsServiceItem'; -export { default as updatePaymentServiceItemStatus } from './UpdatePaymentServiceItem/UpdatePaymentServiceItemStatus'; diff --git a/src/constants/MoveHistory/UIDisplay/Operations.js b/src/constants/MoveHistory/UIDisplay/Operations.js index bbe9d846345..b6bb2f69759 100644 --- a/src/constants/MoveHistory/UIDisplay/Operations.js +++ b/src/constants/MoveHistory/UIDisplay/Operations.js @@ -68,7 +68,7 @@ export default { addAppealToViolation: 'addAppealToViolation', // ghc.yaml addAppealToSeriousIncident: 'addAppealToSeriousIncident', // ghc.yaml cancelMove: 'cancelMove', // internal.yaml + reviewShipmentAddressUpdate: 'reviewShipmentAddressUpdate', // ghc.yaml updateAssignedOfficeUser: 'updateAssignedOfficeUser', // ghc.yaml deleteAssignedOfficeUser: 'deleteAssignedOfficeUser', // ghc.yaml - reviewShipmentAddressUpdate: 'reviewShipmentAddressUpdate', // ghc.yaml }; diff --git a/src/constants/prime.js b/src/constants/prime.js index 267156031b9..3316a91955a 100644 --- a/src/constants/prime.js +++ b/src/constants/prime.js @@ -4,13 +4,15 @@ import { serviceItemCodes } from 'content/serviceItems'; export const createServiceItemModelTypes = { MTOServiceItemOriginSIT: 'MTOServiceItemOriginSIT', MTOServiceItemDestSIT: 'MTOServiceItemDestSIT', - MTOServiceItemShuttle: 'MTOServiceItemShuttle', + MTOServiceItemInternationalOriginSIT: 'MTOServiceItemInternationalOriginSIT', + MTOServiceItemInternationalDestSIT: 'MTOServiceItemInternationalDestSIT', + MTOServiceItemDomesticShuttle: 'MTOServiceItemDomesticShuttle', MTOServiceItemDomesticCrating: 'MTOServiceItemDomesticCrating', MTOServiceItemInternationalCrating: 'MTOServiceItemInternationalCrating', MTOServiceItemInternationalShuttle: 'MTOServiceItemInternationalShuttle', }; -export const shuttleServiceItemCodeOptions = [ +export const domesticShuttleServiceItemCodeOptions = [ { value: serviceItemCodes.DOSHUT, key: SERVICE_ITEM_CODES.DOSHUT }, { value: serviceItemCodes.DDSHUT, key: SERVICE_ITEM_CODES.DDSHUT }, ]; diff --git a/src/constants/routes.js b/src/constants/routes.js index 1c4121aef59..a018cef31ea 100644 --- a/src/constants/routes.js +++ b/src/constants/routes.js @@ -4,8 +4,8 @@ export const generalRoutes = { REQUEST_ACCOUNT: '/request-account', PRIVACY_SECURITY_POLICY_PATH: '/privacy-and-security-policy', ACCESSIBILITY_PATH: '/accessibility', - QUEUE_SEARCH_PATH: 'Search', - BASE_QUEUE_SEARCH_PATH: '/Search', + QUEUE_SEARCH_PATH: 'search', + BASE_QUEUE_SEARCH_PATH: '/search', }; export const customerRoutes = { @@ -120,6 +120,8 @@ export const tooRoutes = { BASE_SHIPMENT_EDIT_PATH: `${BASE_MOVES_PATH}/shipments/:shipmentId`, MOVE_QUEUE: `move-queue`, BASE_MOVE_QUEUE: `/move-queue`, + BASE_DESTINATION_REQUESTS_QUEUE: `/destination-requests`, + DESTINATION_REQUESTS_QUEUE: `destination-requests`, SHIPMENT_EDIT_PATH: 'shipments/:shipmentId', BASE_MOVE_VIEW_PATH: `${BASE_MOVES_PATH}/details`, MOVE_VIEW_PATH: 'details', diff --git a/src/constants/serviceItems.js b/src/constants/serviceItems.js index 213afb45f38..39f62d6511c 100644 --- a/src/constants/serviceItems.js +++ b/src/constants/serviceItems.js @@ -17,6 +17,7 @@ const SERVICE_ITEM_PARAM_KEYS = { DistanceZipSITDest: 'DistanceZipSITDest', DistanceZipSITOrigin: 'DistanceZipSITOrigin', EIAFuelPrice: 'EIAFuelPrice', + ExternalCrate: 'ExternalCrate', FSCPriceDifferenceInCents: 'FSCPriceDifferenceInCents', EscalationCompounded: 'EscalationCompounded', FSCWeightBasedDistanceMultiplier: 'FSCWeightBasedDistanceMultiplier', @@ -54,13 +55,17 @@ const SERVICE_ITEM_PARAM_KEYS = { StandaloneCrate: 'StandaloneCrate', StandaloneCrateCap: 'StandaloneCrateCap', UncappedRequestTotal: 'UncappedRequestTotal', + MarketOrigin: 'MarketOrigin', + MarketDest: 'MarketDest', }; const SERVICE_ITEM_CALCULATION_LABELS = { [SERVICE_ITEM_PARAM_KEYS.ActualPickupDate]: 'Pickup date', [SERVICE_ITEM_PARAM_KEYS.ContractYearName]: 'Base year', + [SERVICE_ITEM_PARAM_KEYS.CubicFeetCrating]: 'Actual size', [SERVICE_ITEM_PARAM_KEYS.DestinationPrice]: 'Destination price', [SERVICE_ITEM_PARAM_KEYS.EIAFuelPrice]: 'EIA diesel', + [SERVICE_ITEM_PARAM_KEYS.ExternalCrate]: 'External crate', [SERVICE_ITEM_PARAM_KEYS.FSCPriceDifferenceInCents]: 'Baseline rate difference', [SERVICE_ITEM_PARAM_KEYS.FSCWeightBasedDistanceMultiplier]: 'Weight-based distance multiplier', // Domestic non-peak or Domestic peak @@ -102,7 +107,9 @@ const SERVICE_ITEM_CALCULATION_LABELS = { Domestic: 'Domestic', FuelSurchargePrice: 'Mileage factor', InternationalShippingAndLinehaul: 'ISLH price', + Market: 'Market', Mileage: 'Mileage', + MinSizeCrateApplied: 'Minimum crating size applied', MileageIntoSIT: 'Mileage into SIT', MileageOutOfSIT: 'Mileage out of SIT', NTSPackingFactor: 'NTS packing factor', @@ -121,8 +128,8 @@ const SERVICE_ITEM_CALCULATION_LABELS = { UncratingDate: 'Uncrating date', UncratingPrice: 'Uncrating price (per cu ft)', SITFuelSurchargePrice: 'SIT mileage factor', - StandaloneCrate: 'Standalone Crate Cap', - UncappedRequestTotal: 'Uncapped Request Total', + StandaloneCrate: 'Standalone crate cap', + UncappedRequestTotal: 'Uncapped request total', Total: 'Total', }; @@ -146,6 +153,7 @@ const SERVICE_ITEM_CODES = { FSC: 'FSC', DDSHUT: 'DDSHUT', IDSHUT: 'IDSHUT', + DCRTSA: 'DCRTSA', DCRT: 'DCRT', DUCRT: 'DUCRT', ICRT: 'ICRT', @@ -158,6 +166,17 @@ const SERVICE_ITEM_CODES = { IHPK: 'IHPK', IHUPK: 'IHUPK', ISLH: 'ISLH', + IDDSIT: 'IDDSIT', + IDASIT: 'IDASIT', + IOASIT: 'IOASIT', + IOFSIT: 'IOFSIT', + IOPSIT: 'IOPSIT', + IDFSIT: 'IDFSIT', + IOSFSC: 'IOSFSC', + IDSFSC: 'IDSFSC', + IUBPK: 'IUBPK', + IUBUPK: 'IUBUPK', + UBP: 'UBP', }; const SERVICE_ITEMS_ALLOWED_WEIGHT_BILLED_PARAM = [ @@ -185,8 +204,18 @@ const SERVICE_ITEMS_ALLOWED_UPDATE = [ SERVICE_ITEM_CODES.DDFSIT, SERVICE_ITEM_CODES.DOSFSC, SERVICE_ITEM_CODES.DDSFSC, + SERVICE_ITEM_CODES.IDSHUT, + SERVICE_ITEM_CODES.IOSHUT, SERVICE_ITEM_CODES.PODFSC, SERVICE_ITEM_CODES.POEFSC, + SERVICE_ITEM_CODES.IDDSIT, + SERVICE_ITEM_CODES.IDASIT, + SERVICE_ITEM_CODES.IOASIT, + SERVICE_ITEM_CODES.IOFSIT, + SERVICE_ITEM_CODES.IOPSIT, + SERVICE_ITEM_CODES.IDFSIT, + SERVICE_ITEM_CODES.IOSFSC, + SERVICE_ITEM_CODES.IDSFSC, ]; /** @@ -205,6 +234,18 @@ const SIT_SERVICE_ITEM_CODES = { DDASIT: 'DDASIT', /** Domestic destination SIT delivery */ DDDSIT: 'DDDSIT', + /** International origin 1st day SIT */ + IOFSIT: 'IOFSIT', + /** International origin Additional day SIT */ + IOASIT: 'IOASIT', + /** International origin SIT pickup */ + IOPSIT: 'IOPSIT', + /** International destination 1st day SIT */ + IDFSIT: 'IDFSIT', + /** International destination Additional day SIT */ + IDASIT: 'IDASIT', + /** International destination SIT delivery */ + IDDSIT: 'IDDSIT', }; // TODO - temporary, will remove once all service item calculations are implemented @@ -219,12 +260,14 @@ const allowedServiceItemCalculations = [ SERVICE_ITEM_CODES.DOP, SERVICE_ITEM_CODES.DOPSIT, SERVICE_ITEM_CODES.DOSHUT, + SERVICE_ITEM_CODES.IOSHUT, SERVICE_ITEM_CODES.DPK, SERVICE_ITEM_CODES.DNPK, SERVICE_ITEM_CODES.DSH, SERVICE_ITEM_CODES.DUPK, SERVICE_ITEM_CODES.FSC, SERVICE_ITEM_CODES.DDSHUT, + SERVICE_ITEM_CODES.IDSHUT, SERVICE_ITEM_CODES.DCRT, SERVICE_ITEM_CODES.DUCRT, SERVICE_ITEM_CODES.DOSFSC, @@ -234,8 +277,12 @@ const allowedServiceItemCalculations = [ SERVICE_ITEM_CODES.ISLH, SERVICE_ITEM_CODES.POEFSC, SERVICE_ITEM_CODES.PODFSC, + SERVICE_ITEM_CODES.ICRT, + SERVICE_ITEM_CODES.IUCRT, ]; +const EXTERNAL_CRATE_MIN_CUBIC_FT = '4.00'; + export default SERVICE_ITEM_STATUSES; export { @@ -247,4 +294,5 @@ export { SERVICE_ITEM_STATUSES, SERVICE_ITEMS_ALLOWED_WEIGHT_BILLED_PARAM, SERVICE_ITEMS_ALLOWED_UPDATE, + EXTERNAL_CRATE_MIN_CUBIC_FT, }; diff --git a/src/constants/sitUpdates.js b/src/constants/sitUpdates.js index 6a724b5e205..7e7bedb2fc2 100644 --- a/src/constants/sitUpdates.js +++ b/src/constants/sitUpdates.js @@ -1,6 +1,6 @@ // allowing edit of SIT entry date for Domestic destination 1st day SIT (DDFSIT) // allowing edit of SIT entry date for Domestic origin 1st day SIT (DOFSIT) -export const ALLOWED_SIT_UPDATE_SI_CODES = ['DOFSIT', 'DDFSIT']; +export const ALLOWED_SIT_UPDATE_SI_CODES = ['DOFSIT', 'DDFSIT', 'IOFSIT', 'IDFSIT']; // allowing display of old service item details for following SIT types which can be resubmitted export const ALLOWED_RESUBMISSION_SI_CODES = [ @@ -12,4 +12,12 @@ export const ALLOWED_RESUBMISSION_SI_CODES = [ 'DOSFSC', 'DDASIT', 'DDSFSC', + 'IDFSIT', + 'IDASIT', + 'IDDSIT', + 'IDSFSC', + 'IOFSIT', + 'IOASIT', + 'IOPSIT', + 'IOSFSC', ]; diff --git a/src/hooks/queries.js b/src/hooks/queries.js index 17ccfe30f5a..1129a3e7901 100644 --- a/src/hooks/queries.js +++ b/src/hooks/queries.js @@ -34,6 +34,7 @@ import { getPPMActualWeight, searchCustomers, getGBLOCs, + getDestinationRequestsQueue, getBulkAssignmentData, } from 'services/ghcApi'; import { getLoggedInUserQueries } from 'services/internalApi'; @@ -587,6 +588,28 @@ export const useMovesQueueQueries = ({ }; }; +export const useDestinationRequestsQueueQueries = ({ + sort, + order, + filters = [], + currentPage = PAGINATION_PAGE_DEFAULT, + currentPageSize = PAGINATION_PAGE_SIZE_DEFAULT, + viewAsGBLOC, +}) => { + const { data = {}, ...movesQueueQuery } = useQuery( + [MOVES_QUEUE, { sort, order, filters, currentPage, currentPageSize, viewAsGBLOC }], + ({ queryKey }) => getDestinationRequestsQueue(...queryKey), + ); + const { isLoading, isError, isSuccess } = movesQueueQuery; + const { queueMoves, ...dataProps } = data; + return { + queueResult: { data: queueMoves, ...dataProps }, + isLoading, + isError, + isSuccess, + }; +}; + export const useServicesCounselingQueuePPMQueries = ({ sort, order, diff --git a/src/pages/Admin/RequestedOfficeUsers/RequestedOfficeUserList.jsx b/src/pages/Admin/RequestedOfficeUsers/RequestedOfficeUserList.jsx index 9aeb51ea0f9..f87651ad31a 100644 --- a/src/pages/Admin/RequestedOfficeUsers/RequestedOfficeUserList.jsx +++ b/src/pages/Admin/RequestedOfficeUsers/RequestedOfficeUserList.jsx @@ -1,41 +1,126 @@ import React from 'react'; -import { Datagrid, DateField, Filter, List, ReferenceField, TextField, TextInput, TopToolbar } from 'react-admin'; +import { + ArrayField, + Datagrid, + DateField, + Filter, + List, + ReferenceField, + TextField, + TextInput, + TopToolbar, + useRecordContext, + downloadCSV, + useDataProvider, + ExportButton, + useListController, +} from 'react-admin'; +import jsonExport from 'jsonexport/dist'; import AdminPagination from 'scenes/SystemAdmin/shared/AdminPagination'; -// Overriding the default toolbar -const ListActions = () => { - return ; -}; - -const RequestedOfficeUserListFilter = () => ( - - +const RequestedOfficeUserListFilter = (props) => ( + + + + ); const defaultSort = { field: 'createdAt', order: 'DESC' }; -const RequestedOfficeUserList = () => ( - } - perPage={25} - sort={defaultSort} - filters={} - actions={} - > - - - - - - - - - - - - -); +const UserRolesToString = (user) => { + const { roles } = user; + + let roleStr = ''; + for (let i = 0; i < roles.length; i += 1) { + roleStr += roles[i].roleName; + + if (i < roles.length - 1) { + roleStr += ', '; + } + } + + return roleStr; +}; + +const RolesField = () => { + const record = useRecordContext(); + return
{UserRolesToString(record)}
; +}; + +const ListActions = () => { + const { total, resource, sort, filterValues } = useListController(); + const dataProvider = useDataProvider(); + + const exporter = async (users) => { + const officeObjects = {}; + const offices = await dataProvider.getMany('offices'); + offices.data.forEach((office) => { + if (!officeObjects[`${office.id}`]) { + officeObjects[`${office.id}`] = office; + } + }); + + const usersWithTransportationOfficeName = users.map((user) => ({ + ...user, + transportationOfficeName: officeObjects[user.transportationOfficeId]?.name, + })); + + const usersForExport = usersWithTransportationOfficeName.map((user) => { + const { id, email, firstName, lastName, transportationOfficeName, status, createdAt } = user; + const userRoles = UserRolesToString(user); + return { + Id: id, + Email: email, + 'First Name': firstName, + 'Last Name': lastName, + 'Transportation Office': transportationOfficeName, + Status: status, + 'Requested On': createdAt, + Roles: userRoles, + }; + }); + + // convert data to csv and download + jsonExport(usersForExport, {}, (err, csv) => { + if (err) throw err; + downloadCSV(csv, 'requested_office_users'); + }); + }; + + return ( + + + + ); +}; + +const RequestedOfficeUserList = () => { + return ( + } + perPage={25} + sort={defaultSort} + filters={} + actions={} + > + + + + + + + + + + + + + + + + ); +}; export default RequestedOfficeUserList; diff --git a/src/pages/Admin/RequestedOfficeUsers/RequestedOfficeUserShow.module.scss b/src/pages/Admin/RequestedOfficeUsers/RequestedOfficeUserShow.module.scss index 974aa69bf7f..d2b9adfc325 100644 --- a/src/pages/Admin/RequestedOfficeUsers/RequestedOfficeUserShow.module.scss +++ b/src/pages/Admin/RequestedOfficeUsers/RequestedOfficeUserShow.module.scss @@ -52,4 +52,6 @@ margin-left: 15px; margin-right: 15px; margin-bottom: 10px; -} \ No newline at end of file +} + +ul { list-style-type: none; } \ No newline at end of file diff --git a/src/pages/MyMove/Home/MoveHome.test.jsx b/src/pages/MyMove/Home/MoveHome.test.jsx index 82e7c5b53ba..19b83b275f8 100644 --- a/src/pages/MyMove/Home/MoveHome.test.jsx +++ b/src/pages/MyMove/Home/MoveHome.test.jsx @@ -6,8 +6,8 @@ import { act, waitFor } from '@testing-library/react'; import MoveHome from './MoveHome'; -import { MockProviders } from 'testUtils'; import { customerRoutes } from 'constants/routes'; +import { MockProviders } from 'testUtils'; import { cancelMove, downloadPPMAOAPacket } from 'services/internalApi'; import { ORDERS_TYPE } from 'constants/orders'; import { isBooleanFlagEnabled } from 'utils/featureFlags'; diff --git a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx index b49f6aea696..062635c3488 100644 --- a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx +++ b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx @@ -119,7 +119,7 @@ const fakePayload = { }, create_okta_account: 'true', cac_user: 'false', - is_safety_move: false, + is_safety_move: 'false', is_bluebark: 'false', }; diff --git a/src/pages/Office/MoveAllowances/MoveAllowances.jsx b/src/pages/Office/MoveAllowances/MoveAllowances.jsx index 5e7057c752f..a09f22e87de 100644 --- a/src/pages/Office/MoveAllowances/MoveAllowances.jsx +++ b/src/pages/Office/MoveAllowances/MoveAllowances.jsx @@ -44,10 +44,17 @@ const validationSchema = Yup.object({ .transform((value) => (Number.isNaN(value) ? 0 : value)) .notRequired(), weightRestriction: Yup.number() - .min(1, 'Weight restriction must be greater than 0') - .max(18000, 'Weight restriction cannot exceed 18,000 lbs') .transform((value) => (Number.isNaN(value) ? 0 : value)) - .notRequired(), + .when('adminRestrictedWeightLocation', { + is: true, + then: (schema) => + schema + .min(1, 'Weight restriction must be greater than 0') + .max(18000, 'Weight restriction cannot exceed 18,000 lbs') + .required('Weight restriction is required when Admin Restricted Weight Location is enabled'), + otherwise: (schema) => schema.notRequired().nullable(), + }), + adminRestrictedWeightLocation: Yup.boolean().notRequired(), }); const MoveAllowances = () => { @@ -96,18 +103,19 @@ const MoveAllowances = () => { const { grade, agency, - dependentsAuthorized, proGearWeight, proGearWeightSpouse, requiredMedicalEquipmentWeight, organizationalClothingAndIndividualEquipment, storageInTransit, gunSafe, + adminRestrictedWeightLocation, weightRestriction, accompaniedTour, dependentsTwelveAndOver, dependentsUnderTwelve, } = values; + const body = { issueDate: order.date_issued, newDutyLocationId: order.destinationDutyLocation.id, @@ -117,24 +125,23 @@ const MoveAllowances = () => { reportByDate: order.report_by_date, grade, agency, - dependentsAuthorized, proGearWeight: Number(proGearWeight), proGearWeightSpouse: Number(proGearWeightSpouse), requiredMedicalEquipmentWeight: Number(requiredMedicalEquipmentWeight), organizationalClothingAndIndividualEquipment, storageInTransit: Number(storageInTransit), gunSafe, - weightRestriction: Number(weightRestriction), + weightRestriction: adminRestrictedWeightLocation && weightRestriction ? Number(weightRestriction) : null, accompaniedTour, dependentsTwelveAndOver: Number(dependentsTwelveAndOver), dependentsUnderTwelve: Number(dependentsUnderTwelve), }; + mutateOrders({ orderID: orderId, ifMatchETag: order.eTag, body }); }; const { entitlement, grade, agency } = order; const { - dependentsAuthorized, proGearWeight, proGearWeightSpouse, requiredMedicalEquipmentWeight, @@ -150,13 +157,13 @@ const MoveAllowances = () => { const initialValues = { grade, agency, - dependentsAuthorized, proGearWeight: `${proGearWeight}`, proGearWeightSpouse: `${proGearWeightSpouse}`, requiredMedicalEquipmentWeight: `${requiredMedicalEquipmentWeight}`, organizationalClothingAndIndividualEquipment, gunSafe, - weightRestriction: `${weightRestriction}`, + adminRestrictedWeightLocation: weightRestriction > 0, + weightRestriction: weightRestriction ? `${weightRestriction}` : '0', storageInTransit: `${storageInTransit}`, accompaniedTour, dependentsUnderTwelve: `${dependentsUnderTwelve}`, @@ -206,7 +213,7 @@ const MoveAllowances = () => {
-
diff --git a/src/pages/Office/MoveDocumentWrapper/MoveDocumentWrapper.jsx b/src/pages/Office/MoveDocumentWrapper/MoveDocumentWrapper.jsx index f7d97fde5a9..186a1da3c4a 100644 --- a/src/pages/Office/MoveDocumentWrapper/MoveDocumentWrapper.jsx +++ b/src/pages/Office/MoveDocumentWrapper/MoveDocumentWrapper.jsx @@ -20,6 +20,7 @@ const MoveDocumentWrapper = () => { // this is to update the id when it is created to store amendedUpload data. const [amendedDocumentId, setAmendedDocumentId] = useState(amendedOrderDocumentId); const { amendedUpload } = useAmendedDocumentQueries(amendedDocumentId); + const [isFileUploading, setFileUploading] = useState(false); const updateAmendedDocument = (newId) => { setAmendedDocumentId(newId); @@ -63,7 +64,7 @@ const MoveDocumentWrapper = () => {
{documentsForViewer && (
- +
)} {showOrders ? ( @@ -72,6 +73,9 @@ const MoveDocumentWrapper = () => { files={documentsByTypes} amendedDocumentId={amendedDocumentId} updateAmendedDocument={updateAmendedDocument} + onAddFile={() => { + setFileUploading(true); + }} /> ) : ( diff --git a/src/pages/Office/MoveQueue/MoveQueue.jsx b/src/pages/Office/MoveQueue/MoveQueue.jsx index 12d42ac5d22..e1cfdff3f35 100644 --- a/src/pages/Office/MoveQueue/MoveQueue.jsx +++ b/src/pages/Office/MoveQueue/MoveQueue.jsx @@ -6,12 +6,17 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import styles from './MoveQueue.module.scss'; import { createHeader } from 'components/Table/utils'; -import { useMovesQueueQueries, useUserQueries, useMoveSearchQueries } from 'hooks/queries'; -import { getMovesQueue } from 'services/ghcApi'; +import { + useMovesQueueQueries, + useUserQueries, + useMoveSearchQueries, + useDestinationRequestsQueueQueries, +} from 'hooks/queries'; +import { getDestinationRequestsQueue, getMovesQueue } from 'services/ghcApi'; import { formatDateFromIso, serviceMemberAgencyLabel } from 'utils/formatters'; import MultiSelectCheckBoxFilter from 'components/Table/Filters/MultiSelectCheckBoxFilter'; import SelectFilter from 'components/Table/Filters/SelectFilter'; -import { MOVE_STATUS_OPTIONS, GBLOC, MOVE_STATUS_LABELS, BRANCH_OPTIONS } from 'constants/queues'; +import { MOVE_STATUS_OPTIONS, GBLOC, MOVE_STATUS_LABELS, BRANCH_OPTIONS, QUEUE_TYPES } from 'constants/queues'; import TableQueue from 'components/Table/TableQueue'; import LoadingPlaceholder from 'shared/LoadingPlaceholder'; import SomethingWentWrong from 'shared/SomethingWentWrong'; @@ -161,13 +166,17 @@ export const columns = (moveLockFlag, isQueueManagementEnabled, showBranchFilter ) : (
handleQueueAssignment(row.id, e.target.value, roleTypes.TOO)} title="Assigned dropdown" > {row.availableOfficeUsers?.map(({ lastName, firstName, officeUserId }) => ( - ))} @@ -270,6 +279,15 @@ const MoveQueue = ({ isQueueManagementFFEnabled, userPrivileges, isBulkAssignmen Task Order Queue , + (isActive ? 'usa-current' : '')} + to={tooRoutes.BASE_DESTINATION_REQUESTS_QUEUE} + > + + Destination Requests Queue + + , (isActive ? 'usa-current' : '')} @@ -334,11 +352,38 @@ const MoveQueue = ({ isQueueManagementFFEnabled, userPrivileges, isBulkAssignmen key={queueType} isSupervisor={supervisor} isBulkAssignmentFFEnabled={isBulkAssignmentFFEnabled} + queueType={QUEUE_TYPES.TASK_ORDER} activeRole={activeRole} />
); } + if (queueType === tooRoutes.DESTINATION_REQUESTS_QUEUE) { + return ( +
+ {renderNavBar()} + +
+ ); + } return ; }; diff --git a/src/pages/Office/MoveQueue/MoveQueue.test.jsx b/src/pages/Office/MoveQueue/MoveQueue.test.jsx index 4e5ec01cd33..9779f03f75a 100644 --- a/src/pages/Office/MoveQueue/MoveQueue.test.jsx +++ b/src/pages/Office/MoveQueue/MoveQueue.test.jsx @@ -166,6 +166,31 @@ jest.mock('hooks/queries', () => ({ }, }; }, + useBulkAssignmentQueries: () => { + return { + availableOfficeUsers: [ + { + firstName: 'John', + lastName: 'Snow', + officeUserId: '123', + workload: 0, + }, + { + firstName: 'Jane', + lastName: 'Doe', + officeUserId: '456', + workload: 1, + }, + { + firstName: 'Jimmy', + lastName: 'Page', + officeUserId: '789', + workload: 2, + }, + ], + bulkAssignmentMoveIDs: ['1', '2', '3'], + }; + }, })); const GetMountedComponent = (queueTypeToMount) => { @@ -328,7 +353,7 @@ describe('MoveQueue', () => { wrapper.update(); expect(wrapper.find('[data-testid="multi-value-container"]').text()).toEqual('New move'); }); - it('renders Search and Move Queue tabs', () => { + it('renders Search, Destination Queue and Move Queue tabs', () => { reactRouterDom.useParams.mockReturnValue({ queueType: generalRoutes.QUEUE_SEARCH_PATH }); render( @@ -338,6 +363,7 @@ describe('MoveQueue', () => { expect(screen.getByTestId('closeout-tab-link')).toBeInTheDocument(); expect(screen.getByTestId('search-tab-link')).toBeInTheDocument(); expect(screen.getByText('Task Order Queue', { selector: 'span' })).toBeInTheDocument(); + expect(screen.getByText('Destination Requests Queue', { selector: 'span' })).toBeInTheDocument(); expect(screen.getByText('Search', { selector: 'span' })).toBeInTheDocument(); }); it('renders TableQueue when Search tab is selected', () => { diff --git a/src/pages/Office/MoveTaskOrder/MoveTaskOrder.jsx b/src/pages/Office/MoveTaskOrder/MoveTaskOrder.jsx index a1fe5abec1c..d2b71885c0a 100644 --- a/src/pages/Office/MoveTaskOrder/MoveTaskOrder.jsx +++ b/src/pages/Office/MoveTaskOrder/MoveTaskOrder.jsx @@ -798,6 +798,16 @@ export const MoveTaskOrder = (props) => { setAlertMessage('SIT entry date updated'); setAlertType('success'); }, + onError: (error) => { + let errorMessage = 'There was a problem updating the SIT entry date'; + if (error.response.status === 422) { + const responseData = JSON.parse(error?.response?.data); + errorMessage = responseData?.detail; + setAlertMessage(errorMessage); + setAlertType('error'); + } + setIsEditSitEntryDateModalVisible(false); + }, }, ); }; diff --git a/src/pages/Office/MoveTaskOrder/MoveTaskOrder.test.jsx b/src/pages/Office/MoveTaskOrder/MoveTaskOrder.test.jsx index 81a65bd6098..d07cd167678 100644 --- a/src/pages/Office/MoveTaskOrder/MoveTaskOrder.test.jsx +++ b/src/pages/Office/MoveTaskOrder/MoveTaskOrder.test.jsx @@ -1,6 +1,8 @@ import React from 'react'; import { mount } from 'enzyme'; -import { render, screen } from '@testing-library/react'; +import { render, screen, within, cleanup } from '@testing-library/react'; +import * as reactQuery from '@tanstack/react-query'; +import userEvent from '@testing-library/user-event'; import { unapprovedMTOQuery, @@ -22,6 +24,7 @@ import { multiplePaymentRequests, moveHistoryTestData, actualPPMWeightQuery, + approvedMTOWithApprovedSitItemsQuery, } from './moveTaskOrderUnitTestData'; import { MoveTaskOrder } from 'pages/Office/MoveTaskOrder/MoveTaskOrder'; @@ -543,6 +546,153 @@ describe('MoveTaskOrder', () => { }); }); + describe('SIT entry date update', () => { + const mockMutateServiceItemSitEntryDate = jest.fn(); + jest.spyOn(reactQuery, 'useMutation').mockImplementation(() => ({ + mutate: mockMutateServiceItemSitEntryDate, + })); + beforeEach(() => { + // Reset the mock before each test + mockMutateServiceItemSitEntryDate.mockReset(); + }); + afterEach(() => { + cleanup(); // This will unmount the component after each test + }); + + const renderComponent = () => { + useMoveTaskOrderQueries.mockReturnValue(approvedMTOWithApprovedSitItemsQuery); + useMovePaymentRequestsQueries.mockReturnValue({ paymentRequests: [] }); + useGHCGetMoveHistory.mockReturnValue(moveHistoryTestData); + const isMoveLocked = false; + render( + + + , + ); + }; + it('shows error message when SIT entry date is invalid', async () => { + renderComponent(); + // Set up the mock to simulate an error + mockMutateServiceItemSitEntryDate.mockImplementation((data, options) => { + options.onError({ + response: { + status: 422, + data: JSON.stringify({ + detail: + 'UpdateSitEntryDate failed for service item: the SIT Entry Date (2025-03-05) must be before the SIT Departure Date (2025-02-27)', + }), + }, + }); + }); + const approvedServiceItems = await screen.findByTestId('ApprovedServiceItemsTable'); + expect(approvedServiceItems).toBeInTheDocument(); + const spanElement = within(approvedServiceItems).getByText(/Domestic origin 1st day SIT/i); + expect(spanElement).toBeInTheDocument(); + // Search for the edit button within the approvedServiceItems div + const editButton = within(approvedServiceItems).getByRole('button', { name: /edit/i }); + expect(editButton).toBeInTheDocument(); + await userEvent.click(editButton); + const modal = await screen.findByTestId('modal'); + expect(modal).toBeInTheDocument(); + const heading = within(modal).getByRole('heading', { name: /Edit SIT Entry Date/i, level: 2 }); + expect(heading).toBeInTheDocument(); + const formGroups = screen.getAllByTestId('formGroup'); + const sitEntryDateFormGroup = Array.from(formGroups).find( + (group) => + within(group).queryByPlaceholderText('DD MMM YYYY') && + within(group).queryByPlaceholderText('DD MMM YYYY').getAttribute('name') === 'sitEntryDate', + ); + const dateInput = within(sitEntryDateFormGroup).getByPlaceholderText('DD MMM YYYY'); + expect(dateInput).toBeInTheDocument(); + const remarksTextarea = within(modal).getByTestId('officeRemarks'); + expect(remarksTextarea).toBeInTheDocument(); + const saveButton = within(modal).getByRole('button', { name: /Save/ }); + + await userEvent.clear(dateInput); + await userEvent.type(dateInput, '05 Mar 2025'); + await userEvent.type(remarksTextarea, 'Need to update the sit entry date.'); + expect(saveButton).toBeEnabled(); + await userEvent.click(saveButton); + + // Verify that the mutation was called + expect(mockMutateServiceItemSitEntryDate).toHaveBeenCalled(); + + // The modal should close + expect(screen.queryByTestId('modal')).not.toBeInTheDocument(); + + // Verify that the error message is displayed + const alert = screen.getByTestId('alert'); + expect(alert).toBeInTheDocument(); + expect(alert).toHaveClass('usa-alert--error'); + expect(alert).toHaveTextContent( + 'UpdateSitEntryDate failed for service item: the SIT Entry Date (2025-03-05) must be before the SIT Departure Date (2025-02-27)', + ); + }); + + it('shows success message when SIT entry date is valid', async () => { + renderComponent(); + // Set up the mock to simulate an error + mockMutateServiceItemSitEntryDate.mockImplementation((data, options) => { + options.onSuccess({ + response: { + status: 200, + data: JSON.stringify({ + detail: 'SIT entry date updated', + }), + }, + }); + }); + const approvedServiceItems = await screen.findByTestId('ApprovedServiceItemsTable'); + expect(approvedServiceItems).toBeInTheDocument(); + const spanElement = within(approvedServiceItems).getByText(/Domestic origin 1st day SIT/i); + expect(spanElement).toBeInTheDocument(); + // Search for the edit button within the approvedServiceItems div + const editButton = within(approvedServiceItems).getByRole('button', { name: /edit/i }); + expect(editButton).toBeInTheDocument(); + await userEvent.click(editButton); + const modal = await screen.findByTestId('modal'); + expect(modal).toBeInTheDocument(); + const heading = within(modal).getByRole('heading', { name: /Edit SIT Entry Date/i, level: 2 }); + expect(heading).toBeInTheDocument(); + const formGroups = screen.getAllByTestId('formGroup'); + const sitEntryDateFormGroup = Array.from(formGroups).find( + (group) => + within(group).queryByPlaceholderText('DD MMM YYYY') && + within(group).queryByPlaceholderText('DD MMM YYYY').getAttribute('name') === 'sitEntryDate', + ); + const dateInput = within(sitEntryDateFormGroup).getByPlaceholderText('DD MMM YYYY'); + expect(dateInput).toBeInTheDocument(); + const remarksTextarea = within(modal).getByTestId('officeRemarks'); + expect(remarksTextarea).toBeInTheDocument(); + const saveButton = within(modal).getByRole('button', { name: /Save/ }); + + await userEvent.clear(dateInput); + await userEvent.type(dateInput, '03 Mar 2024'); + await userEvent.type(remarksTextarea, 'Need to update the sit entry date.'); + expect(saveButton).toBeEnabled(); + await userEvent.click(saveButton); + + // Verify that the mutation was called + expect(mockMutateServiceItemSitEntryDate).toHaveBeenCalled(); + + // The modal should close + expect(screen.queryByTestId('modal')).not.toBeInTheDocument(); + + // Verify that the error message is displayed + const alert = screen.getByTestId('alert'); + expect(alert).toBeInTheDocument(); + expect(alert).toHaveClass('usa-alert--success'); + expect(alert).toHaveTextContent('SIT entry date updated'); + }); + }); + describe('approved mto with both submitted and approved shipments', () => { useMoveTaskOrderQueries.mockReturnValue(someShipmentsApprovedMTOQuery); useMovePaymentRequestsQueries.mockReturnValue(multiplePaymentRequests); diff --git a/src/pages/Office/MoveTaskOrder/moveTaskOrderUnitTestData.js b/src/pages/Office/MoveTaskOrder/moveTaskOrderUnitTestData.js index 614867fe84b..a1cc6a708ff 100644 --- a/src/pages/Office/MoveTaskOrder/moveTaskOrderUnitTestData.js +++ b/src/pages/Office/MoveTaskOrder/moveTaskOrderUnitTestData.js @@ -3004,3 +3004,76 @@ export const moveHistoryTestData = { ], }, }; + +export const approvedMTOWithApprovedSitItemsQuery = { + orders: { + 1: { + id: '1', + originDutyLocation: { + address: { + streetAddress1: '', + city: 'Fort Knox', + state: 'KY', + postalCode: '40121', + }, + }, + destinationDutyLocation: { + address: { + streetAddress1: '', + city: 'Fort Irwin', + state: 'CA', + postalCode: '92310', + }, + }, + entitlement: { + authorizedWeight: 8000, + totalWeight: 8500, + }, + }, + }, + move: { + id: '2', + status: MOVE_STATUSES.APPROVALS_REQUESTED, + }, + mtoShipments: [ + { + id: '3', + moveTaskOrderID: '2', + shipmentType: SHIPMENT_OPTIONS.HHG, + scheduledPickupDate: '2020-03-16', + requestedPickupDate: '2020-03-15', + pickupAddress: { + streetAddress1: '932 Baltic Avenue', + city: 'Chicago', + state: 'IL', + postalCode: '60601', + eTag: '1234', + }, + destinationAddress: { + streetAddress1: '10 Park Place', + city: 'Atlantic City', + state: 'NJ', + postalCode: '08401', + }, + status: shipmentStatuses.APPROVED, + eTag: '1234', + reweigh: { + id: '00000000-0000-0000-0000-000000000000', + }, + sitExtensions: [], + sitStatus: SITStatusOrigin, + }, + ], + mtoServiceItems: [ + { + id: '5', + mtoShipmentID: '3', + reServiceName: 'Domestic origin 1st day SIT', + status: SERVICE_ITEM_STATUS.APPROVED, + reServiceCode: 'DOFSIT', + }, + ], + isLoading: false, + isError: false, + isSuccess: true, +}; diff --git a/src/pages/Office/Orders/Orders.jsx b/src/pages/Office/Orders/Orders.jsx index 1bf21c4fc50..8f19a48874b 100644 --- a/src/pages/Office/Orders/Orders.jsx +++ b/src/pages/Office/Orders/Orders.jsx @@ -33,7 +33,7 @@ const ordersTypeDropdownOptions = dropdownInputOptions(ORDERS_TYPE_OPTIONS); const ordersTypeDetailsDropdownOptions = dropdownInputOptions(ORDERS_TYPE_DETAILS_OPTIONS); const payGradeDropdownOptions = dropdownInputOptions(ORDERS_PAY_GRADE_OPTIONS); -const Orders = ({ files, amendedDocumentId, updateAmendedDocument }) => { +const Orders = ({ files, amendedDocumentId, updateAmendedDocument, onAddFile }) => { const navigate = useNavigate(); const { moveCode } = useParams(); const [tacValidationState, tacValidationDispatch] = useReducer(tacReducer, null, initialTacState); @@ -190,6 +190,7 @@ const Orders = ({ files, amendedDocumentId, updateAmendedDocument }) => { proGearWeightSpouse, requiredMedicalEquipmentWeight, organizationalClothingAndIndividualEquipment, + dependentsAuthorized, } = entitlement; useEffect(() => { @@ -310,6 +311,7 @@ const Orders = ({ files, amendedDocumentId, updateAmendedDocument }) => { ntsSac: order?.ntsSac, ordersAcknowledgement: !!amendedOrdersAcknowledgedAt, payGrade: order?.grade, + dependentsAuthorized, }; return ( @@ -375,6 +377,7 @@ const Orders = ({ files, amendedDocumentId, updateAmendedDocument }) => { documentId={documentId} files={ordersDocuments} documentType={MOVE_DOCUMENT_TYPE.ORDERS} + onAddFile={onAddFile} /> { files={amendedDocuments} documentType={MOVE_DOCUMENT_TYPE.AMENDMENTS} updateAmendedDocument={updateAmendedDocument} + onAddFile={onAddFile} />
diff --git a/src/pages/Office/Orders/Orders.test.jsx b/src/pages/Office/Orders/Orders.test.jsx index e2d0ada3624..2dca7071881 100644 --- a/src/pages/Office/Orders/Orders.test.jsx +++ b/src/pages/Office/Orders/Orders.test.jsx @@ -209,6 +209,7 @@ describe('Orders page', () => { expect(screen.getByTestId('ntsTacInput')).toHaveValue('1111'); expect(screen.getByTestId('ntsSacInput')).toHaveValue('2222'); expect(screen.getByTestId('payGradeInput')).toHaveDisplayValue('E-1'); + expect(screen.getByLabelText('Dependents authorized')).toBeChecked(); }); }); diff --git a/src/pages/Office/PPM/ReviewDocuments/ReviewDocuments.test.jsx b/src/pages/Office/PPM/ReviewDocuments/ReviewDocuments.test.jsx index ec2f277d650..9685d68dc01 100644 --- a/src/pages/Office/PPM/ReviewDocuments/ReviewDocuments.test.jsx +++ b/src/pages/Office/PPM/ReviewDocuments/ReviewDocuments.test.jsx @@ -34,6 +34,12 @@ jest.mock('react-router-dom', () => ({ useNavigate: () => mockNavigate, })); +global.EventSource = jest.fn().mockImplementation(() => ({ + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + close: jest.fn(), +})); + const mockPatchWeightTicket = jest.fn(); const mockPatchProGear = jest.fn(); const mockPatchExpense = jest.fn(); diff --git a/src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.jsx b/src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.jsx index c43cf7bfbe6..457c4069d99 100644 --- a/src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.jsx +++ b/src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.jsx @@ -18,7 +18,7 @@ import { } from 'utils/formatters'; import SelectFilter from 'components/Table/Filters/SelectFilter'; import DateSelectFilter from 'components/Table/Filters/DateSelectFilter'; -import { BRANCH_OPTIONS, GBLOC } from 'constants/queues'; +import { BRANCH_OPTIONS, GBLOC, QUEUE_TYPES } from 'constants/queues'; import TableQueue from 'components/Table/TableQueue'; import LoadingPlaceholder from 'shared/LoadingPlaceholder'; import SomethingWentWrong from 'shared/SomethingWentWrong'; @@ -160,7 +160,7 @@ export const columns = (moveLockFlag, isQueueManagementEnabled, showBranchFilter ) : (
{ handleQueueAssignment(row.moveID, e.target.value, roleTypes.TIO); }} @@ -169,7 +169,11 @@ export const columns = (moveLockFlag, isQueueManagementEnabled, showBranchFilter {row.availableOfficeUsers.map(({ lastName, firstName, officeUserId }) => { return ( - ); @@ -334,6 +338,7 @@ const PaymentRequestQueue = ({ isQueueManagementFFEnabled, userPrivileges, isBul key={queueType} isSupervisor={supervisor} isBulkAssignmentFFEnabled={isBulkAssignmentFFEnabled} + queueType={QUEUE_TYPES.PAYMENT_REQUEST} activeRole={activeRole} />
diff --git a/src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.test.jsx b/src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.test.jsx index 772fd604bba..5d1f3363409 100644 --- a/src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.test.jsx +++ b/src/pages/Office/PaymentRequestQueue/PaymentRequestQueue.test.jsx @@ -153,6 +153,31 @@ jest.mock('hooks/queries', () => ({ isSuccess: true, }; }, + useBulkAssignmentQueries: () => { + return { + availableOfficeUsers: [ + { + firstName: 'John', + lastName: 'Snow', + officeUserId: '123', + workload: 0, + }, + { + firstName: 'Jane', + lastName: 'Doe', + officeUserId: '456', + workload: 1, + }, + { + firstName: 'Jimmy', + lastName: 'Page', + officeUserId: '789', + workload: 2, + }, + ], + bulkAssignmentMoveIDs: ['1', '2', '3'], + }; + }, })); const SEARCH_OPTIONS = ['Move Code', 'DoD ID', 'Customer Name', 'Payment Request Number']; diff --git a/src/pages/Office/PaymentRequestReview/PaymentRequestReview.test.jsx b/src/pages/Office/PaymentRequestReview/PaymentRequestReview.test.jsx index f95bd113559..f97ad6da589 100644 --- a/src/pages/Office/PaymentRequestReview/PaymentRequestReview.test.jsx +++ b/src/pages/Office/PaymentRequestReview/PaymentRequestReview.test.jsx @@ -16,6 +16,12 @@ jest.mock('react-router-dom', () => ({ useNavigate: () => jest.fn(), })); +global.EventSource = jest.fn().mockImplementation(() => ({ + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + close: jest.fn(), +})); + const mockPDFUpload = { contentType: 'application/pdf', createdAt: '2020-09-17T16:00:48.099137Z', diff --git a/src/pages/Office/ServicesCounselingMoveAllowances/ServicesCounselingMoveAllowances.jsx b/src/pages/Office/ServicesCounselingMoveAllowances/ServicesCounselingMoveAllowances.jsx index d80502bcd17..e5cb77fb576 100644 --- a/src/pages/Office/ServicesCounselingMoveAllowances/ServicesCounselingMoveAllowances.jsx +++ b/src/pages/Office/ServicesCounselingMoveAllowances/ServicesCounselingMoveAllowances.jsx @@ -41,28 +41,18 @@ const validationSchema = Yup.object({ .min(0, 'Storage in transit (days) must be greater than or equal to 0') .transform((value) => (Number.isNaN(value) ? 0 : value)) .notRequired(), - adminRestrictedWeightLocation: Yup.bool(), - weightRestriction: Yup.number().when('adminRestrictedWeightLocation', { - is: (value) => { - return value === true; - }, - then: (schema) => { - return schema - .required('Weight restriction is required when location is restricted') - .min(1, 'Weight restriction must be greater than 0') - .max(18000, 'Weight restriction cannot exceed 18,000 lbs') - .transform((value) => { - return Number.isNaN(value) ? 0 : value; - }); - }, - otherwise: (schema) => { - return schema - .transform((value) => { - return Number.isNaN(value) ? 0 : value; - }) - .notRequired(); - }, - }), + weightRestriction: Yup.number() + .transform((value) => (Number.isNaN(value) ? 0 : value)) + .when('adminRestrictedWeightLocation', { + is: true, + then: (schema) => + schema + .min(1, 'Weight restriction must be greater than 0') + .max(18000, 'Weight restriction cannot exceed 18,000 lbs') + .required('Weight restriction is required when Admin Restricted Weight Location is enabled'), + otherwise: (schema) => schema.notRequired().nullable(), + }), + adminRestrictedWeightLocation: Yup.boolean().notRequired(), }); const ServicesCounselingMoveAllowances = () => { const { moveCode } = useParams(); @@ -100,13 +90,13 @@ const ServicesCounselingMoveAllowances = () => { const { grade, agency, - dependentsAuthorized, proGearWeight, proGearWeightSpouse, requiredMedicalEquipmentWeight, organizationalClothingAndIndividualEquipment, storageInTransit, gunSafe, + adminRestrictedWeightLocation, weightRestriction, accompaniedTour, dependentsTwelveAndOver, @@ -121,14 +111,13 @@ const ServicesCounselingMoveAllowances = () => { reportByDate: order.report_by_date, grade, agency, - dependentsAuthorized, proGearWeight: Number(proGearWeight), proGearWeightSpouse: Number(proGearWeightSpouse), requiredMedicalEquipmentWeight: Number(requiredMedicalEquipmentWeight), storageInTransit: Number(storageInTransit), organizationalClothingAndIndividualEquipment, gunSafe, - weightRestriction: Number(weightRestriction), + weightRestriction: adminRestrictedWeightLocation && weightRestriction ? Number(weightRestriction) : null, accompaniedTour, dependentsTwelveAndOver: Number(dependentsTwelveAndOver), dependentsUnderTwelve: Number(dependentsUnderTwelve), @@ -138,7 +127,6 @@ const ServicesCounselingMoveAllowances = () => { const { entitlement, grade, agency } = order; const { - dependentsAuthorized, proGearWeight, proGearWeightSpouse, requiredMedicalEquipmentWeight, @@ -154,18 +142,17 @@ const ServicesCounselingMoveAllowances = () => { const initialValues = { grade, agency, - dependentsAuthorized, proGearWeight: `${proGearWeight}`, proGearWeightSpouse: `${proGearWeightSpouse}`, requiredMedicalEquipmentWeight: `${requiredMedicalEquipmentWeight}`, storageInTransit: `${storageInTransit}`, gunSafe, - weightRestriction: `${weightRestriction}`, + adminRestrictedWeightLocation: weightRestriction > 0, + weightRestriction: weightRestriction ? `${weightRestriction}` : '0', organizationalClothingAndIndividualEquipment, accompaniedTour, dependentsUnderTwelve: `${dependentsUnderTwelve}`, dependentsTwelveAndOver: `${dependentsTwelveAndOver}`, - adminRestrictedWeightLocation: false, }; return ( @@ -202,7 +189,11 @@ const ServicesCounselingMoveAllowances = () => {
-
diff --git a/src/pages/Office/ServicesCounselingMoveDocumentWrapper/ServicesCounselingMoveDocumentWrapper.jsx b/src/pages/Office/ServicesCounselingMoveDocumentWrapper/ServicesCounselingMoveDocumentWrapper.jsx index f3c50c20e39..60c9661dc26 100644 --- a/src/pages/Office/ServicesCounselingMoveDocumentWrapper/ServicesCounselingMoveDocumentWrapper.jsx +++ b/src/pages/Office/ServicesCounselingMoveDocumentWrapper/ServicesCounselingMoveDocumentWrapper.jsx @@ -20,6 +20,7 @@ const ServicesCounselingMoveDocumentWrapper = () => { // this is to update the id when it is created to store amendedUpload data. const [amendedDocumentId, setAmendedDocumentId] = useState(amendedOrderDocumentId); const { amendedUpload } = useAmendedDocumentQueries(amendedDocumentId); + const [isFileUploading, setFileUploading] = useState(false); const updateAmendedDocument = (newId) => { setAmendedDocumentId(newId); @@ -64,7 +65,7 @@ const ServicesCounselingMoveDocumentWrapper = () => {
{documentsForViewer && (
- +
)} {showOrders ? ( @@ -73,6 +74,9 @@ const ServicesCounselingMoveDocumentWrapper = () => { files={documentsByTypes} amendedDocumentId={amendedDocumentId} updateAmendedDocument={updateAmendedDocument} + onAddFile={() => { + setFileUploading(true); + }} /> ) : ( diff --git a/src/pages/Office/ServicesCounselingOrders/ServicesCounselingOrders.jsx b/src/pages/Office/ServicesCounselingOrders/ServicesCounselingOrders.jsx index 5a3d37c59e0..f524e03f6e8 100644 --- a/src/pages/Office/ServicesCounselingOrders/ServicesCounselingOrders.jsx +++ b/src/pages/Office/ServicesCounselingOrders/ServicesCounselingOrders.jsx @@ -37,7 +37,7 @@ const deptIndicatorDropdownOptions = dropdownInputOptions(DEPARTMENT_INDICATOR_O const ordersTypeDetailsDropdownOptions = dropdownInputOptions(ORDERS_TYPE_DETAILS_OPTIONS); const payGradeDropdownOptions = dropdownInputOptions(ORDERS_PAY_GRADE_OPTIONS); -const ServicesCounselingOrders = ({ files, amendedDocumentId, updateAmendedDocument }) => { +const ServicesCounselingOrders = ({ files, amendedDocumentId, updateAmendedDocument, onAddFile }) => { const navigate = useNavigate(); const queryClient = useQueryClient(); const { moveCode } = useParams(); @@ -306,6 +306,7 @@ const ServicesCounselingOrders = ({ files, amendedDocumentId, updateAmendedDocum ntsTac: order?.ntsTac, ntsSac: order?.ntsSac, payGrade: order?.grade, + dependentsAuthorized: order?.entitlement?.dependentsAuthorized, }; const tacWarningMsg = @@ -371,6 +372,7 @@ const ServicesCounselingOrders = ({ files, amendedDocumentId, updateAmendedDocum documentId={orderDocumentId} files={ordersDocuments} documentType={MOVE_DOCUMENT_TYPE.ORDERS} + onAddFile={onAddFile} />
diff --git a/src/pages/Office/ServicesCounselingOrders/ServicesCounselingOrders.test.jsx b/src/pages/Office/ServicesCounselingOrders/ServicesCounselingOrders.test.jsx index b10032c6da9..2a893702ffd 100644 --- a/src/pages/Office/ServicesCounselingOrders/ServicesCounselingOrders.test.jsx +++ b/src/pages/Office/ServicesCounselingOrders/ServicesCounselingOrders.test.jsx @@ -212,6 +212,7 @@ describe('Orders page', () => { ); expect(await screen.findByLabelText('Current duty location')).toBeInTheDocument(); + expect(screen.getByLabelText('Dependents authorized')).toBeChecked(); }); it('renders the sidebar elements', async () => { diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx index 5ba55412a60..be1c4ec9e4c 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx @@ -1,57 +1,58 @@ -import React, { useCallback, useEffect, useState, useContext } from 'react'; -import { generatePath, useNavigate, Navigate, useParams, NavLink } from 'react-router-dom'; -import { connect } from 'react-redux'; -import { Button, Dropdown } from '@trussworks/react-uswds'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Button, Dropdown } from '@trussworks/react-uswds'; +import React, { useCallback, useContext, useEffect, useState } from 'react'; +import { connect } from 'react-redux'; +import { generatePath, Navigate, NavLink, useNavigate, useParams } from 'react-router-dom'; import styles from './ServicesCounselingQueue.module.scss'; -import { createHeader } from 'components/Table/utils'; -import SelectFilter from 'components/Table/Filters/SelectFilter'; +import CustomerSearchForm from 'components/CustomerSearchForm/CustomerSearchForm'; +import MoveSearchForm from 'components/MoveSearchForm/MoveSearchForm'; +import NotFound from 'components/NotFound/NotFound'; +import SelectedGblocContext from 'components/Office/GblocSwitcher/SelectedGblocContext'; import DateSelectFilter from 'components/Table/Filters/DateSelectFilter'; +import MultiSelectTypeAheadCheckBoxFilter from 'components/Table/Filters/MutliSelectTypeAheadCheckboxFilter'; +import SelectFilter from 'components/Table/Filters/SelectFilter'; +import SearchResultsTable from 'components/Table/SearchResultsTable'; import TableQueue from 'components/Table/TableQueue'; +import { createHeader } from 'components/Table/utils'; +import TabNav from 'components/TabNav'; +import { CHECK_SPECIAL_ORDERS_TYPES, SPECIAL_ORDERS_TYPES } from 'constants/orders'; import { BRANCH_OPTIONS, + QUEUE_TYPES, SERVICE_COUNSELING_MOVE_STATUS_LABELS, - SERVICE_COUNSELING_PPM_TYPE_OPTIONS, - SERVICE_COUNSELING_PPM_TYPE_LABELS, - SERVICE_COUNSELING_PPM_STATUS_OPTIONS, SERVICE_COUNSELING_PPM_STATUS_LABELS, + SERVICE_COUNSELING_PPM_STATUS_OPTIONS, + SERVICE_COUNSELING_PPM_TYPE_LABELS, + SERVICE_COUNSELING_PPM_TYPE_OPTIONS, } from 'constants/queues'; import { generalRoutes, servicesCounselingRoutes } from 'constants/routes'; import { elevatedPrivilegeTypes } from 'constants/userPrivileges'; +import { roleTypes } from 'constants/userRoles'; +import ConnectedFlashMessage from 'containers/FlashMessage/FlashMessage'; import { - useServicesCounselingQueueQueries, + useCustomerSearchQueries, + useMoveSearchQueries, useServicesCounselingQueuePPMQueries, + useServicesCounselingQueueQueries, useUserQueries, - useMoveSearchQueries, - useCustomerSearchQueries, } from 'hooks/queries'; import { getServicesCounselingOriginLocations, - getServicesCounselingQueue, getServicesCounselingPPMQueue, + getServicesCounselingQueue, } from 'services/ghcApi'; import { DATE_FORMAT_STRING, DEFAULT_EMPTY_VALUE, MOVE_STATUSES } from 'shared/constants'; -import { formatDateFromIso, serviceMemberAgencyLabel } from 'utils/formatters'; import LoadingPlaceholder from 'shared/LoadingPlaceholder'; import SomethingWentWrong from 'shared/SomethingWentWrong'; -import NotFound from 'components/NotFound/NotFound'; -import MoveSearchForm from 'components/MoveSearchForm/MoveSearchForm'; -import { roleTypes } from 'constants/userRoles'; -import SearchResultsTable from 'components/Table/SearchResultsTable'; -import TabNav from 'components/TabNav'; +import { isNullUndefinedOrWhitespace } from 'shared/utils'; +import { selectLoggedInUser } from 'store/entities/selectors'; import { isBooleanFlagEnabled, isCounselorMoveCreateEnabled } from 'utils/featureFlags'; -import retryPageLoading from 'utils/retryPageLoading'; +import { formatDateFromIso, serviceMemberAgencyLabel } from 'utils/formatters'; import { milmoveLogger } from 'utils/milmoveLog'; -import { CHECK_SPECIAL_ORDERS_TYPES, SPECIAL_ORDERS_TYPES } from 'constants/orders'; -import ConnectedFlashMessage from 'containers/FlashMessage/FlashMessage'; -import { isNullUndefinedOrWhitespace } from 'shared/utils'; -import CustomerSearchForm from 'components/CustomerSearchForm/CustomerSearchForm'; -import MultiSelectTypeAheadCheckBoxFilter from 'components/Table/Filters/MutliSelectTypeAheadCheckboxFilter'; import handleQueueAssignment from 'utils/queues'; -import { selectLoggedInUser } from 'store/entities/selectors'; -import SelectedGblocContext from 'components/Office/GblocSwitcher/SelectedGblocContext'; +import retryPageLoading from 'utils/retryPageLoading'; export const counselingColumns = (moveLockFlag, originLocationList, supervisor, isQueueManagementEnabled) => { const cols = [ @@ -202,15 +203,19 @@ export const counselingColumns = (moveLockFlag, originLocationList, supervisor, return !row?.assignable ? (
{row.assignedTo ? `${row.assignedTo?.lastName}, ${row.assignedTo?.firstName}` : ''}
) : ( -
+
handleQueueAssignment(row.id, e.target.value, roleTypes.SERVICES_COUNSELOR)} title="Assigned dropdown" > {row.availableOfficeUsers.map(({ lastName, firstName, officeUserId }) => ( - ))} @@ -610,7 +615,7 @@ const ServicesCounselingQueue = ({ return ; }; - if (queueType === 'Search') { + if (queueType === generalRoutes.QUEUE_SEARCH_PATH) { return (
{renderNavBar()} @@ -671,6 +676,7 @@ const ServicesCounselingQueue = ({ key={queueType} isSupervisor={supervisor} isBulkAssignmentFFEnabled={isBulkAssignmentFFEnabled} + queueType={QUEUE_TYPES.CLOSEOUT} activeRole={activeRole} />
@@ -701,6 +707,7 @@ const ServicesCounselingQueue = ({ key={queueType} isSupervisor={supervisor} isBulkAssignmentFFEnabled={isBulkAssignmentFFEnabled} + queueType={QUEUE_TYPES.COUNSELING} activeRole={activeRole} />
diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx index eda36174ca3..592cd9a8500 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx @@ -19,6 +19,31 @@ jest.mock('hooks/queries', () => ({ useUserQueries: jest.fn(), useServicesCounselingQueueQueries: jest.fn(), useServicesCounselingQueuePPMQueries: jest.fn(), + useBulkAssignmentQueries: () => { + return { + availableOfficeUsers: [ + { + firstName: 'John', + lastName: 'Snow', + officeUserId: '123', + workload: 0, + }, + { + firstName: 'Jane', + lastName: 'Doe', + officeUserId: '456', + workload: 1, + }, + { + firstName: 'Jimmy', + lastName: 'Page', + officeUserId: '789', + workload: 2, + }, + ], + bulkAssignmentMoveIDs: ['1', '2', '3'], + }; + }, })); jest.mock('utils/featureFlags', () => ({ diff --git a/src/pages/Office/SupportingDocuments/SupportingDocuments.jsx b/src/pages/Office/SupportingDocuments/SupportingDocuments.jsx index aeae84fd136..a226732aaa7 100644 --- a/src/pages/Office/SupportingDocuments/SupportingDocuments.jsx +++ b/src/pages/Office/SupportingDocuments/SupportingDocuments.jsx @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import moment from 'moment'; import classNames from 'classnames'; @@ -10,6 +10,7 @@ import { permissionTypes } from 'constants/permissions'; import { MOVE_DOCUMENT_TYPE } from 'shared/constants'; const SupportingDocuments = ({ move, uploads }) => { + const [isFileUploading, setFileUploading] = useState(false); const filteredAndSortedUploads = Object.values(uploads || {}) ?.filter((file) => { return !file.deletedAt; @@ -23,7 +24,7 @@ const SupportingDocuments = ({ move, uploads }) => { filteredAndSortedUploads?.length <= 0 ? (

No supporting documents have been uploaded.

) : ( - + )}
@@ -36,6 +37,9 @@ const SupportingDocuments = ({ move, uploads }) => { documentId={move.additionalDocuments?.id} files={filteredAndSortedUploads} documentType={MOVE_DOCUMENT_TYPE.SUPPORTING} + onAddFile={() => { + setFileUploading(true); + }} />
diff --git a/src/pages/Office/SupportingDocuments/SupportingDocuments.test.jsx b/src/pages/Office/SupportingDocuments/SupportingDocuments.test.jsx index 3e466e8fabc..81f91f7fc1a 100644 --- a/src/pages/Office/SupportingDocuments/SupportingDocuments.test.jsx +++ b/src/pages/Office/SupportingDocuments/SupportingDocuments.test.jsx @@ -12,6 +12,11 @@ beforeEach(() => { jest.clearAllMocks(); }); +global.EventSource = jest.fn().mockImplementation(() => ({ + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + close: jest.fn(), +})); // prevents react-fileviewer from throwing errors without mocking relevant DOM elements jest.mock('components/DocumentViewer/Content/Content', () => { const MockContent = () =>
Content
; diff --git a/src/pages/PrimeUI/CreateSITExtensionRequest/CreateSITExtensionRequest.test.jsx b/src/pages/PrimeUI/CreateSITExtensionRequest/CreateSITExtensionRequest.test.jsx index 917aadf7937..05e4e0b3599 100644 --- a/src/pages/PrimeUI/CreateSITExtensionRequest/CreateSITExtensionRequest.test.jsx +++ b/src/pages/PrimeUI/CreateSITExtensionRequest/CreateSITExtensionRequest.test.jsx @@ -33,12 +33,7 @@ const moveTaskOrder = { id: '2', shipmentType: 'HHG', requestedPickupDate: '2021-11-26', - pickupAddress: { - streetAddress1: '100 1st Avenue', - city: 'New York', - state: 'NY', - postalCode: '10001', - }, + pickupAddress: { streetAddress1: '100 1st Avenue', city: 'New York', state: 'NY', postalCode: '10001' }, marketCode: 'd', }, ], diff --git a/src/pages/PrimeUI/MoveTaskOrder/MoveDetails.jsx b/src/pages/PrimeUI/MoveTaskOrder/MoveDetails.jsx index d07ddc4c34c..42216ae631f 100644 --- a/src/pages/PrimeUI/MoveTaskOrder/MoveDetails.jsx +++ b/src/pages/PrimeUI/MoveTaskOrder/MoveDetails.jsx @@ -265,6 +265,16 @@ const MoveDetails = ({ setFlashMessage }) => {

{serviceItem.reServiceCode} - {serviceItem.reServiceName}

+
+
Status:
+
{serviceItem.status}
+
+ {serviceItem.market && ( +
+
Market
+
{serviceItem.market}
+
+ )}
{SERVICE_ITEMS_ALLOWED_UPDATE.includes(serviceItem.reServiceCode) ? ( { }); }); +describe('Error when submitting', () => { + it('Correctly displays the unexpected server error window when an unusuable api error response is returned', async () => { + createPrimeMTOShipmentV3.mockRejectedValue('malformed api error response'); + render(mockedComponent); + + waitFor(async () => { + await userEvent.selectOptions(screen.getByLabelText('Shipment type'), 'HHG'); + + const saveButton = await screen.getByRole('button', { name: 'Save' }); + + expect(saveButton).not.toBeDisabled(); + await userEvent.click(saveButton); + expect(screen.getByText('Unexpected error')).toBeInTheDocument(); + expect( + screen.getByText('An unknown error has occurred, please check the address values used'), + ).toBeInTheDocument(); + }); + }); + + it('Correctly displays the invalid fields in the error window when an api error response is returned', async () => { + createPrimeMTOShipmentV3.mockRejectedValue({ body: { title: 'Error', invalidFields: { someField: true } } }); + render(mockedComponent); + + waitFor(async () => { + await userEvent.selectOptions(screen.getByLabelText('Shipment type'), 'HHG'); + + const saveButton = await screen.getByRole('button', { name: 'Save' }); + + expect(saveButton).not.toBeDisabled(); + await userEvent.click(saveButton); + expect(screen.getByText('Prime API: Error')).toBeInTheDocument(); + expect( + screen.getByText('An unknown error has occurred, please check the address values used'), + ).toBeInTheDocument(); + }); + }); +}); + describe('Create PPM', () => { it('test destination address street 1 is OPTIONAL', async () => { createPrimeMTOShipmentV3.mockReturnValue({}); diff --git a/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateDestSITForm.jsx b/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateDestSITForm.jsx index 6ef614279da..7903fc4f494 100644 --- a/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateDestSITForm.jsx +++ b/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateDestSITForm.jsx @@ -36,6 +36,7 @@ const PrimeUIUpdateDestSITForm = ({ initialValues, onSubmit, serviceItem }) => { Here you can update specific fields for a destination SIT service item.
At this time, only the following values can be updated:
{' '} + SIT Entry Date
SIT Departure Date
SIT Requested Delivery
SIT Customer Contacted
@@ -67,8 +68,12 @@ const PrimeUIUpdateDestSITForm = ({ initialValues, onSubmit, serviceItem }) => {
+ +
+
+
{serviceItem.status === SERVICE_ITEM_STATUSES.REJECTED && ( diff --git a/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateDestSITForm.test.jsx b/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateDestSITForm.test.jsx index 60e50555bba..3e7c1c055e3 100644 --- a/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateDestSITForm.test.jsx +++ b/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateDestSITForm.test.jsx @@ -26,6 +26,7 @@ const reformatPrimeApiSITDestinationAddress = fromPrimeAPIAddressFormat(shipment const destSitInitialValues = { sitDestinationFinalAddress: reformatPrimeApiSITDestinationAddress, + sitEntryDate: '25 Oct 2023', sitDepartureDate: '01 Nov 2023', sitRequestedDelivery: '01 Dec 2023', sitCustomerContacted: '15 Oct 2023', @@ -56,6 +57,7 @@ describe('PrimeUIRequestSITDestAddressChangeForm', () => { expect( screen.getByRole('heading', { name: 'DDDSIT - Domestic destination SIT delivery', level: 3 }), ).toBeInTheDocument(); + expect(await screen.findByLabelText('SIT Entry Date')).toHaveValue('25 Oct 2023'); expect(await screen.findByLabelText('SIT Departure Date')).toHaveValue('01 Nov 2023'); expect(await screen.findByLabelText('SIT Requested Delivery')).toHaveValue('01 Dec 2023'); expect(await screen.findByLabelText('SIT Customer Contacted')).toHaveValue('15 Oct 2023'); diff --git a/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateInternationalDestSITForm.jsx b/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateInternationalDestSITForm.jsx new file mode 100644 index 00000000000..4de27935ee3 --- /dev/null +++ b/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateInternationalDestSITForm.jsx @@ -0,0 +1,103 @@ +import React from 'react'; +import { Formik } from 'formik'; +import { useNavigate, useParams, generatePath } from 'react-router-dom'; +import { FormGroup } from '@trussworks/react-uswds'; +import classnames from 'classnames'; + +import styles from './PrimeUIUpdateSITForms.module.scss'; + +import SectionWrapper from 'components/Customer/SectionWrapper'; +import formStyles from 'styles/form.module.scss'; +import { Form } from 'components/form/Form'; +import TextField from 'components/form/fields/TextField/TextField'; +import WizardNavigation from 'components/Customer/WizardNavigation/WizardNavigation'; +import descriptionListStyles from 'styles/descriptionList.module.scss'; +import { primeSimulatorRoutes } from 'constants/routes'; +import { DatePickerInput } from 'components/form/fields'; +import { SERVICE_ITEM_STATUSES } from 'constants/serviceItems'; + +const PrimeUIUpdateInternationalDestSITForm = ({ initialValues, onSubmit, serviceItem }) => { + const { moveCodeOrID } = useParams(); + const navigate = useNavigate(); + + const handleClose = () => { + navigate(generatePath(primeSimulatorRoutes.VIEW_MOVE_PATH, { moveCodeOrID })); + }; + + return ( + + {({ handleSubmit }) => ( +
+ +
+

Update International Destination SIT Service Item

+ +
+ Here you can update specific fields for an international destination SIT service item.
+ At this time, only the following values can be updated:
{' '} + + SIT Departure Date
+ SIT Requested Delivery
+ SIT Customer Contacted
+ Update Reason +
+
+
+
+ +

+ {serviceItem.reServiceCode} - {serviceItem.reServiceName} +

+
+
+
ID:
+
{serviceItem.id}
+
+
+
MTO ID:
+
{serviceItem.moveTaskOrderID}
+
+
+
Shipment ID:
+
{serviceItem.mtoShipmentID}
+
+
+
Status:
+
{serviceItem.status}
+
+
+
+ + + +
+ {serviceItem.status === SERVICE_ITEM_STATUSES.REJECTED && ( + + )} +
+ +
+
+
+ )} +
+ ); +}; + +export default PrimeUIUpdateInternationalDestSITForm; diff --git a/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateInternationalDestSITForm.test.jsx b/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateInternationalDestSITForm.test.jsx new file mode 100644 index 00000000000..2286ccacac8 --- /dev/null +++ b/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateInternationalDestSITForm.test.jsx @@ -0,0 +1,99 @@ +import React from 'react'; +import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import PrimeUIUpdateDestSITForm from './PrimeUIUpdateDestSITForm'; + +import { fromPrimeAPIAddressFormat } from 'utils/formatters'; +import { renderWithProviders } from 'testUtils'; +import { primeSimulatorRoutes } from 'constants/routes'; + +const shipmentAddress = { + streetAddress1: '444 Main Ave', + streetAddress2: 'Apartment 9000', + streetAddress3: 'Something else', + city: 'Anytown', + state: 'AL', + postalCode: '90210', +}; + +const serviceItem = { + reServiceCode: 'IDDSIT', + reServiceName: 'International destination SIT delivery', +}; + +const reformatPrimeApiSITDestinationAddress = fromPrimeAPIAddressFormat(shipmentAddress); + +const destSitInitialValues = { + sitDestinationFinalAddress: reformatPrimeApiSITDestinationAddress, + sitDepartureDate: '01 Nov 2023', + sitRequestedDelivery: '01 Dec 2023', + sitCustomerContacted: '15 Oct 2023', + mtoServiceItemID: '45fe9475-d592-48f5-896a-45d4d6eb7e76', + reServiceCode: 'IDDSIT', +}; + +// Mock the react-router-dom functions +const mockNavigate = jest.fn(); +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useNavigate: () => mockNavigate, + useParams: jest.fn().mockReturnValue({ moveCodeOrID: ':moveCodeOrID' }), +})); + +describe('PrimeUIRequestInternationalSITDestAddressChangeForm', () => { + it('renders the address change request form', async () => { + renderWithProviders( + , + ); + + expect(screen.getByRole('heading', { name: 'Update Destination SIT Service Item', level: 2 })).toBeInTheDocument(); + expect( + screen.getByRole('heading', { name: 'IDDSIT - International destination SIT delivery', level: 3 }), + ).toBeInTheDocument(); + expect(await screen.findByLabelText('SIT Departure Date')).toHaveValue('01 Nov 2023'); + expect(await screen.findByLabelText('SIT Requested Delivery')).toHaveValue('01 Dec 2023'); + expect(await screen.findByLabelText('SIT Customer Contacted')).toHaveValue('15 Oct 2023'); + expect(screen.getByRole('button', { name: 'Save' })).toBeEnabled(); + expect(screen.getByRole('button', { name: 'Cancel' })).toBeEnabled(); + }); + + it('fires off onSubmit function when save button is clicked', async () => { + const onSubmitMock = jest.fn(); + renderWithProviders( + , + ); + + const saveButton = await screen.findByRole('button', { name: 'Save' }); + + await userEvent.click(saveButton); + + expect(onSubmitMock).toHaveBeenCalled(); + }); + + it('directs the user back to the move page when cancel button is clicked', async () => { + renderWithProviders( + , + ); + + const cancelButton = await screen.findByRole('button', { name: 'Cancel' }); + + await userEvent.click(cancelButton); + + expect(mockNavigate).toHaveBeenCalledWith(primeSimulatorRoutes.VIEW_MOVE_PATH); + }); +}); diff --git a/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateInternationalFuelSurchargeForm.test.jsx b/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateInternationalFuelSurchargeForm.test.jsx index 78461a6e840..7a92ae361d2 100644 --- a/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateInternationalFuelSurchargeForm.test.jsx +++ b/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateInternationalFuelSurchargeForm.test.jsx @@ -12,7 +12,7 @@ const mtoServiceItemID = '38569958-2889-41e5-8101-82c56ec48430'; const serviceItem = { id: mtoServiceItemID, reServiceCode: 'POEFSC', - reServiceName: 'International POE Fuel Surcharge', + reServiceName: 'International POE fuel surcharge', status: 'APPROVED', mtoShipmentID: '38569958-2889-41e5-8102-82c56ec48430', }; @@ -64,7 +64,7 @@ describe('PrimeUIUpdateInternationalFuelSurchargeForm', () => { screen.getByRole('heading', { name: 'Update International Fuel Surcharge Service Item', level: 2 }), ).toBeInTheDocument(); expect( - screen.getByRole('heading', { name: 'POEFSC - International POE Fuel Surcharge', level: 3 }), + screen.getByRole('heading', { name: 'POEFSC - International POE fuel surcharge', level: 3 }), ).toBeInTheDocument(); expect(screen.getByText('Port:')).toBeInTheDocument(); expect(screen.getByText('SEATTLE TACOMA INTL')).toBeInTheDocument(); diff --git a/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateInternationalOriginSITForm.jsx b/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateInternationalOriginSITForm.jsx new file mode 100644 index 00000000000..eead130eff2 --- /dev/null +++ b/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateInternationalOriginSITForm.jsx @@ -0,0 +1,103 @@ +import React from 'react'; +import { Formik } from 'formik'; +import { useNavigate, useParams, generatePath } from 'react-router-dom'; +import { FormGroup } from '@trussworks/react-uswds'; +import classnames from 'classnames'; + +import styles from './PrimeUIUpdateSITForms.module.scss'; + +import SectionWrapper from 'components/Customer/SectionWrapper'; +import formStyles from 'styles/form.module.scss'; +import { Form } from 'components/form/Form'; +import TextField from 'components/form/fields/TextField/TextField'; +import WizardNavigation from 'components/Customer/WizardNavigation/WizardNavigation'; +import descriptionListStyles from 'styles/descriptionList.module.scss'; +import { primeSimulatorRoutes } from 'constants/routes'; +import { DatePickerInput } from 'components/form/fields'; +import { SERVICE_ITEM_STATUSES } from 'constants/serviceItems'; + +const PrimeUIUpdateInternationalOriginSITForm = ({ initialValues, onSubmit, serviceItem }) => { + const { moveCodeOrID } = useParams(); + const navigate = useNavigate(); + + const handleClose = () => { + navigate(generatePath(primeSimulatorRoutes.VIEW_MOVE_PATH, { moveCodeOrID })); + }; + + return ( + + {({ handleSubmit }) => ( +
+ +
+

Update International Origin SIT Service Item

+ +
+ Here you can update specific fields for an origin SIT service item.
+ At this time, only the following values can be updated:
{' '} + + SIT Departure Date
+ SIT Requested Delivery
+ SIT Customer Contacted
+ Update Reason +
+
+
+
+ +

+ {serviceItem.reServiceCode} - {serviceItem.reServiceName} +

+
+
+
ID:
+
{serviceItem.id}
+
+
+
MTO ID:
+
{serviceItem.moveTaskOrderID}
+
+
+
Shipment ID:
+
{serviceItem.mtoShipmentID}
+
+
+
Status:
+
{serviceItem.status}
+
+
+
+ + + +
+ {serviceItem.status === SERVICE_ITEM_STATUSES.REJECTED && ( + + )} +
+ +
+
+
+ )} +
+ ); +}; + +export default PrimeUIUpdateInternationalOriginSITForm; diff --git a/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateInternationalOriginSITForm.test.jsx b/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateInternationalOriginSITForm.test.jsx new file mode 100644 index 00000000000..cad194d17c6 --- /dev/null +++ b/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateInternationalOriginSITForm.test.jsx @@ -0,0 +1,84 @@ +import React from 'react'; +import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import PrimeUIUpdateOriginSITForm from './PrimeUIUpdateOriginSITForm'; + +import { renderWithProviders } from 'testUtils'; +import { primeSimulatorRoutes } from 'constants/routes'; + +const originSitInitialValues = { + sitDepartureDate: '01 Nov 2023', + sitRequestedDelivery: '01 Dec 2023', + sitCustomerContacted: '15 Oct 2023', + mtoServiceItemID: '45fe9475-d592-48f5-896a-45d4d6eb7e76', + reServiceCode: 'IOPSIT', +}; + +const serviceItem = { + reServiceCode: 'IOPSIT', + reServiceName: 'International origin SIT pickup', +}; + +// Mock the react-router-dom functions +const mockNavigate = jest.fn(); +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useNavigate: () => mockNavigate, + useParams: jest.fn().mockReturnValue({ moveCodeOrID: ':moveCodeOrID' }), +})); + +describe('PrimeUIRequestInternationalSITDestAddressChangeForm', () => { + it('renders the address change request form', async () => { + renderWithProviders( + , + ); + + expect(screen.getByRole('heading', { name: 'Update Origin SIT Service Item', level: 2 })).toBeInTheDocument(); + expect( + screen.getByRole('heading', { name: 'IOPSIT - International origin SIT pickup', level: 3 }), + ).toBeInTheDocument(); + expect(await screen.findByLabelText('SIT Departure Date')).toHaveValue('01 Nov 2023'); + expect(await screen.findByLabelText('SIT Requested Delivery')).toHaveValue('01 Dec 2023'); + expect(await screen.findByLabelText('SIT Customer Contacted')).toHaveValue('15 Oct 2023'); + expect(screen.getByRole('button', { name: 'Save' })).toBeEnabled(); + expect(screen.getByRole('button', { name: 'Cancel' })).toBeEnabled(); + }); + + it('fires off onSubmit function when save button is clicked', async () => { + const onSubmitMock = jest.fn(); + renderWithProviders( + , + ); + + const saveButton = await screen.findByRole('button', { name: 'Save' }); + + await userEvent.click(saveButton); + + expect(onSubmitMock).toHaveBeenCalled(); + }); + + it('directs the user back to the move page when cancel button is clicked', async () => { + renderWithProviders( + , + ); + + const cancelButton = await screen.findByRole('button', { name: 'Cancel' }); + + await userEvent.click(cancelButton); + + expect(mockNavigate).toHaveBeenCalledWith(primeSimulatorRoutes.VIEW_MOVE_PATH); + }); +}); diff --git a/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateInternationalShuttleForm.jsx b/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateInternationalShuttleForm.jsx new file mode 100644 index 00000000000..5ab8c91c742 --- /dev/null +++ b/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateInternationalShuttleForm.jsx @@ -0,0 +1,175 @@ +import React from 'react'; +import { Formik } from 'formik'; +import { useNavigate, useParams, generatePath } from 'react-router-dom'; +import { FormGroup } from '@trussworks/react-uswds'; +import classnames from 'classnames'; + +import styles from './PrimeUIUpdateShuttleForms.module.scss'; + +import SectionWrapper from 'components/Customer/SectionWrapper'; +import formStyles from 'styles/form.module.scss'; +import { Form } from 'components/form/Form'; +import TextField from 'components/form/fields/TextField/TextField'; +import WizardNavigation from 'components/Customer/WizardNavigation/WizardNavigation'; +import descriptionListStyles from 'styles/descriptionList.module.scss'; +import { primeSimulatorRoutes } from 'constants/routes'; +import { SERVICE_ITEM_STATUSES } from 'constants/serviceItems'; +import MaskedTextField from 'components/form/fields/MaskedTextField/MaskedTextField'; +import { CheckboxField } from 'components/form/fields'; + +const PrimeUIUpdateInternationalShuttleForm = ({ onUpdateServiceItem, serviceItem }) => { + const { moveCodeOrID } = useParams(); + const navigate = useNavigate(); + + const handleClose = () => { + navigate(generatePath(primeSimulatorRoutes.VIEW_MOVE_PATH, { moveCodeOrID })); + }; + + const initialValues = { + mtoServiceItemID: serviceItem.id, + reServiceCode: serviceItem.reServiceCode, + eTag: serviceItem.eTag, + }; + + const onSubmit = (values) => { + const { + eTag, + mtoServiceItemID, + estimatedWeight, + actualWeight, + updateReason, + reServiceCode, + requestApprovalsRequestedStatus, + } = values; + + const body = { + reServiceCode, + modelType: 'UpdateMTOServiceItemInternationalShuttle', + actualWeight: Number.parseInt(actualWeight, 10), + estimatedWeight: Number.parseInt(estimatedWeight, 10), + updateReason, + requestApprovalsRequestedStatus, + }; + + onUpdateServiceItem({ mtoServiceItemID, eTag, body }); + }; + + return ( + + {({ handleSubmit, setFieldValue }) => ( +
+ +
+

Update International Shuttle Service Item

+ +
+ Here you can update specific fields for an international shuttle service item.
+ At this time, only the following values can be updated:
{' '} + + Estimated Weight
+ Actual Weight
+ Update Reason +
+
+
+
+ +

+ {serviceItem.reServiceCode} - {serviceItem.reServiceName} +

+
+
+
ID:
+
{serviceItem.id}
+
+
+
MTO ID:
+
{serviceItem.moveTaskOrderID}
+
+
+
Shipment ID:
+
{serviceItem.mtoShipmentID}
+
+
+
Status:
+
{serviceItem.status}
+
+ {serviceItem.status === SERVICE_ITEM_STATUSES.REJECTED && ( +
+
Rejection Reason:
+
{serviceItem.rejectionReason}
+
+ )} +
+
Estimated Weight:
+
{serviceItem.estimatedWeight}
+
+
+
Actual Weight:
+
{serviceItem.actualWeight}
+
+
+ { + setFieldValue('estimatedWeight', e.target.value); + }} + /> + { + setFieldValue('actualWeight', e.target.value); + }} + /> + {serviceItem.status === SERVICE_ITEM_STATUSES.REJECTED && ( + + )} + {serviceItem.status === SERVICE_ITEM_STATUSES.REJECTED && ( + + )} +
+ +
+
+
+ )} +
+ ); +}; + +export default PrimeUIUpdateInternationalShuttleForm; diff --git a/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateInternationalShuttleForm.test.jsx b/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateInternationalShuttleForm.test.jsx new file mode 100644 index 00000000000..3655d77b238 --- /dev/null +++ b/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateInternationalShuttleForm.test.jsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import PrimeUIUpdateInternationalShuttleForm from './PrimeUIUpdateInternationalShuttleForm'; + +import { renderWithProviders } from 'testUtils'; +import { primeSimulatorRoutes } from 'constants/routes'; + +const serviceItem = { + reServiceCode: 'IDSHUT', + reServiceName: 'International Shuttle', + estimatedWeight: 500, + actualWeight: 600, + mtoServiceItemID: '45fe9475-d592-48f5-896a-45d4d6eb7e76', +}; + +// Mock the react-router-dom functions +const mockNavigate = jest.fn(); +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useNavigate: () => mockNavigate, + useParams: jest.fn().mockReturnValue({ moveCodeOrID: ':moveCodeOrID' }), +})); + +describe('PrimeUIUpdateInternationalShuttleForm', () => { + it('renders the shuttle change request form', async () => { + renderWithProviders( + , + ); + + expect( + screen.getByRole('heading', { name: 'Update International Shuttle Service Item', level: 2 }), + ).toBeInTheDocument(); + expect(screen.getByTestId('estimatedWeightInput')).toBeInTheDocument(); + expect(screen.getByTestId('actualWeightInput')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Save' })).toBeEnabled(); + expect(screen.getByRole('button', { name: 'Cancel' })).toBeEnabled(); + }); + + it('fires off onSubmit function when save button is clicked', async () => { + const onSubmitMock = jest.fn(); + renderWithProviders( + , + ); + + const saveButton = await screen.findByRole('button', { name: 'Save' }); + + await userEvent.click(saveButton); + + expect(onSubmitMock).toHaveBeenCalled(); + }); + + it('directs the user back to the move page when cancel button is clicked', async () => { + renderWithProviders( + , + ); + + const cancelButton = await screen.findByRole('button', { name: 'Cancel' }); + + await userEvent.click(cancelButton); + + expect(mockNavigate).toHaveBeenCalledWith(primeSimulatorRoutes.VIEW_MOVE_PATH); + }); +}); diff --git a/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateOriginSITForm.jsx b/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateOriginSITForm.jsx index 240eb042f39..8865706bacd 100644 --- a/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateOriginSITForm.jsx +++ b/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateOriginSITForm.jsx @@ -36,6 +36,7 @@ const PrimeUIUpdateOriginSITForm = ({ initialValues, onSubmit, serviceItem }) => Here you can update specific fields for an origin SIT service item.
At this time, only the following values can be updated:
{' '} + SIT Entry Date
SIT Departure Date
SIT Requested Delivery
SIT Customer Contacted
@@ -67,8 +68,12 @@ const PrimeUIUpdateOriginSITForm = ({ initialValues, onSubmit, serviceItem }) =>
+ +
+
+
{serviceItem.status === SERVICE_ITEM_STATUSES.REJECTED && ( diff --git a/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateOriginSITForm.test.jsx b/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateOriginSITForm.test.jsx index 317f52b16bc..8503af25088 100644 --- a/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateOriginSITForm.test.jsx +++ b/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateOriginSITForm.test.jsx @@ -8,6 +8,7 @@ import { renderWithProviders } from 'testUtils'; import { primeSimulatorRoutes } from 'constants/routes'; const originSitInitialValues = { + sitEntryDate: '25 Oct 2023', sitDepartureDate: '01 Nov 2023', sitRequestedDelivery: '01 Dec 2023', sitCustomerContacted: '15 Oct 2023', @@ -40,6 +41,7 @@ describe('PrimeUIRequestSITDestAddressChangeForm', () => { expect(screen.getByRole('heading', { name: 'Update Origin SIT Service Item', level: 2 })).toBeInTheDocument(); expect(screen.getByRole('heading', { name: 'DOPSIT - Domestic origin SIT pickup', level: 3 })).toBeInTheDocument(); + expect(await screen.findByLabelText('SIT Entry Date')).toHaveValue('25 Oct 2023'); expect(await screen.findByLabelText('SIT Departure Date')).toHaveValue('01 Nov 2023'); expect(await screen.findByLabelText('SIT Requested Delivery')).toHaveValue('01 Dec 2023'); expect(await screen.findByLabelText('SIT Customer Contacted')).toHaveValue('15 Oct 2023'); diff --git a/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateServiceItem.jsx b/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateServiceItem.jsx index 387981922c4..de05308a0d6 100644 --- a/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateServiceItem.jsx +++ b/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateServiceItem.jsx @@ -6,7 +6,10 @@ import { connect } from 'react-redux'; import PrimeUIUpdateOriginSITForm from './PrimeUIUpdateOriginSITForm'; import PrimeUIUpdateDestSITForm from './PrimeUIUpdateDestSITForm'; +import PrimeUIUpdateInternationalOriginSITForm from './PrimeUIUpdateInternationalOriginSITForm'; +import PrimeUIUpdateInternationalDestSITForm from './PrimeUIUpdateInternationalDestSITForm'; import PrimeUIUpdateInternationalFuelSurchargeForm from './PrimeUIUpdateInternationalFuelSurchargeForm'; +import PrimeUIUpdateInternationalShuttleForm from './PrimeUIUpdateInternationalShuttleForm'; import { updateMTOServiceItem } from 'services/primeApi'; import scrollToTop from 'shared/scrollToTop'; @@ -71,8 +74,14 @@ const PrimeUIUpdateServiceItem = ({ setFlashMessage }) => { const { modelType } = serviceItem; let initialValues; let onSubmit; - if (modelType === 'MTOServiceItemOriginSIT' || modelType === 'MTOServiceItemDestSIT') { + if ( + modelType === 'MTOServiceItemOriginSIT' || + modelType === 'MTOServiceItemDestSIT' || + modelType === 'MTOServiceItemInternationalDestSIT' || + modelType === 'MTOServiceItemInternationalOriginSIT' + ) { initialValues = { + sitEntryDate: formatDateWithUTC(serviceItem.sitEntryDate, 'YYYY-MM-DD', 'DD MMM YYYY') || '', sitDepartureDate: formatDateWithUTC(serviceItem.sitDepartureDate, 'YYYY-MM-DD', 'DD MMM YYYY') || '', sitRequestedDelivery: formatDateWithUTC(serviceItem.sitRequestedDelivery, 'YYYY-MM-DD', 'DD MMM YYYY') || '', sitCustomerContacted: formatDateWithUTC(serviceItem.sitCustomerContacted, 'YYYY-MM-DD', 'DD MMM YYYY') || '', @@ -85,6 +94,7 @@ const PrimeUIUpdateServiceItem = ({ setFlashMessage }) => { onSubmit = (values) => { const { sitCustomerContacted, + sitEntryDate, sitDepartureDate, sitRequestedDelivery, updateReason, @@ -94,6 +104,7 @@ const PrimeUIUpdateServiceItem = ({ setFlashMessage }) => { } = values; const body = { + sitEntryDate: sitEntryDate === 'Invalid date' ? null : formatDateForSwagger(sitEntryDate), sitDepartureDate: sitDepartureDate === 'Invalid date' ? null : formatDateForSwagger(sitDepartureDate), sitRequestedDelivery: sitRequestedDelivery === 'Invalid date' ? null : formatDateForSwagger(sitRequestedDelivery), @@ -141,6 +152,21 @@ const PrimeUIUpdateServiceItem = ({ setFlashMessage }) => { onSubmit={onSubmit} /> ) : null} + {modelType === 'MTOServiceItemInternationalDestSIT' ? ( + + ) : null} + {modelType === 'MTOServiceItemInternationalOriginSIT' ? ( + + ) : null} {modelType === 'MTOServiceItemInternationalFuelSurcharge' ? ( { onUpdateServiceItem={createUpdateServiceItemRequestMutation} /> ) : null} + {modelType === 'MTOServiceItemInternationalShuttle' ? ( + + ) : null} diff --git a/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateShuttleForms.module.scss b/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateShuttleForms.module.scss new file mode 100644 index 00000000000..ce30f6f0000 --- /dev/null +++ b/src/pages/PrimeUI/UpdateServiceItems/PrimeUIUpdateShuttleForms.module.scss @@ -0,0 +1,23 @@ +@import 'shared/styles/basics'; +@import 'shared/styles/colors'; + +.Shuttle { + + .shuttleHeader { + text-align: center; + } + + .shuttleDestAddressHeader { + text-align: center; + @include u-margin-bottom(-2); + } + + .shuttleDatePickerRow { + @include u-margin-top(-4); + display: flex; + justify-content: space-between; + } + + @include u-margin-bottom(4); + +} \ No newline at end of file diff --git a/src/pages/PrimeUI/UpdateServiceItems/PrimeUpdateSitServiceItem.test.jsx b/src/pages/PrimeUI/UpdateServiceItems/PrimeUpdateSitServiceItem.test.jsx index e7f21f94309..f08f7386f08 100644 --- a/src/pages/PrimeUI/UpdateServiceItems/PrimeUpdateSitServiceItem.test.jsx +++ b/src/pages/PrimeUI/UpdateServiceItems/PrimeUpdateSitServiceItem.test.jsx @@ -69,6 +69,7 @@ describe('PrimeUIUpdateSitServiceItems page', () => { renderComponent(); expect(screen.getByRole('heading', { name: 'Update Destination SIT Service Item', level: 2 })).toBeInTheDocument(); + expect(screen.getByRole('textbox', { name: 'SIT Entry Date' })).toBeInTheDocument(); expect(screen.getByRole('textbox', { name: 'SIT Departure Date' })).toBeInTheDocument(); expect(screen.getByRole('textbox', { name: 'SIT Requested Delivery' })).toBeInTheDocument(); expect(screen.getByRole('textbox', { name: 'SIT Customer Contacted' })).toBeInTheDocument(); @@ -131,6 +132,135 @@ describe('PrimeUIUpdateSitServiceItems page', () => { renderComponent(); expect(screen.getByRole('heading', { name: 'Update Origin SIT Service Item', level: 2 })).toBeInTheDocument(); + expect(screen.getByRole('textbox', { name: 'SIT Entry Date' })).toBeInTheDocument(); + expect(screen.getByRole('textbox', { name: 'SIT Departure Date' })).toBeInTheDocument(); + expect(screen.getByRole('textbox', { name: 'SIT Requested Delivery' })).toBeInTheDocument(); + expect(screen.getByRole('textbox', { name: 'SIT Customer Contacted' })).toBeInTheDocument(); + expect(screen.getByRole('textbox', { name: 'Update Reason' })).toBeInTheDocument(); + }); + + it('renders the destination sit service item form - international', async () => { + const routingParams = { + moveCodeOrID: 'bf2fc98f-3cb5-40a0-a125-4c222096c35b', + mtoServiceItemId: '45fe9475-d592-48f5-896a-45d4d6eb7e76', + }; + + const renderComponent = () => { + render( + + + + + , + ); + }; + + const moveTaskOrder = { + id: '1', + moveCode: 'LN4T89', + mtoShipments: [ + { + id: '2', + shipmentType: 'HHG', + requestedPickupDate: '2021-11-26', + pickupAddress: { streetAddress1: '100 1st Avenue', city: 'New York', state: 'NY', postalCode: '10001' }, + }, + ], + mtoServiceItems: [ + { + reServiceCode: 'IDDSIT', + modelType: 'MTOServiceItemInternationalDestSIT', + reason: 'Holiday break', + sitDestinationFinalAddress: { + streetAddress1: '444 Main Ave', + streetAddress2: 'Apartment 9000', + streetAddress3: 'c/o Some Person', + city: 'Anytown', + state: 'AL', + postalCode: '90210', + }, + id: '45fe9475-d592-48f5-896a-45d4d6eb7e76', + status: 'REJECTED', + }, + ], + }; + + const moveReturnValue = { + moveTaskOrder, + isLoading: false, + isError: false, + }; + usePrimeSimulatorGetMove.mockReturnValue(moveReturnValue); + + renderComponent(); + + expect( + screen.getByRole('heading', { name: 'Update International Destination SIT Service Item', level: 2 }), + ).toBeInTheDocument(); + expect(screen.getByRole('textbox', { name: 'SIT Departure Date' })).toBeInTheDocument(); + expect(screen.getByRole('textbox', { name: 'SIT Requested Delivery' })).toBeInTheDocument(); + expect(screen.getByRole('textbox', { name: 'SIT Customer Contacted' })).toBeInTheDocument(); + expect(screen.getByRole('textbox', { name: 'Update Reason' })).toBeInTheDocument(); + }); + + it('renders the origin sit service item form - international', async () => { + const routingParams = { + moveCodeOrID: 'bf2fc98f-3cb5-40a0-a125-4c222096c35b', + mtoServiceItemId: '45fe9475-d592-48f5-896a-45d4d6eb7e76', + }; + + const renderComponent = () => { + render( + + + + + , + ); + }; + + const moveTaskOrder = { + id: '1', + moveCode: 'LN4T89', + mtoShipments: [ + { + id: '2', + shipmentType: 'HHG', + requestedPickupDate: '2021-11-26', + pickupAddress: { streetAddress1: '100 1st Avenue', city: 'New York', state: 'NY', postalCode: '10001' }, + }, + ], + mtoServiceItems: [ + { + reServiceCode: 'IDDSIT', + modelType: 'MTOServiceItemInternationalOriginSIT', + reason: 'Holiday break', + sitDestinationFinalAddress: { + streetAddress1: '444 Main Ave', + streetAddress2: 'Apartment 9000', + streetAddress3: 'c/o Some Person', + city: 'Anytown', + state: 'AL', + postalCode: '90210', + }, + id: '45fe9475-d592-48f5-896a-45d4d6eb7e76', + status: 'REJECTED', + }, + ], + }; + + const moveReturnValue = { + moveTaskOrder, + isLoading: false, + isError: false, + }; + usePrimeSimulatorGetMove.mockReturnValue(moveReturnValue); + + renderComponent(); + + expect( + screen.getByRole('heading', { name: 'Update International Origin SIT Service Item', level: 2 }), + ).toBeInTheDocument(); expect(screen.getByRole('textbox', { name: 'SIT Departure Date' })).toBeInTheDocument(); expect(screen.getByRole('textbox', { name: 'SIT Requested Delivery' })).toBeInTheDocument(); expect(screen.getByRole('textbox', { name: 'SIT Customer Contacted' })).toBeInTheDocument(); diff --git a/src/services/ghcApi.js b/src/services/ghcApi.js index b05441939fc..6e4409cf5c9 100644 --- a/src/services/ghcApi.js +++ b/src/services/ghcApi.js @@ -643,6 +643,22 @@ export async function getMovesQueue( ); } +export async function getDestinationRequestsQueue( + key, + { sort, order, filters = [], currentPage = 1, currentPageSize = 20, viewAsGBLOC }, +) { + const operationPath = 'queues.getDestinationRequestsQueue'; + const paramFilters = {}; + filters.forEach((filter) => { + paramFilters[`${filter.id}`] = filter.value; + }); + return makeGHCRequest( + operationPath, + { sort, order, page: currentPage, perPage: currentPageSize, viewAsGBLOC, ...paramFilters }, + { schemaKey: 'queueMovesResult', normalize: false }, + ); +} + export async function getServicesCounselingQueue( key, { @@ -947,7 +963,7 @@ export async function updateAssignedOfficeUserForMove({ moveID, officeUserId, ro } export async function checkForLockedMovesAndUnlock(key, officeUserID) { - return makeGHCRequest('move.checkForLockedMovesAndUnlock', { + return makeGHCRequestRaw('move.checkForLockedMovesAndUnlock', { officeUserID, }); } diff --git a/src/shared/ErrorModal/ErrorModal.jsx b/src/shared/ErrorModal/ErrorModal.jsx index cf6f28794aa..2706dce1c8c 100644 --- a/src/shared/ErrorModal/ErrorModal.jsx +++ b/src/shared/ErrorModal/ErrorModal.jsx @@ -11,9 +11,7 @@ export const ErrorModal = ({ closeModal, errorMessage, displayHelpDeskLink = tru {errorMessage} - {displayHelpDeskLink && ( - Technical Help Desk - )} + {displayHelpDeskLink && Technical Help Desk}